web/satellite: tslint update (#2962)

This commit is contained in:
Yehor Butko 2019-09-09 13:33:39 +03:00 committed by GitHub
parent 64602c3007
commit 2b9fcd1191
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 3037 additions and 2893 deletions

View File

@ -10,45 +10,41 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import NotificationArea from '@/components/notifications/NotificationArea.vue';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { Component, Vue } from 'vue-property-decorator';
@Component({
data: function() {
return {
ids: [
'accountDropdown',
'accountDropdownButton',
'projectDropdown',
'projectDropdownButton',
'sortTeamMemberByDropdown',
'sortTeamMemberByDropdownButton',
'notificationArea',
'successfulRegistrationPopup',
]
};
},
components: {
NotificationArea
},
methods: {
onClick: function(e) {
let target: any = e.target;
while (target) {
if (this.$data.ids.includes(target.id)) {
return;
}
target = target.parentNode;
}
import NotificationArea from '@/components/notifications/NotificationArea.vue';
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
@Component({
components: {
NotificationArea
},
})
export default class App extends Vue {
private ids: string[] = [
'accountDropdown',
'accountDropdownButton',
'projectDropdown',
'projectDropdownButton',
'sortTeamMemberByDropdown',
'sortTeamMemberByDropdownButton',
'notificationArea',
'successfulRegistrationPopup',
];
private onClick(e: Event): void {
let target: any = e.target;
while (target) {
if (this.$data.ids.includes(target.id)) {
return;
}
target = target.parentNode;
}
})
export default class App extends Vue {
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
}
}
</script>
<style lang="scss">

View File

@ -68,8 +68,8 @@ export class ApiKeysApiGql extends BaseGql implements ApiKeysApi {
};
const response = await this.mutate(query, variables);
let key: any = response.data.createAPIKey.keyInfo;
let secret: string = response.data.createAPIKey.key;
const key: any = response.data.createAPIKey.keyInfo;
const secret: string = response.data.createAPIKey.key;
return new ApiKey(key.id, key.name, key.createdAt, secret);
}

View File

@ -2,6 +2,7 @@
// See LICENSE for copying information.
import gql from 'graphql-tag';
import apollo from '@/utils/apollo';
/**
@ -16,7 +17,7 @@ export class BaseGql {
* @throws Error
*/
protected async query(query: string, variables: any = null): Promise<any> {
let response: any = await apollo.query(
const response: any = await apollo.query(
{
query: gql(query),
variables,
@ -40,7 +41,7 @@ export class BaseGql {
* @throws Error
*/
protected async mutate(query: string, variables: any = null): Promise<any> {
let response: any = await apollo.mutate(
const response: any = await apollo.mutate(
{
mutation: gql(query),
variables,

View File

@ -1,14 +1,15 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import apollo from '@/utils/apollo';
import gql from 'graphql-tag';
import { RequestResponse } from '@/types/response';
import apollo from '@/utils/apollo';
export async function addProjectPaymentMethodRequest(projectId: string, cardToken: string, isDefault: boolean): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = new RequestResponse<null>();
const result: RequestResponse<null> = new RequestResponse<null>();
let response: any = await apollo.mutate(
const response: any = await apollo.mutate(
{
mutation: gql(`
mutation($projectId: String!, cardToken: String!, isDefault: Boolean!) {
@ -39,9 +40,9 @@ export async function addProjectPaymentMethodRequest(projectId: string, cardToke
}
export async function setDefaultPaymentMethodRequest(projectId: string, paymentId: string): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = new RequestResponse<null>();
const result: RequestResponse<null> = new RequestResponse<null>();
let response: any = await apollo.mutate(
const response: any = await apollo.mutate(
{
mutation: gql(`
mutation($projectId: String!, paymentId: String!) {
@ -70,9 +71,9 @@ export async function setDefaultPaymentMethodRequest(projectId: string, paymentI
}
export async function deletePaymentMethodRequest(paymentId: string):Promise<RequestResponse<null>> {
let result: RequestResponse<null> = new RequestResponse<null>();
const result: RequestResponse<null> = new RequestResponse<null>();
let response: any = await apollo.mutate(
const response: any = await apollo.mutate(
{
mutation: gql(`
mutation($id: String!) {
@ -100,9 +101,9 @@ export async function deletePaymentMethodRequest(paymentId: string):Promise<Requ
// fetchProjectInvoices retrieves project invoices
export async function fetchProjectPaymentMethods(projectId: string): Promise<RequestResponse<PaymentMethod[]>> {
let result: RequestResponse<PaymentMethod[]> = new RequestResponse<PaymentMethod[]>();
const result: RequestResponse<PaymentMethod[]> = new RequestResponse<PaymentMethod[]>();
let response: any = await apollo.query(
const response: any = await apollo.query(
{
query: gql(`
query($projectId: String!) {

View File

@ -1,10 +1,11 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import apollo from '@/utils/apollo';
import gql from 'graphql-tag';
import { ProjectUsage, UsageApi } from '@/types/usage';
import { BaseGql } from '@/api/baseGql';
import { ProjectUsage, UsageApi } from '@/types/usage';
import apollo from '@/utils/apollo';
/**
* Exposes all project-usage-related functionality

View File

@ -12,23 +12,25 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import TabNavigation from '@/components/navigation/TabNavigation.vue';
import { NavigationLink } from '@/types/navigation';
import { RouteConfig } from '@/router';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
TabNavigation,
},
})
export default class AccountArea extends Vue {
public navigation: NavigationLink[] = [
RouteConfig.Account.with(RouteConfig.Profile),
RouteConfig.Account.with(RouteConfig.Billing),
RouteConfig.Account.with(RouteConfig.PaymentMethods),
];
}
import TabNavigation from '@/components/navigation/TabNavigation.vue';
import { RouteConfig } from '@/router';
import { NavigationLink } from '@/types/navigation';
@Component({
components: {
TabNavigation,
},
})
export default class AccountArea extends Vue {
public navigation: NavigationLink[] = [
RouteConfig.Account.with(RouteConfig.Profile),
RouteConfig.Account.with(RouteConfig.Billing),
RouteConfig.Account.with(RouteConfig.PaymentMethods),
];
}
</script>
<style scoped lang="scss">

View File

@ -44,15 +44,16 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Button,
}
})
export default class AccountPaymentMethods extends Vue {}
import Button from '@/components/common/Button.vue';
@Component({
components: {
Button,
}
})
export default class AccountPaymentMethods extends Vue {}
</script>
<style scoped lang="scss">

View File

@ -60,86 +60,88 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
import Button from '@/components/common/Button.vue';
import { NOTIFICATION_ACTIONS, APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { validatePassword } from '@/utils/validation';
import { AuthApi } from '@/api/auth';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
HeaderlessInput,
Button,
}
})
export default class ChangePasswordPopup extends Vue {
private oldPassword: string = '';
private newPassword: string = '';
private confirmationPassword: string = '';
private oldPasswordError: string = '';
private newPasswordError: string = '';
private confirmationPasswordError: string = '';
import Button from '@/components/common/Button.vue';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
private readonly auth: AuthApi = new AuthApi();
import { AuthApi } from '@/api/auth';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { validatePassword } from '@/utils/validation';
public setOldPassword(value: string): void {
this.oldPassword = value;
this.oldPasswordError = '';
}
public setNewPassword(value: string): void {
this.newPassword = value;
this.newPasswordError = '';
}
public setPasswordConfirmation(value: string): void {
this.confirmationPassword = value;
this.confirmationPasswordError = '';
}
public async onUpdateClick(): Promise<void> {
let hasError = false;
if (!this.oldPassword) {
this.oldPasswordError = 'Password required';
hasError = true;
}
if (!validatePassword(this.newPassword)) {
this.newPasswordError = 'Invalid password. Use 6 or more characters';
hasError = true;
}
if (!this.confirmationPassword) {
this.confirmationPasswordError = 'Password required';
hasError = true;
}
if (this.newPassword !== this.confirmationPassword) {
this.confirmationPasswordError = 'Password not match to new one';
hasError = true;
}
if (hasError) {
return;
}
try {
await this.auth.changePassword(this.oldPassword, this.newPassword);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
return;
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Password successfully changed!');
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
}
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
}
@Component({
components: {
HeaderlessInput,
Button,
}
})
export default class ChangePasswordPopup extends Vue {
private oldPassword: string = '';
private newPassword: string = '';
private confirmationPassword: string = '';
private oldPasswordError: string = '';
private newPasswordError: string = '';
private confirmationPasswordError: string = '';
private readonly auth: AuthApi = new AuthApi();
public setOldPassword(value: string): void {
this.oldPassword = value;
this.oldPasswordError = '';
}
public setNewPassword(value: string): void {
this.newPassword = value;
this.newPasswordError = '';
}
public setPasswordConfirmation(value: string): void {
this.confirmationPassword = value;
this.confirmationPasswordError = '';
}
public async onUpdateClick(): Promise<void> {
let hasError = false;
if (!this.oldPassword) {
this.oldPasswordError = 'Password required';
hasError = true;
}
if (!validatePassword(this.newPassword)) {
this.newPasswordError = 'Invalid password. Use 6 or more characters';
hasError = true;
}
if (!this.confirmationPassword) {
this.confirmationPasswordError = 'Password required';
hasError = true;
}
if (this.newPassword !== this.confirmationPassword) {
this.confirmationPasswordError = 'Password not match to new one';
hasError = true;
}
if (hasError) {
return;
}
try {
await this.auth.changePassword(this.oldPassword, this.newPassword);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
return;
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Password successfully changed!');
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
}
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
}
}
</script>
<style scoped lang="scss">

View File

@ -63,56 +63,58 @@
</template>
<script lang='ts'>
import { Component, Vue } from 'vue-property-decorator';
import { AuthApi } from '@/api/auth';
import Button from '@/components/common/Button.vue';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { AuthToken } from '@/utils/authToken';
import { RouteConfig } from '@/router';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
HeaderedInput,
Button
}
})
export default class DeleteAccountPopup extends Vue {
public passwordError: string = '';
private password: string = '';
private isLoading: boolean = false;
import Button from '@/components/common/Button.vue';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
private readonly auth: AuthApi = new AuthApi();
import { AuthApi } from '@/api/auth';
import { RouteConfig } from '@/router';
import { AuthToken } from '@/utils/authToken';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
public setPassword(value: string): void {
this.password = value;
@Component({
components: {
HeaderedInput,
Button
}
})
export default class DeleteAccountPopup extends Vue {
public passwordError: string = '';
private password: string = '';
private isLoading: boolean = false;
private readonly auth: AuthApi = new AuthApi();
public setPassword(value: string): void {
this.password = value;
}
public async onDeleteAccountClick(): Promise<void> {
if (this.isLoading) {
return;
}
public async onDeleteAccountClick(): Promise<void> {
if (this.isLoading) {
return;
}
this.isLoading = true;
this.isLoading = true;
try {
await this.auth.delete(this.password);
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Account was successfully deleted');
try {
await this.auth.delete(this.password);
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Account was successfully deleted');
AuthToken.remove();
AuthToken.remove();
this.isLoading = false;
this.$router.push(RouteConfig.Login.path);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
this.isLoading = false;
}
}
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_ACCOUNT);
this.isLoading = false;
this.$router.push(RouteConfig.Login.path);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
this.isLoading = false;
}
}
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_ACCOUNT);
}
}
</script>
<style scoped lang='scss'>

View File

@ -43,62 +43,64 @@
</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 { NOTIFICATION_ACTIONS, APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { USER_ACTIONS } from '@/store/modules/users';
import { UpdatedUser } from '@/types/users';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
HeaderedInput,
Button,
}
})
export default class EditProfilePopup extends Vue {
private fullNameError: string = '';
import Button from '@/components/common/Button.vue';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
private readonly userInfo: UpdatedUser =
new UpdatedUser(this.$store.getters.user.fullName, this.$store.getters.user.shortName);
import { USER_ACTIONS } from '@/store/modules/users';
import { UpdatedUser } from '@/types/users';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
public setFullName(value: string): void {
this.userInfo.setFullName(value);
this.fullNameError = '';
}
public setShortName(value: string): void {
this.userInfo.setShortName(value);
}
public async onUpdateClick(): Promise<void> {
if (!this.userInfo.isValid()) {
this.fullNameError = 'Full name expected';
return;
}
try {
await this.$store.dispatch(USER_ACTIONS.UPDATE, this.userInfo);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
return;
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Account info successfully updated!');
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP);
}
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP);
}
public get avatarLetter(): string {
return this.$store.getters.userName.slice(0, 1).toUpperCase();
}
@Component({
components: {
HeaderedInput,
Button,
}
})
export default class EditProfilePopup extends Vue {
private fullNameError: string = '';
private readonly userInfo: UpdatedUser =
new UpdatedUser(this.$store.getters.user.fullName, this.$store.getters.user.shortName);
public setFullName(value: string): void {
this.userInfo.setFullName(value);
this.fullNameError = '';
}
public setShortName(value: string): void {
this.userInfo.setShortName(value);
}
public async onUpdateClick(): Promise<void> {
if (!this.userInfo.isValid()) {
this.fullNameError = 'Full name expected';
return;
}
try {
await this.$store.dispatch(USER_ACTIONS.UPDATE, this.userInfo);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
return;
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Account info successfully updated!');
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP);
}
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP);
}
public get avatarLetter(): string {
return this.$store.getters.userName.slice(0, 1).toUpperCase();
}
}
</script>
<style scoped lang="scss">

View File

@ -68,54 +68,56 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { USER_ACTIONS } from '@/store/modules/users';
import DeleteAccountPopup from '@/components/account/DeleteAccountPopup.vue';
import ChangePasswordPopup from '@/components/account/ChangePasswordPopup.vue';
import EditProfilePopup from '@/components/account/EditProfilePopup.vue';
import { User } from '@/types/users';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Button,
DeleteAccountPopup,
ChangePasswordPopup,
EditProfilePopup,
},
})
export default class Profile extends Vue {
public mounted(): void {
this.$store.dispatch(USER_ACTIONS.GET);
}
import ChangePasswordPopup from '@/components/account/ChangePasswordPopup.vue';
import DeleteAccountPopup from '@/components/account/DeleteAccountPopup.vue';
import EditProfilePopup from '@/components/account/EditProfilePopup.vue';
import Button from '@/components/common/Button.vue';
public toggleDeleteAccountPopup(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_ACCOUNT);
}
public toggleChangePasswordPopup(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
}
public toggleEditProfilePopup(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP);
}
import { USER_ACTIONS } from '@/store/modules/users';
import { User } from '@/types/users';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
public get user(): User {
return this.$store.getters.user;
}
public get isEditProfilePopupShown(): boolean {
return this.$store.state.appStateModule.appState.isEditProfilePopupShown;
}
public get isChangePasswordPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isChangePasswordPopupShown;
}
public get isDeleteAccountPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isDeleteAccountPopupShown;
}
public get avatarLetter(): string {
return this.$store.getters.userName.slice(0, 1).toUpperCase();
}
@Component({
components: {
Button,
DeleteAccountPopup,
ChangePasswordPopup,
EditProfilePopup,
},
})
export default class Profile extends Vue {
public mounted(): void {
this.$store.dispatch(USER_ACTIONS.GET);
}
public toggleDeleteAccountPopup(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_ACCOUNT);
}
public toggleChangePasswordPopup(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
}
public toggleEditProfilePopup(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP);
}
public get user(): User {
return this.$store.getters.user;
}
public get isEditProfilePopupShown(): boolean {
return this.$store.state.appStateModule.appState.isEditProfilePopupShown;
}
public get isChangePasswordPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isChangePasswordPopupShown;
}
public get isDeleteAccountPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isDeleteAccountPopupShown;
}
public get avatarLetter(): string {
return this.$store.getters.userName.slice(0, 1).toUpperCase();
}
}
</script>
<style scoped lang="scss">

View File

@ -17,19 +17,20 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Button
}
})
export default class AccountBalance extends Vue {
public onEarnCredits(): void {
return;
}
import Button from '@/components/common/Button.vue';
@Component({
components: {
Button
}
})
export default class AccountBalance extends Vue {
public onEarnCredits(): void {
return;
}
}
</script>
<style scoped lang="scss">

View File

@ -10,19 +10,20 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import MonthlyBillingSummary from '@/components/account/billing/MonthlyBillingSummary.vue';
import AccountBalance from '@/components/account/billing/AccountBalance.vue';
import DepositAndBilling from '@/components/account/billing/DepositAndBilling.vue';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
AccountBalance,
MonthlyBillingSummary,
DepositAndBilling,
}
})
export default class BillingArea extends Vue {}
import AccountBalance from '@/components/account/billing/AccountBalance.vue';
import DepositAndBilling from '@/components/account/billing/DepositAndBilling.vue';
import MonthlyBillingSummary from '@/components/account/billing/MonthlyBillingSummary.vue';
@Component({
components: {
AccountBalance,
MonthlyBillingSummary,
DepositAndBilling,
}
})
export default class BillingArea extends Vue {}
</script>
<style scoped lang="scss">

View File

@ -15,17 +15,18 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import BillingItem from '@/components/account/billing/BillingItem.vue';
import SortingHeader from '@/components/account/billing/SortingHeader.vue';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
BillingItem,
SortingHeader,
}
})
export default class DepositAndBilling extends Vue {}
import BillingItem from '@/components/account/billing/BillingItem.vue';
import SortingHeader from '@/components/account/billing/SortingHeader.vue';
@Component({
components: {
BillingItem,
SortingHeader,
}
})
export default class DepositAndBilling extends Vue {}
</script>
<style scoped lang="scss">

View File

@ -72,26 +72,27 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Button
}
})
export default class MonthlyBillingSummary extends Vue {
private areUsageChargesShown: boolean = false;
private areReferralCreditsShown: boolean = false;
import Button from '@/components/common/Button.vue';
public toggleUsageChargesPopup(): void {
this.areUsageChargesShown = !this.areUsageChargesShown;
}
public toggleReferralCreditsPopup(): void {
this.areReferralCreditsShown = !this.areReferralCreditsShown;
}
@Component({
components: {
Button
}
})
export default class MonthlyBillingSummary extends Vue {
private areUsageChargesShown: boolean = false;
private areReferralCreditsShown: boolean = false;
public toggleUsageChargesPopup(): void {
this.areUsageChargesShown = !this.areUsageChargesShown;
}
public toggleReferralCreditsPopup(): void {
this.areReferralCreditsShown = !this.areReferralCreditsShown;
}
}
</script>
<style scoped lang="scss">

View File

@ -15,15 +15,16 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class PaginationArea extends Vue {
// TODO: use svg loader in future
public arrowLeft: string = EMPTY_STATE_IMAGES.ARROW_LEFT;
public arrowRight: string = EMPTY_STATE_IMAGES.ARROW_RIGHT;
}
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
@Component
export default class PaginationArea extends Vue {
// TODO: use svg loader in future
public arrowLeft: string = EMPTY_STATE_IMAGES.ARROW_LEFT;
public arrowRight: string = EMPTY_STATE_IMAGES.ARROW_RIGHT;
}
</script>
<style scoped lang="scss">

View File

@ -56,138 +56,141 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VueClipboards from 'vue-clipboards';
import ApiKeysCreationPopup from './ApiKeysCreationPopup.vue';
import ApiKeysCopyPopup from './ApiKeysCopyPopup.vue';
import ApiKeysItem from '@/components/apiKeys/ApiKeysItem.vue';
import Button from '@/components/common/Button.vue';
import EmptyState from '@/components/common/EmptyStateArea.vue';
import List from '@/components/common/List.vue';
import HeaderComponent from '@/components/common/HeaderComponent.vue';
import SortingHeader from '@/components/apiKeys/SortingHeader.vue';
import { API_KEYS_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import { ApiKey } from '@/types/apiKeys';
import VueClipboards from 'vue-clipboards';
import { Component, Vue } from 'vue-property-decorator';
Vue.use(VueClipboards);
import ApiKeysItem from '@/components/apiKeys/ApiKeysItem.vue';
import SortingHeader from '@/components/apiKeys/SortingHeader.vue';
import Button from '@/components/common/Button.vue';
import EmptyState from '@/components/common/EmptyStateArea.vue';
import HeaderComponent from '@/components/common/HeaderComponent.vue';
import List from '@/components/common/List.vue';
// header state depends on api key selection state
enum HeaderState {
DEFAULT = 0,
ON_SELECT,
import { ApiKey } from '@/types/apiKeys';
import { API_KEYS_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import ApiKeysCopyPopup from './ApiKeysCopyPopup.vue';
import ApiKeysCreationPopup from './ApiKeysCreationPopup.vue';
Vue.use(VueClipboards);
// header state depends on api key selection state
enum HeaderState {
DEFAULT = 0,
ON_SELECT,
}
const {
FETCH,
DELETE,
TOGGLE_SELECTION,
CLEAR_SELECTION,
} = API_KEYS_ACTIONS;
@Component({
components: {
List,
EmptyState,
HeaderComponent,
ApiKeysItem,
Button,
ApiKeysCreationPopup,
ApiKeysCopyPopup,
SortingHeader,
},
})
export default class ApiKeysArea extends Vue {
public emptyImage: string = EMPTY_STATE_IMAGES.API_KEY;
private isDeleteClicked: boolean = false;
private isNewApiKeyPopupShown: boolean = false;
private isCopyApiKeyPopupShown: boolean = false;
private apiKeySecret: string = '';
public mounted(): void {
this.$store.dispatch(FETCH);
}
const {
FETCH,
DELETE,
TOGGLE_SELECTION,
CLEAR_SELECTION,
} = API_KEYS_ACTIONS;
@Component({
components: {
List,
EmptyState,
HeaderComponent,
ApiKeysItem,
Button,
ApiKeysCreationPopup,
ApiKeysCopyPopup,
SortingHeader,
},
})
export default class ApiKeysArea extends Vue {
public emptyImage: string = EMPTY_STATE_IMAGES.API_KEY;
private isDeleteClicked: boolean = false;
private isNewApiKeyPopupShown: boolean = false;
private isCopyApiKeyPopupShown: boolean = false;
private apiKeySecret: string = '';
public mounted(): void {
this.$store.dispatch(FETCH);
}
public toggleSelection(apiKey: ApiKey): void {
this.$store.dispatch(TOGGLE_SELECTION, apiKey.id);
}
public onCreateApiKeyClick(): void {
this.isNewApiKeyPopupShown = true;
}
public onFirstDeleteClick(): void {
this.isDeleteClicked = true;
}
public onClearSelection(): void {
this.$store.dispatch(CLEAR_SELECTION);
this.isDeleteClicked = false;
}
public closeNewApiKeyPopup() {
this.isNewApiKeyPopupShown = false;
}
public showCopyApiKeyPopup(secret: string) {
this.isCopyApiKeyPopupShown = true;
this.apiKeySecret = secret;
}
public closeCopyNewApiKeyPopup() {
this.isCopyApiKeyPopupShown = false;
}
public async onDelete(): Promise<void> {
let selectedKeys: string[] = this.$store.getters.selectedAPIKeys.map((key) => { return key.id; });
let keySuffix = selectedKeys.length > 1 ? '\'s' : '';
try {
await this.$store.dispatch(DELETE, selectedKeys);
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, `API key${keySuffix} deleted successfully`);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
}
this.isDeleteClicked = false;
}
public get itemComponent() {
return ApiKeysItem;
}
public get apiKeyList(): ApiKey[] {
return this.$store.getters.apiKeys;
}
public get apiKeyCountTitle(): string {
if (this.selectedAPIKeysCount === 1) {
return 'api key';
}
return 'api keys';
}
public get isEmpty(): boolean {
return this.$store.getters.apiKeys.length === 0;
}
public get isSelected(): boolean {
return this.$store.getters.selectedAPIKeys.length > 0;
}
public get selectedAPIKeysCount(): number {
return this.$store.getters.selectedAPIKeys.length;
}
public get headerState(): number {
if (this.selectedAPIKeysCount > 0) {
return HeaderState.ON_SELECT;
}
return HeaderState.DEFAULT;
}
public toggleSelection(apiKey: ApiKey): void {
this.$store.dispatch(TOGGLE_SELECTION, apiKey.id);
}
public onCreateApiKeyClick(): void {
this.isNewApiKeyPopupShown = true;
}
public onFirstDeleteClick(): void {
this.isDeleteClicked = true;
}
public onClearSelection(): void {
this.$store.dispatch(CLEAR_SELECTION);
this.isDeleteClicked = false;
}
public closeNewApiKeyPopup() {
this.isNewApiKeyPopupShown = false;
}
public showCopyApiKeyPopup(secret: string) {
this.isCopyApiKeyPopupShown = true;
this.apiKeySecret = secret;
}
public closeCopyNewApiKeyPopup() {
this.isCopyApiKeyPopupShown = false;
}
public async onDelete(): Promise<void> {
const selectedKeys: string[] = this.$store.getters.selectedAPIKeys.map((key) => key.id);
const keySuffix = selectedKeys.length > 1 ? '\'s' : '';
try {
await this.$store.dispatch(DELETE, selectedKeys);
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, `API key${keySuffix} deleted successfully`);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
}
this.isDeleteClicked = false;
}
public get itemComponent() {
return ApiKeysItem;
}
public get apiKeyList(): ApiKey[] {
return this.$store.getters.apiKeys;
}
public get apiKeyCountTitle(): string {
if (this.selectedAPIKeysCount === 1) {
return 'api key';
}
return 'api keys';
}
public get isEmpty(): boolean {
return this.$store.getters.apiKeys.length === 0;
}
public get isSelected(): boolean {
return this.$store.getters.selectedAPIKeys.length > 0;
}
public get selectedAPIKeysCount(): number {
return this.$store.getters.selectedAPIKeys.length;
}
public get headerState(): number {
if (this.selectedAPIKeysCount > 0) {
return HeaderState.ON_SELECT;
}
return HeaderState.DEFAULT;
}
}
</script>
<style scoped lang="scss">

View File

@ -37,35 +37,37 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import Button from '@/components/common/Button.vue';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component({
components: {
HeaderlessInput,
Button,
},
})
export default class ApiKeysCopyPopup extends Vue {
@Prop({default: false})
private readonly isPopupShown: boolean;
@Prop({default: ''})
private readonly apiKeySecret: string;
import Button from '@/components/common/Button.vue';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
private isCopiedButtonShown: boolean = false;
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
public onCloseClick(): void {
this.isCopiedButtonShown = false;
this.$emit('closePopup');
}
@Component({
components: {
HeaderlessInput,
Button,
},
})
export default class ApiKeysCopyPopup extends Vue {
@Prop({default: false})
private readonly isPopupShown: boolean;
@Prop({default: ''})
private readonly apiKeySecret: string;
public onCopyClick(): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Key saved to clipboard');
this.isCopiedButtonShown = true;
}
private isCopiedButtonShown: boolean = false;
public onCloseClick(): void {
this.isCopiedButtonShown = false;
this.$emit('closePopup');
}
public onCopyClick(): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Key saved to clipboard');
this.isCopiedButtonShown = true;
}
}
</script>
<style scoped lang="scss">

View File

@ -21,71 +21,73 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
import { API_KEYS_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { ApiKey } from '@/types/apiKeys';
import { Component, Prop, Vue } from 'vue-property-decorator';
const CREATE = API_KEYS_ACTIONS.CREATE;
import Button from '@/components/common/Button.vue';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
@Component({
components: {
HeaderlessInput,
Button,
},
})
export default class ApiKeysCreationPopup extends Vue {
@Prop({default: false})
private readonly isPopupShown: boolean;
import { ApiKey } from '@/types/apiKeys';
import { API_KEYS_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
private name: string = '';
private errorMessage: string = '';
private isLoading: boolean = false;
private key: string = '';
const CREATE = API_KEYS_ACTIONS.CREATE;
public onChangeName(value: string): void {
this.name = value.trim();
this.errorMessage = '';
@Component({
components: {
HeaderlessInput,
Button,
},
})
export default class ApiKeysCreationPopup extends Vue {
@Prop({default: false})
private readonly isPopupShown: boolean;
private name: string = '';
private errorMessage: string = '';
private isLoading: boolean = false;
private key: string = '';
public onChangeName(value: string): void {
this.name = value.trim();
this.errorMessage = '';
}
public onCloseClick(): void {
this.onChangeName('');
this.$emit('closePopup');
}
public async onNextClick(): Promise<void> {
if (this.isLoading) {
return;
}
public onCloseClick(): void {
this.onChangeName('');
this.$emit('closePopup');
if (!this.name) {
this.errorMessage = 'API Key name can`t be empty';
return;
}
public async onNextClick(): Promise<void> {
if (this.isLoading) {
return;
}
this.isLoading = true;
if (!this.name) {
this.errorMessage = 'API Key name can`t be empty';
let createdApiKey: ApiKey;
return;
}
this.isLoading = true;
let createdApiKey: ApiKey;
try {
createdApiKey = await this.$store.dispatch(CREATE, this.name);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
this.isLoading = false;
return;
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Successfully created new api key');
this.key = createdApiKey.secret;
try {
createdApiKey = await this.$store.dispatch(CREATE, this.name);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
this.isLoading = false;
this.$emit('closePopup');
this.$emit('showCopyPopup', this.key);
return;
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Successfully created new api key');
this.key = createdApiKey.secret;
this.isLoading = false;
this.$emit('closePopup');
this.$emit('showCopyPopup', this.key);
}
}
</script>
<style scoped lang="scss">

View File

@ -24,14 +24,15 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { ApiKey } from '@/types/apiKeys';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class ApiKeysItem extends Vue {
@Prop({default: () => new ApiKey('', '', '', '')})
private readonly itemData: ApiKey;
}
import { ApiKey } from '@/types/apiKeys';
@Component
export default class ApiKeysItem extends Vue {
@Prop({default: () => new ApiKey('', '', '', '')})
private readonly itemData: ApiKey;
}
</script>
<style scoped lang="scss">

View File

@ -21,15 +21,16 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import { Component, Vue } from 'vue-property-decorator';
@Component
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
export default class SortApiKeysHeader extends Vue {
public arrowUp: string = EMPTY_STATE_IMAGES.ARROW_UP;
public arrowDown: string = EMPTY_STATE_IMAGES.ARROW_DOWN;
}
@Component
export default class SortApiKeysHeader extends Vue {
public arrowUp: string = EMPTY_STATE_IMAGES.ARROW_UP;
public arrowDown: string = EMPTY_STATE_IMAGES.ARROW_DOWN;
}
</script>
<style scoped lang="scss">

View File

@ -24,84 +24,86 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import EmptyState from '@/components/common/EmptyStateArea.vue';
import BucketItem from '@/components/buckets/BucketItem.vue';
import SortingHeader from '@/components/buckets/SortingHeader.vue';
import NoBucketArea from '@/components/buckets/NoBucketsArea.vue';
import HeaderComponent from '@/components/common/HeaderComponent.vue';
import Pagination from '@/components/common/Pagination.vue';
import List from '@/components/common/List.vue';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { Bucket } from '@/types/buckets';
import { Component, Vue } from 'vue-property-decorator';
const {
FETCH,
SET_SEARCH,
} = BUCKET_ACTIONS;
import BucketItem from '@/components/buckets/BucketItem.vue';
import NoBucketArea from '@/components/buckets/NoBucketsArea.vue';
import SortingHeader from '@/components/buckets/SortingHeader.vue';
import EmptyState from '@/components/common/EmptyStateArea.vue';
import HeaderComponent from '@/components/common/HeaderComponent.vue';
import List from '@/components/common/List.vue';
import Pagination from '@/components/common/Pagination.vue';
@Component({
components: {
EmptyState,
SortingHeader,
BucketItem,
NoBucketArea,
HeaderComponent,
Pagination,
List
}
})
export default class BucketArea extends Vue {
public emptyImage: string = EMPTY_STATE_IMAGES.API_KEY;
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { Bucket } from '@/types/buckets';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
public mounted(): void {
this.$store.dispatch(FETCH, 1);
}
const {
FETCH,
SET_SEARCH,
} = BUCKET_ACTIONS;
public doNothing(): void {
// this method is used to mock prop function of common List
}
@Component({
components: {
EmptyState,
SortingHeader,
BucketItem,
NoBucketArea,
HeaderComponent,
Pagination,
List
}
})
export default class BucketArea extends Vue {
public emptyImage: string = EMPTY_STATE_IMAGES.API_KEY;
public get totalPageCount(): number {
return this.$store.getters.page.pageCount;
}
public mounted(): void {
this.$store.dispatch(FETCH, 1);
}
public get totalCount(): number {
return this.$store.getters.page.totalCount;
}
public doNothing(): void {
// this method is used to mock prop function of common List
}
public get itemComponent() {
return BucketItem;
}
public get totalPageCount(): number {
return this.$store.getters.page.pageCount;
}
public get buckets(): Bucket[] {
return this.$store.getters.page.buckets;
}
public get totalCount(): number {
return this.$store.getters.page.totalCount;
}
public get search(): string {
return this.$store.getters.cursor.search;
}
public get itemComponent() {
return BucketItem;
}
public async fetch(searchQuery: string): Promise<void> {
await this.$store.dispatch(SET_SEARCH, searchQuery);
public get buckets(): Bucket[] {
return this.$store.getters.page.buckets;
}
try {
await this.$store.dispatch(FETCH, 1);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch buckets: ${error.message}`);
}
}
public get search(): string {
return this.$store.getters.cursor.search;
}
public async onPageClick(page: number): Promise<void> {
try {
await this.$store.dispatch(FETCH, page);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch buckets: ${error.message}`);
}
public async fetch(searchQuery: string): Promise<void> {
await this.$store.dispatch(SET_SEARCH, searchQuery);
try {
await this.$store.dispatch(FETCH, 1);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch buckets: ${error.message}`);
}
}
public async onPageClick(page: number): Promise<void> {
try {
await this.$store.dispatch(FETCH, page);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch buckets: ${error.message}`);
}
}
}
</script>
<style scoped lang="scss">

View File

@ -11,27 +11,28 @@
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { Bucket } from '@/types/buckets';
import { Component, Prop, Vue } from 'vue-property-decorator';
// TODO: should it be functional?
@Component
export default class BucketItem extends Vue {
@Prop()
private readonly itemData: Bucket;
public get storage(): string {
return this.itemData.storage.toFixed(4);
}
public get egress(): string {
return this.itemData.egress.toFixed(4);
}
public get objectCount(): string {
return this.itemData.objectCount.toString();
}
import { Bucket } from '@/types/buckets';
// TODO: should it be functional?
@Component
export default class BucketItem extends Vue {
@Prop()
private readonly itemData: Bucket;
public get storage(): string {
return this.itemData.storage.toFixed(4);
}
public get egress(): string {
return this.itemData.egress.toFixed(4);
}
public get objectCount(): string {
return this.itemData.objectCount.toString();
}
}
</script>
<style scoped lang="scss">

View File

@ -49,15 +49,16 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Button,
},
})
export default class NoBucketArea extends Vue {}
import Button from '@/components/common/Button.vue';
@Component({
components: {
Button,
},
})
export default class NoBucketArea extends Vue {}
</script>

View File

@ -12,40 +12,40 @@
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { Component, Prop, Vue } from 'vue-property-decorator';
// Custom button component with label
@Component
export default class Button extends Vue {
@Prop({default: 'Default'})
private readonly label: string;
@Prop({default: 'inherit'})
private readonly width: string;
@Prop({default: 'inherit'})
private readonly height: string;
@Prop({default: false})
private readonly isWhite: boolean;
@Prop({default: false})
private readonly isDeletion: boolean;
@Prop({default: false})
private isDisabled: boolean;
@Prop({default: () => { return; }})
private readonly onPress: Function;
public get style(): Object {
return { width: this.width, height: this.height };
}
// Custom button component with label
@Component
export default class Button extends Vue {
@Prop({default: 'Default'})
private readonly label: string;
@Prop({default: 'inherit'})
private readonly width: string;
@Prop({default: 'inherit'})
private readonly height: string;
@Prop({default: false})
private readonly isWhite: boolean;
@Prop({default: false})
private readonly isDeletion: boolean;
@Prop({default: false})
private isDisabled: boolean;
@Prop({default: () => { return; }})
private readonly onPress: Function;
public get containerClassName(): string {
if (this.isDisabled) return 'container disabled';
if (this.isWhite) return 'container white';
if (this.isDeletion) return 'container red';
return 'container';
}
public get style(): Object {
return { width: this.width, height: this.height };
}
public get containerClassName(): string {
if (this.isDisabled) return 'container disabled';
if (this.isWhite) return 'container white';
if (this.isDeletion) return 'container red';
return 'container';
}
}
</script>
<style scoped lang="scss">

View File

@ -19,33 +19,34 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component({
components: {
Button,
}
})
export default class EmptyStateProjectArea extends Vue {
@Prop({default: ''})
private readonly mainTitle: string;
@Prop({default: ''})
private readonly additionalText: string;
@Prop({default: ''})
private readonly imageSource: string;
@Prop({default: false})
private readonly isButtonShown: boolean;
@Prop()
private readonly onButtonClick: Function;
@Prop({default: 'Default'})
private readonly buttonLabel: string;
import Button from '@/components/common/Button.vue';
@Component({
components: {
Button,
}
})
export default class EmptyStateProjectArea extends Vue {
@Prop({default: ''})
private readonly mainTitle: string;
@Prop({default: ''})
private readonly additionalText: string;
@Prop({default: ''})
private readonly imageSource: string;
@Prop({default: false})
private readonly isButtonShown: boolean;
@Prop()
private readonly onButtonClick: Function;
@Prop({default: 'Default'})
private readonly buttonLabel: string;
}
</script>
<style scoped lang="scss">

View File

@ -11,33 +11,34 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import SearchComponent from '@/components/common/SearchComponent.vue';
import { Component, Prop, Vue } from 'vue-property-decorator';
declare type searchCallback = (search: string) => Promise<void>;
declare interface ClearSearch {
clearSearch: () => void;
import SearchComponent from '@/components/common/SearchComponent.vue';
declare type searchCallback = (search: string) => Promise<void>;
declare interface ClearSearch {
clearSearch: () => void;
}
@Component({
components: {
SearchComponent,
}
})
export default class HeaderComponent extends Vue {
@Prop({default: ''})
private readonly placeHolder: string;
@Prop({default: () => ''})
private readonly search: searchCallback;
@Component({
components: {
SearchComponent,
}
})
export default class HeaderComponent extends Vue {
@Prop({default: ''})
private readonly placeHolder: string;
@Prop({default: () => { return ''; }})
private readonly search: searchCallback;
public $refs!: {
search: SearchComponent & ClearSearch;
};
public $refs!: {
search: SearchComponent & ClearSearch;
};
public clearSearch() {
this.$refs.search.clearSearch();
}
public clearSearch() {
this.$refs.search.clearSearch();
}
}
</script>
<style scoped lang="scss">

View File

@ -38,27 +38,28 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import HeaderlessInput from './HeaderlessInput.vue';
import { Component, Prop, Vue } from 'vue-property-decorator';
// Custom input component with labeled header
@Component
export default class HeaderedInput extends HeaderlessInput {
@Prop({default: ''})
private readonly initValue: string;
@Prop({default: ''})
private readonly additionalLabel: string;
@Prop({default: false})
private readonly isOptional: boolean;
@Prop({default: false})
private readonly isMultiline: boolean;
import HeaderlessInput from './HeaderlessInput.vue';
// Custom input component with labeled header
@Component
export default class HeaderedInput extends HeaderlessInput {
@Prop({default: ''})
private readonly initValue: string;
@Prop({default: ''})
private readonly additionalLabel: string;
@Prop({default: false})
private readonly isOptional: boolean;
@Prop({default: false})
private readonly isMultiline: boolean;
public constructor() {
super();
public constructor() {
super();
this.value = this.initValue;
}
this.value = this.initValue;
}
}
</script>
<style scoped lang="scss">

View File

@ -92,7 +92,7 @@
private changeVision(): void {
this.isPasswordShown = !this.isPasswordShown;
if (this.isPasswordShown) {
this.type = this.type == this.passwordType ? this.textType : this.passwordType;
this.type = this.type === this.passwordType ? this.textType : this.passwordType;
}
}

View File

@ -12,16 +12,17 @@
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { Page } from '@/types/pagination';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class PagesBlock extends Vue {
@Prop({default: () => []})
public readonly pages: Page[];
@Prop({default: () => false})
public readonly checkSelected: CheckSelected;
}
import { Page } from '@/types/pagination';
@Component
export default class PagesBlock extends Vue {
@Prop({default: () => []})
public readonly pages: Page[];
@Prop({default: () => false})
public readonly checkSelected: CheckSelected;
}
</script>
<style scoped lang="scss">

View File

@ -18,206 +18,208 @@
</template>
<script lang="ts">
import { Component, Vue, Prop, Watch } from 'vue-property-decorator';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import PagesBlock from '@/components/common/PagesBlock.vue';
import { Page } from '@/types/pagination';
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
@Component({
components: {
PagesBlock,
}
})
export default class Pagination extends Vue {
// TODO: use svg loader
public readonly arrowLeft: string = EMPTY_STATE_IMAGES.ARROW_LEFT;
public readonly arrowRight: string = EMPTY_STATE_IMAGES.ARROW_RIGHT;
private readonly MAX_PAGES_PER_BLOCK: number = 3;
private readonly MAX_PAGES_OFF_BLOCKS: number = 6;
private currentPageNumber: number = 1;
public isLoading = false;
public pagesArray: Page[] = [];
public firstBlockPages: Page[] = [];
public middleBlockPages: Page[] = [];
public lastBlockPages: Page[] = [];
import PagesBlock from '@/components/common/PagesBlock.vue';
@Prop({default: 0})
private readonly totalPageCount: number;
@Prop({default: () => { return new Promise(() => false); }})
private readonly onPageClickCallback: OnPageClickCallback;
import { Page } from '@/types/pagination';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
public mounted() {
this.populatePagesArray();
@Component({
components: {
PagesBlock,
}
})
export default class Pagination extends Vue {
// TODO: use svg loader
public readonly arrowLeft: string = EMPTY_STATE_IMAGES.ARROW_LEFT;
public readonly arrowRight: string = EMPTY_STATE_IMAGES.ARROW_RIGHT;
private readonly MAX_PAGES_PER_BLOCK: number = 3;
private readonly MAX_PAGES_OFF_BLOCKS: number = 6;
private currentPageNumber: number = 1;
public isLoading = false;
public pagesArray: Page[] = [];
public firstBlockPages: Page[] = [];
public middleBlockPages: Page[] = [];
public lastBlockPages: Page[] = [];
@Prop({default: 0})
private readonly totalPageCount: number;
@Prop({default: () => new Promise(() => false)})
private readonly onPageClickCallback: OnPageClickCallback;
public mounted() {
this.populatePagesArray();
}
public get isFirstPage(): boolean {
return this.currentPageNumber === 1;
}
public get isLastPage(): boolean {
return this.currentPageNumber === this.totalPageCount;
}
public get isFirstDotsShown(): boolean {
return this.middleBlockPages.length <= this.MAX_PAGES_PER_BLOCK
&& this.pagesArray.length > this.MAX_PAGES_OFF_BLOCKS;
}
public get isSecondDotsShown(): boolean {
return !!this.middleBlockPages.length;
}
public isSelected(page: number): boolean {
return page === this.currentPageNumber;
}
@Watch('totalPageCount')
public onPageCountChange(val: number, oldVal: number) {
this.resetPageIndex();
}
public async onPageClick(page: number): Promise<void> {
if (this.isLoading) {
return;
}
public get isFirstPage(): boolean {
return this.currentPageNumber === 1;
this.isLoading = true;
await this.onPageClickCallback(page);
this.setCurrentPage(page);
this.reorganizePageBlocks();
this.isLoading = false;
}
public async nextPage(): Promise<void> {
if (this.isLastPage || this.isLoading) {
return;
}
public get isLastPage(): boolean {
return this.currentPageNumber === this.totalPageCount;
this.isLoading = true;
await this.onPageClickCallback(this.currentPageNumber + 1);
this.incrementCurrentPage();
this.reorganizePageBlocks();
this.isLoading = false;
}
public async prevPage(): Promise<void> {
if (this.isFirstPage || this.isLoading) {
return;
}
public get isFirstDotsShown(): boolean {
return this.middleBlockPages.length <= this.MAX_PAGES_PER_BLOCK
&& this.pagesArray.length > this.MAX_PAGES_OFF_BLOCKS;
this.isLoading = true;
await this.onPageClickCallback(this.currentPageNumber - 1);
this.decrementCurrentPage();
this.reorganizePageBlocks();
this.isLoading = false;
}
public resetPageIndex(): void {
this.pagesArray = [];
this.firstBlockPages = [];
this.setCurrentPage(1);
this.populatePagesArray();
}
private populatePagesArray(): void {
if (!this.totalPageCount) {
return;
}
public get isSecondDotsShown(): boolean {
return !!this.middleBlockPages.length;
if (this.$route.query.pageNumber) {
const pageNumber = parseInt(this.$route.query.pageNumber as string);
this.setCurrentPage(pageNumber);
// Here we need to set short timeout to let router to set up after page
// hard reload before we can replace query with current page number
setTimeout(this.updateRouterPathWithPageNumber, 1);
}
public isSelected(page: number): boolean {
return page === this.currentPageNumber;
for (let i = 1; i <= this.totalPageCount; i++) {
this.pagesArray.push(new Page(i, this.onPageClick));
}
@Watch('totalPageCount')
public onPageCountChange(val: number, oldVal: number) {
this.resetPageIndex();
if (this.isPagesTotalOffBlocks()) {
this.firstBlockPages = this.pagesArray.slice();
return;
}
public async onPageClick(page: number): Promise<void> {
if (this.isLoading) {
return;
}
this.reorganizePageBlocks();
}
this.isLoading = true;
await this.onPageClickCallback(page);
this.setCurrentPage(page);
this.reorganizePageBlocks();
this.isLoading = false;
private reorganizePageBlocks(): void {
if (this.isPagesTotalOffBlocks()) {
return;
}
public async nextPage(): Promise<void> {
if (this.isLastPage || this.isLoading) {
return;
}
if (this.isCurrentInFirstBlock()) {
this.setBlocksIfCurrentInFirstBlock();
this.isLoading = true;
await this.onPageClickCallback(this.currentPageNumber + 1);
this.incrementCurrentPage();
this.reorganizePageBlocks();
this.isLoading = false;
return;
}
public async prevPage(): Promise<void> {
if (this.isFirstPage || this.isLoading) {
return;
}
if (!this.isCurrentInFirstBlock() && !this.isCurrentInLastBlock()) {
this.setBlocksIfCurrentInMiddleBlock();
this.isLoading = true;
await this.onPageClickCallback(this.currentPageNumber - 1);
this.decrementCurrentPage();
this.reorganizePageBlocks();
this.isLoading = false;
return;
}
public resetPageIndex(): void {
this.pagesArray = [];
this.firstBlockPages = [];
this.setCurrentPage(1);
this.populatePagesArray();
}
private populatePagesArray(): void {
if (!this.totalPageCount) {
return;
}
if (this.$route.query.pageNumber) {
const pageNumber = parseInt(this.$route.query.pageNumber as string);
this.setCurrentPage(pageNumber);
// Here we need to set short timeout to let router to set up after page
// hard reload before we can replace query with current page number
setTimeout(this.updateRouterPathWithPageNumber, 1);
}
for (let i = 1; i <= this.totalPageCount; i++) {
this.pagesArray.push(new Page(i, this.onPageClick));
}
if (this.isPagesTotalOffBlocks()) {
this.firstBlockPages = this.pagesArray.slice();
return;
}
this.reorganizePageBlocks();
}
private reorganizePageBlocks(): void {
if (this.isPagesTotalOffBlocks()) {
return;
}
if (this.isCurrentInFirstBlock()) {
this.setBlocksIfCurrentInFirstBlock();
return;
}
if (!this.isCurrentInFirstBlock() && !this.isCurrentInLastBlock()) {
this.setBlocksIfCurrentInMiddleBlock();
return;
}
if (this.isCurrentInLastBlock()) {
this.setBlocksIfCurrentInLastBlock();
}
}
private setBlocksIfCurrentInFirstBlock(): void {
this.firstBlockPages = this.pagesArray.slice(0, 3);
this.middleBlockPages = [];
this.lastBlockPages = this.pagesArray.slice(-1);
}
private setBlocksIfCurrentInMiddleBlock(): void {
this.firstBlockPages = this.pagesArray.slice(0, 1);
this.middleBlockPages = this.pagesArray.slice(this.currentPageNumber - 2, this.currentPageNumber + 1);
this.lastBlockPages = this.pagesArray.slice(-1);
}
private setBlocksIfCurrentInLastBlock(): void {
this.firstBlockPages = this.pagesArray.slice(0, 1);
this.middleBlockPages = [];
this.lastBlockPages = this.pagesArray.slice(-3);
}
private isCurrentInFirstBlock(): boolean {
return this.currentPageNumber < this.MAX_PAGES_PER_BLOCK;
}
private isCurrentInLastBlock(): boolean {
return this.totalPageCount - this.currentPageNumber < this.MAX_PAGES_PER_BLOCK - 1;
}
private isPagesTotalOffBlocks(): boolean {
return this.totalPageCount <= this.MAX_PAGES_OFF_BLOCKS;
}
private incrementCurrentPage(): void {
this.currentPageNumber++;
this.updateRouterPathWithPageNumber();
}
private decrementCurrentPage(): void {
this.currentPageNumber--;
this.updateRouterPathWithPageNumber();
}
private setCurrentPage(pageNumber: number): void {
this.currentPageNumber = pageNumber;
this.updateRouterPathWithPageNumber();
}
private updateRouterPathWithPageNumber() {
this.$router.replace({ query: { pageNumber: this.currentPageNumber.toString() } });
if (this.isCurrentInLastBlock()) {
this.setBlocksIfCurrentInLastBlock();
}
}
private setBlocksIfCurrentInFirstBlock(): void {
this.firstBlockPages = this.pagesArray.slice(0, 3);
this.middleBlockPages = [];
this.lastBlockPages = this.pagesArray.slice(-1);
}
private setBlocksIfCurrentInMiddleBlock(): void {
this.firstBlockPages = this.pagesArray.slice(0, 1);
this.middleBlockPages = this.pagesArray.slice(this.currentPageNumber - 2, this.currentPageNumber + 1);
this.lastBlockPages = this.pagesArray.slice(-1);
}
private setBlocksIfCurrentInLastBlock(): void {
this.firstBlockPages = this.pagesArray.slice(0, 1);
this.middleBlockPages = [];
this.lastBlockPages = this.pagesArray.slice(-3);
}
private isCurrentInFirstBlock(): boolean {
return this.currentPageNumber < this.MAX_PAGES_PER_BLOCK;
}
private isCurrentInLastBlock(): boolean {
return this.totalPageCount - this.currentPageNumber < this.MAX_PAGES_PER_BLOCK - 1;
}
private isPagesTotalOffBlocks(): boolean {
return this.totalPageCount <= this.MAX_PAGES_OFF_BLOCKS;
}
private incrementCurrentPage(): void {
this.currentPageNumber++;
this.updateRouterPathWithPageNumber();
}
private decrementCurrentPage(): void {
this.currentPageNumber--;
this.updateRouterPathWithPageNumber();
}
private setCurrentPage(pageNumber: number): void {
this.currentPageNumber = pageNumber;
this.updateRouterPathWithPageNumber();
}
private updateRouterPathWithPageNumber() {
this.$router.replace({ query: { pageNumber: this.currentPageNumber.toString() } });
}
}
</script>
<style scoped lang="scss">

View File

@ -4,73 +4,75 @@
<template src="./registrationSuccessPopup.html"></template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { RouteConfig } from '@/router';
import { AuthApi } from '@/api/auth';
import { getUserId } from '@/utils/consoleLocalStorage';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Button,
},
})
export default class RegistrationSuccessPopup extends Vue {
private isResendEmailButtonDisabled: boolean = true;
private timeToEnableResendEmailButton: string = '00:30';
private intervalID: any = null;
import Button from '@/components/common/Button.vue';
private readonly auth: AuthApi = new AuthApi();
import { AuthApi } from '@/api/auth';
import { RouteConfig } from '@/router';
import { getUserId } from '@/utils/consoleLocalStorage';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
public beforeDestroy(): void {
if (this.intervalID) {
clearInterval(this.intervalID);
}
}
@Component({
components: {
Button,
},
})
export default class RegistrationSuccessPopup extends Vue {
private isResendEmailButtonDisabled: boolean = true;
private timeToEnableResendEmailButton: string = '00:30';
private intervalID: any = null;
public async onResendEmailButtonClick(): Promise<void> {
this.isResendEmailButtonDisabled = true;
private readonly auth: AuthApi = new AuthApi();
const userId = getUserId();
if (!userId) {
return;
}
try {
await this.auth.resendEmail(userId);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'could not send email ');
}
this.startResendEmailCountdown();
}
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
this.$router.push(RouteConfig.Login.path);
}
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isSuccessfulRegistrationPopupShown;
}
private startResendEmailCountdown(): void {
let countdown = 30;
this.intervalID = setInterval(() => {
countdown--;
let secondsLeft = countdown > 9 ? countdown : `0${countdown}`;
this.timeToEnableResendEmailButton = `00:${secondsLeft}`;
if (countdown <= 0) {
clearInterval(this.intervalID);
this.isResendEmailButtonDisabled = false;
}
}, 1000);
public beforeDestroy(): void {
if (this.intervalID) {
clearInterval(this.intervalID);
}
}
public async onResendEmailButtonClick(): Promise<void> {
this.isResendEmailButtonDisabled = true;
const userId = getUserId();
if (!userId) {
return;
}
try {
await this.auth.resendEmail(userId);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'could not send email ');
}
this.startResendEmailCountdown();
}
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
this.$router.push(RouteConfig.Login.path);
}
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isSuccessfulRegistrationPopupShown;
}
private startResendEmailCountdown(): void {
let countdown = 30;
this.intervalID = setInterval(() => {
countdown--;
const secondsLeft = countdown > 9 ? countdown : `0${countdown}`;
this.timeToEnableResendEmailButton = `00:${secondsLeft}`;
if (countdown <= 0) {
clearInterval(this.intervalID);
this.isResendEmailButtonDisabled = false;
}
}, 1000);
}
}
</script>
<style scoped lang="scss">

View File

@ -15,59 +15,59 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { Component, Prop, Vue } from 'vue-property-decorator';
declare type searchCallback = (search: string) => Promise<void>;
declare interface SearchStyle {
width: string;
declare type searchCallback = (search: string) => Promise<void>;
declare interface SearchStyle {
width: string;
}
@Component
export default class SearchComponent extends Vue {
@Prop({default: ''})
private readonly placeHolder: string;
@Prop({default: () => ''})
private readonly search: searchCallback;
private inputWidth: string = '56px';
private searchQuery: string = '';
public $refs!: {
input: HTMLElement;
};
public get style(): SearchStyle {
return { width: this.inputWidth };
}
@Component
export default class SearchComponent extends Vue {
@Prop({default: ''})
private readonly placeHolder: string;
@Prop({default: () => { return ''; }})
private readonly search: searchCallback;
public get searchString(): string {
return this.searchQuery;
}
private inputWidth: string = '56px';
private searchQuery: string = '';
public onMouseEnter(): void {
this.inputWidth = '602px';
public $refs!: {
input: HTMLElement;
};
this.$refs.input.focus();
}
public get style(): SearchStyle {
return { width: this.inputWidth };
}
public get searchString(): string {
return this.searchQuery;
}
public onMouseEnter(): void {
this.inputWidth = '602px';
this.$refs.input.focus();
}
public onMouseLeave(): void {
if (!this.searchString) {
this.inputWidth = '56px';
}
this.$refs.input.blur();
}
public clearSearch() {
this.searchQuery = '';
this.processSearchQuery();
public onMouseLeave(): void {
if (!this.searchString) {
this.inputWidth = '56px';
}
private async processSearchQuery() {
await this.search(this.searchString);
}
this.$refs.input.blur();
}
public clearSearch() {
this.searchQuery = '';
this.processSearchQuery();
this.inputWidth = '56px';
}
private async processSearchQuery() {
await this.search(this.searchString);
}
}
</script>
<style scoped lang="scss">

View File

@ -15,24 +15,25 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { SortingDirectionEnum } from '@/types/sortingArrows';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class VerticalArrows extends Vue {
@Prop({default: false})
private isActive: boolean;
@Prop({default: SortingDirectionEnum.BOTTOM})
private direction: SortingDirectionEnum;
import { SortingDirectionEnum } from '@/types/sortingArrows';
public get isTop(): boolean {
return this.direction === SortingDirectionEnum.TOP;
}
@Component
export default class VerticalArrows extends Vue {
@Prop({default: false})
private isActive: boolean;
@Prop({default: SortingDirectionEnum.BOTTOM})
private direction: SortingDirectionEnum;
public get isBottom(): boolean {
return this.direction === SortingDirectionEnum.BOTTOM;
}
public get isTop(): boolean {
return this.direction === SortingDirectionEnum.TOP;
}
public get isBottom(): boolean {
return this.direction === SortingDirectionEnum.BOTTOM;
}
}
</script>
<style scoped lang="scss">

View File

@ -9,33 +9,34 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import TestListItem from '@/components/common/test/TestListItem.vue';
import List from '@/components/common/List.vue';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component({
components: {
List,
import List from '@/components/common/List.vue';
import TestListItem from '@/components/common/test/TestListItem.vue';
@Component({
components: {
List,
}
})
export default class TestList extends Vue {
@Prop({
default: () => {
console.error('onItemClick is not initialized');
}
})
export default class TestList extends Vue {
@Prop({
default: () => {
console.error('onItemClick is not initialized');
}
})
private readonly onItemClick: (item: any) => Promise<void>;
private readonly onItemClick: (item: any) => Promise<void>;
private items: string[] = ['1', '2', '3'];
private items: string[] = ['1', '2', '3'];
public get getItemComponent() {
return TestListItem;
}
public get dataSetItems(): string[] {
return this.items;
}
public get getItemComponent() {
return TestListItem;
}
public get dataSetItems(): string[] {
return this.items;
}
}
</script>
<style scoped lang="scss">

View File

@ -10,16 +10,18 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import EmptyState from '@/components/common/EmptyStateArea.vue';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
EmptyState,
}
})
export default class DashboardArea extends Vue {
public emptyImage: string = EMPTY_STATE_IMAGES.PROJECT;
import EmptyState from '@/components/common/EmptyStateArea.vue';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
@Component({
components: {
EmptyState,
}
})
export default class DashboardArea extends Vue {
public emptyImage: string = EMPTY_STATE_IMAGES.PROJECT;
}
</script>

View File

@ -21,32 +21,34 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import AccountDropdown from './AccountDropdown.vue';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
AccountDropdown
}
})
export default class AccountButton extends Vue {
public toggleSelection(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACCOUNT);
}
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
public get avatarLetter(): string {
return this.$store.getters.userName.slice(0, 1).toUpperCase();
}
import AccountDropdown from './AccountDropdown.vue';
public get userName(): string {
return this.$store.getters.userName;
}
public get isDropdownShown(): boolean {
return this.$store.state.appStateModule.appState.isAccountDropdownShown;
}
@Component({
components: {
AccountDropdown
}
})
export default class AccountButton extends Vue {
public toggleSelection(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACCOUNT);
}
public get avatarLetter(): string {
return this.$store.getters.userName.slice(0, 1).toUpperCase();
}
public get userName(): string {
return this.$store.getters.userName;
}
public get isDropdownShown(): boolean {
return this.$store.state.appStateModule.appState.isAccountDropdownShown;
}
}
</script>
<style scoped lang="scss">

View File

@ -27,42 +27,43 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { AuthToken } from '@/utils/authToken';
import { RouteConfig } from '@/router';
import {
APP_STATE_ACTIONS,
PM_ACTIONS,
API_KEYS_ACTIONS,
NOTIFICATION_ACTIONS,
} from '@/utils/constants/actionNames';
import { USER_ACTIONS } from '@/store/modules/users';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class AccountDropdown extends Vue {
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACCOUNT);
}
import { RouteConfig } from '@/router';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { USER_ACTIONS } from '@/store/modules/users';
import { AuthToken } from '@/utils/authToken';
import {
API_KEYS_ACTIONS,
APP_STATE_ACTIONS,
NOTIFICATION_ACTIONS,
PM_ACTIONS,
} from '@/utils/constants/actionNames';
public onAccountSettingsClick(): void {
this.$router.push(RouteConfig.Account.with(RouteConfig.Profile).path);
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACCOUNT);
}
public onLogoutClick(): void {
AuthToken.remove();
this.$router.push(RouteConfig.Login.path);
this.$store.dispatch(PM_ACTIONS.CLEAR);
this.$store.dispatch(PROJECTS_ACTIONS.CLEAR);
this.$store.dispatch(USER_ACTIONS.CLEAR);
this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
this.$store.dispatch(NOTIFICATION_ACTIONS.CLEAR);
this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
}
@Component
export default class AccountDropdown extends Vue {
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACCOUNT);
}
public onAccountSettingsClick(): void {
this.$router.push(RouteConfig.Account.with(RouteConfig.Profile).path);
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACCOUNT);
}
public onLogoutClick(): void {
AuthToken.remove();
this.$router.push(RouteConfig.Login.path);
this.$store.dispatch(PM_ACTIONS.CLEAR);
this.$store.dispatch(PROJECTS_ACTIONS.CLEAR);
this.$store.dispatch(USER_ACTIONS.CLEAR);
this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
this.$store.dispatch(NOTIFICATION_ACTIONS.CLEAR);
this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
}
}
</script>
<style scoped lang="scss">

View File

@ -15,21 +15,23 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ProjectCreationSuccessPopup from '@/components/project/ProjectCreationSuccessPopup.vue';
import ProjectSelectionArea from '@/components/header/projectSelection/ProjectSelectionArea.vue';
import NewProjectArea from '@/components/header/NewProjectArea.vue';
import AccountButton from './AccountButton.vue';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
ProjectCreationSuccessPopup,
ProjectSelectionArea,
NewProjectArea,
AccountButton,
},
})
export default class DashboardHeader extends Vue {}
import NewProjectArea from '@/components/header/NewProjectArea.vue';
import ProjectSelectionArea from '@/components/header/projectSelection/ProjectSelectionArea.vue';
import ProjectCreationSuccessPopup from '@/components/project/ProjectCreationSuccessPopup.vue';
import AccountButton from './AccountButton.vue';
@Component({
components: {
ProjectCreationSuccessPopup,
ProjectSelectionArea,
NewProjectArea,
AccountButton,
},
})
export default class DashboardHeader extends Vue {}
</script>
<style scoped lang="scss">

View File

@ -11,29 +11,31 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import NewProjectPopup from '@/components/project/NewProjectPopup.vue';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { Component, Vue } from 'vue-property-decorator';
// Button and popup for adding new Project
@Component({
components: {
NewProjectPopup
}
})
export default class NewProjectArea extends Vue {
public toggleSelection(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
}
import NewProjectPopup from '@/components/project/NewProjectPopup.vue';
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isNewProjectPopupShown;
}
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
public get hasProjects(): boolean {
return this.$store.state.projectsModule.projects.length;
}
// Button and popup for adding new Project
@Component({
components: {
NewProjectPopup
}
})
export default class NewProjectArea extends Vue {
public toggleSelection(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
}
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isNewProjectPopupShown;
}
public get hasProjects(): boolean {
return this.$store.state.projectsModule.projects.length;
}
}
</script>
<style scoped lang="scss">

View File

@ -16,42 +16,44 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ProjectSelectionDropdown from './ProjectSelectionDropdown.vue';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { Project } from '@/types/projects';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
ProjectSelectionDropdown,
}
})
export default class ProjectSelectionArea extends Vue {
public async toggleSelection(): Promise<void> {
try {
await this.$store.dispatch(PROJECTS_ACTIONS.FETCH);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { Project } from '@/types/projects';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
await this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_PROJECTS);
}
import ProjectSelectionDropdown from './ProjectSelectionDropdown.vue';
public get name(): string {
let selectedProject: Project = this.$store.state.projectsModule.selectedProject;
return selectedProject.id ? selectedProject.name : 'Choose project';
}
public get isDropdownShown(): boolean {
return this.$store.state.appStateModule.appState.isProjectsDropdownShown;
}
public get hasProjects(): boolean {
return !!this.$store.state.projectsModule.projects.length;
}
@Component({
components: {
ProjectSelectionDropdown,
}
})
export default class ProjectSelectionArea extends Vue {
public async toggleSelection(): Promise<void> {
try {
await this.$store.dispatch(PROJECTS_ACTIONS.FETCH);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
await this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_PROJECTS);
}
public get name(): string {
const selectedProject: Project = this.$store.state.projectsModule.selectedProject;
return selectedProject.id ? selectedProject.name : 'Choose project';
}
public get isDropdownShown(): boolean {
return this.$store.state.appStateModule.appState.isProjectsDropdownShown;
}
public get hasProjects(): boolean {
return !!this.$store.state.projectsModule.projects.length;
}
}
</script>
<style scoped lang="scss">

View File

@ -19,58 +19,58 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Component, Vue } from 'vue-property-decorator';
import {
APP_STATE_ACTIONS,
NOTIFICATION_ACTIONS,
PM_ACTIONS,
API_KEYS_ACTIONS,
PROJECT_PAYMENT_METHODS_ACTIONS
} from '@/utils/constants/actionNames';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { Project } from '@/types/projects';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { Project } from '@/types/projects';
import {
API_KEYS_ACTIONS,
APP_STATE_ACTIONS,
NOTIFICATION_ACTIONS,
PM_ACTIONS,
PROJECT_PAYMENT_METHODS_ACTIONS
} from '@/utils/constants/actionNames';
@Component
export default class ProjectSelectionDropdown extends Vue {
private FIRST_PAGE = 1;
@Component
export default class ProjectSelectionDropdown extends Vue {
private FIRST_PAGE = 1;
public async onProjectSelected(projectID: string): Promise<void> {
this.$store.dispatch(PROJECTS_ACTIONS.SELECT, projectID);
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_PROJECTS);
this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
public async onProjectSelected(projectID: string): Promise<void> {
this.$store.dispatch(PROJECTS_ACTIONS.SELECT, projectID);
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_PROJECTS);
this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${error.message}`);
}
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${error.message}`);
}
try {
await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch api keys. ${error.message}`);
}
try {
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + error.message);
}
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${error.message}`);
}
public get projects(): Project[] {
return this.$store.getters.projects;
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${error.message}`);
}
try {
await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch api keys. ${error.message}`);
}
try {
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + error.message);
}
}
public get projects(): Project[] {
return this.$store.getters.projects;
}
}
</script>
<style scoped lang="scss">

View File

@ -4,61 +4,62 @@
<template src="./navigationArea.html"></template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { NavigationLink } from '@/types/navigation';
import { RouteConfig } from '@/router';
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class NavigationArea extends Vue {
// TODO: Use SvgLoaderComponent to reduce markup lines
public readonly navigation: NavigationLink[] = [
RouteConfig.ProjectOverview.with(RouteConfig.ProjectDetails).withIcon(
`<svg class="svg" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.073 5.3913C21.8382 5.15652 21.5252 5 21.2121 5H6.89038C6.26429 5 5.71647 5.54783 5.79473 6.09565L2.19473 6.01739C1.80342 6.09565 1.49038 6.17391 1.2556 6.48696C1.09908 6.72174 0.942555 7.03478 1.02082 7.42609L3.13386 18.4609C3.21212 19.0087 3.68169 19.4 4.22951 19.4H19.0991C19.6469 19.4 20.1165 19.0087 20.1947 18.4609L22.3078 6.33043C22.386 6.01739 22.3078 5.62609 22.073 5.3913ZM19.6469 14.7826L18.7078 9.38261C18.6295 8.75652 18.0034 8.28696 17.3773 8.28696H8.4556C8.37734 8.28696 8.29908 8.2087 8.29908 8.13043L8.14256 7.34783C8.14256 6.8 7.75125 6.33043 7.28169 6.17391L21.2121 6.09565L19.6469 14.7826Z" fill="#363840"/>
</svg>`),
RouteConfig.Team.withIcon(
`<svg class="svg" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M19.6621 11.1388C19.8233 10.8971 19.9039 10.6553 19.9845 10.333C20.5485 10.0913 20.8709 9.52718 20.9515 8.72136C21.032 7.99612 20.7097 7.43204 20.3068 7.10971C20.2262 5.01456 19.5816 3 16.6 3C13.6184 3 12.9738 5.01456 12.8126 7.10971C12.4097 7.43204 12.0874 8.0767 12.168 8.72136C12.2485 9.4466 12.6515 10.0913 13.135 10.333C13.2155 10.5748 13.3767 10.8971 13.4573 11.1388C12.9738 11.3 12.4903 11.4612 12.0874 11.6223C11.6845 11.4612 11.201 11.2194 10.7175 11.1388C10.8786 10.8971 10.9592 10.6553 11.0398 10.333C11.6039 10.0913 11.9262 9.52718 12.0068 8.72136C12.0874 7.99612 11.765 7.43204 11.3621 7.10971C11.201 5.01456 10.6369 3 7.57476 3C4.5932 3 3.94854 5.01456 3.78738 7.10971C3.38447 7.43204 3.06214 8.0767 3.14272 8.72136C3.2233 9.4466 3.62621 10.0913 4.10971 10.333C4.19029 10.5748 4.35146 10.8971 4.43204 11.1388C1.53107 11.9447 0 13.6369 0 16.2155C0 16.4573 0.161165 16.6184 0.32233 16.699C2.09515 17.5049 4.75437 17.9883 7.49418 17.9883C9.10583 17.9883 10.6369 17.8272 12.0068 17.5049C13.3767 17.8272 14.9078 17.9883 16.5194 17.9883C19.2592 17.9883 21.9184 17.5049 23.6913 16.699C23.8524 16.6184 24.0136 16.3767 24.0136 16.2155C24.0942 13.6369 22.5631 11.9447 19.6621 11.1388Z" fill="#354049"/>
<path d="M13.7532 12.8937C13.1891 13.1354 12.5445 13.2966 11.8192 13.4577C11.094 13.2966 10.4493 13.1354 9.88525 12.8937C9.96584 11.6043 10.6105 10.5568 11.8192 9.91211C13.1086 10.5568 13.6726 11.5238 13.7532 12.8937Z" fill="#2683FF"/>
<path class="white" d="M14.3174 13.832C17.0572 14.4767 19.0717 15.8466 19.0717 18.7476C17.1378 19.634 14.3174 20.0369 11.8193 20.0369C9.32126 20.0369 6.42029 19.634 4.56689 18.7476C4.56689 15.8466 6.50087 14.4767 9.32126 13.832" fill="white"/>
<path d="M11.7389 20.6001C8.8379 20.6001 6.01751 20.1166 4.24469 19.2302C4.08353 19.2302 3.92236 18.9884 3.92236 18.8273C3.92236 16.0069 5.6146 14.1535 9.07964 13.3477L9.32139 14.3952C6.58159 15.0399 5.21168 16.3292 5.05052 18.4244C6.74275 19.1496 9.24081 19.5525 11.8195 19.5525C14.3981 19.5525 16.8156 19.1496 18.5884 18.4244C18.4272 16.3292 17.0573 15.0399 14.3175 14.3952L14.5593 13.3477C17.9437 14.1535 19.7165 16.0069 19.7165 18.8273C19.7165 19.069 19.5554 19.2302 19.3942 19.3108C17.4602 20.1166 14.6398 20.6001 11.7389 20.6001Z" fill="#354049"/>
<path class="white" d="M11.7388 15.3628C10.5301 15.3628 9.16018 13.9929 8.5961 12.1395C8.11261 12.1395 7.79028 11.5754 7.7097 10.9308C7.62911 10.2861 7.95144 9.72205 8.35436 9.56088C8.43494 6.98224 9.16018 5.45117 11.7388 5.45117C14.3175 5.45117 15.0427 6.98224 15.1233 9.56088C15.5262 9.64146 15.8485 10.2055 15.7679 10.8502C15.6874 11.4949 15.365 12.0589 14.8815 12.0589C14.398 13.9929 12.9476 15.3628 11.7388 15.3628Z" fill="white"/>
<path d="M11.7387 15.926C10.2882 15.926 8.83776 14.5561 8.11251 12.6221C7.54844 12.3804 7.14552 11.7357 7.06494 11.0105C7.06494 10.2047 7.38727 9.56 7.79018 9.23767C7.95135 7.06194 8.59601 4.9668 11.7387 4.9668C14.8814 4.9668 15.5261 7.06194 15.6873 9.23767C16.1708 9.56 16.4125 10.2047 16.3319 10.9299C16.2513 11.7357 15.8484 12.3804 15.2844 12.5416C14.7203 14.4755 13.1892 15.926 11.7387 15.926ZM11.7387 6.01437C9.72416 6.01437 8.99892 6.98136 8.83776 9.56C8.83776 9.80175 8.67659 10.0435 8.43484 10.0435C8.35426 10.0435 8.1931 10.3658 8.1931 10.7687C8.1931 11.2522 8.43484 11.494 8.51543 11.494C8.75717 11.494 8.99892 11.6551 8.99892 11.8969C9.48242 13.6697 10.7717 14.7173 11.6581 14.7173C12.5446 14.7173 13.7533 13.6697 14.3174 11.8969C14.398 11.6551 14.5591 11.494 14.8009 11.494C14.8814 11.494 15.1232 11.2522 15.1232 10.7687C15.1232 10.2852 14.962 10.0435 14.8814 10.0435C14.6397 9.96291 14.4785 9.80175 14.4785 9.56C14.4785 6.98136 13.7533 6.01437 11.7387 6.01437Z" fill="#354049"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>`),
RouteConfig.ApiKeys.withIcon(
`<svg class="svg" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M18.6059 9.10806C18.6059 8.85201 18.6912 8.51062 18.6912 8.25458C18.6912 4.24322 15.3626 1 11.3513 1C7.33993 1 4.0967 4.24322 4.0967 8.25458C4.0967 8.51062 4.0967 8.85201 4.18205 9.10806C1.87766 9.27875 0 11.1564 0 13.5462C0 16.0212 1.963 17.9842 4.4381 17.9842H7.76667V21.9103L6.82784 21.0568C6.57179 20.8007 6.14506 20.8007 5.88901 21.0568C5.63297 21.3128 5.63297 21.7396 5.88901 21.9956L8.27875 24.2147L10.6685 21.9956C10.9245 21.7396 10.9245 21.3128 10.6685 21.0568C10.4125 20.8007 9.98571 20.8007 9.72967 21.0568L8.79084 21.9103V17.9842H13.3143V12.0952L12.3755 12.9487C12.1194 13.2048 11.6927 13.2048 11.4366 12.9487C11.1806 12.6927 11.1806 12.2659 11.4366 12.0099L13.8264 9.79084L16.2161 12.0099C16.4722 12.2659 16.4722 12.6927 16.2161 12.9487C16.1308 13.1194 15.8747 13.1194 15.704 13.1194C15.5333 13.1194 15.3626 13.0341 15.2773 12.9487L14.3385 12.0952V17.9842H17.8377C17.9231 17.9842 18.0084 17.9842 18.0084 17.8989C20.2275 17.6429 22.0198 15.7652 22.0198 13.4608C22.6172 11.2418 20.8249 9.3641 18.6059 9.10806Z" fill="#354049"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>`),
RouteConfig.Buckets.withIcon(
`<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>`),
];
import { RouteConfig } from '@/router';
import { NavigationLink } from '@/types/navigation';
public onLogoClick(): void {
location.reload();
}
@Component
export default class NavigationArea extends Vue {
// TODO: Use SvgLoaderComponent to reduce markup lines
public readonly navigation: NavigationLink[] = [
RouteConfig.ProjectOverview.with(RouteConfig.ProjectDetails).withIcon(
`<svg class="svg" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.073 5.3913C21.8382 5.15652 21.5252 5 21.2121 5H6.89038C6.26429 5 5.71647 5.54783 5.79473 6.09565L2.19473 6.01739C1.80342 6.09565 1.49038 6.17391 1.2556 6.48696C1.09908 6.72174 0.942555 7.03478 1.02082 7.42609L3.13386 18.4609C3.21212 19.0087 3.68169 19.4 4.22951 19.4H19.0991C19.6469 19.4 20.1165 19.0087 20.1947 18.4609L22.3078 6.33043C22.386 6.01739 22.3078 5.62609 22.073 5.3913ZM19.6469 14.7826L18.7078 9.38261C18.6295 8.75652 18.0034 8.28696 17.3773 8.28696H8.4556C8.37734 8.28696 8.29908 8.2087 8.29908 8.13043L8.14256 7.34783C8.14256 6.8 7.75125 6.33043 7.28169 6.17391L21.2121 6.09565L19.6469 14.7826Z" fill="#363840"/>
</svg>`),
RouteConfig.Team.withIcon(
`<svg class="svg" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M19.6621 11.1388C19.8233 10.8971 19.9039 10.6553 19.9845 10.333C20.5485 10.0913 20.8709 9.52718 20.9515 8.72136C21.032 7.99612 20.7097 7.43204 20.3068 7.10971C20.2262 5.01456 19.5816 3 16.6 3C13.6184 3 12.9738 5.01456 12.8126 7.10971C12.4097 7.43204 12.0874 8.0767 12.168 8.72136C12.2485 9.4466 12.6515 10.0913 13.135 10.333C13.2155 10.5748 13.3767 10.8971 13.4573 11.1388C12.9738 11.3 12.4903 11.4612 12.0874 11.6223C11.6845 11.4612 11.201 11.2194 10.7175 11.1388C10.8786 10.8971 10.9592 10.6553 11.0398 10.333C11.6039 10.0913 11.9262 9.52718 12.0068 8.72136C12.0874 7.99612 11.765 7.43204 11.3621 7.10971C11.201 5.01456 10.6369 3 7.57476 3C4.5932 3 3.94854 5.01456 3.78738 7.10971C3.38447 7.43204 3.06214 8.0767 3.14272 8.72136C3.2233 9.4466 3.62621 10.0913 4.10971 10.333C4.19029 10.5748 4.35146 10.8971 4.43204 11.1388C1.53107 11.9447 0 13.6369 0 16.2155C0 16.4573 0.161165 16.6184 0.32233 16.699C2.09515 17.5049 4.75437 17.9883 7.49418 17.9883C9.10583 17.9883 10.6369 17.8272 12.0068 17.5049C13.3767 17.8272 14.9078 17.9883 16.5194 17.9883C19.2592 17.9883 21.9184 17.5049 23.6913 16.699C23.8524 16.6184 24.0136 16.3767 24.0136 16.2155C24.0942 13.6369 22.5631 11.9447 19.6621 11.1388Z" fill="#354049"/>
<path d="M13.7532 12.8937C13.1891 13.1354 12.5445 13.2966 11.8192 13.4577C11.094 13.2966 10.4493 13.1354 9.88525 12.8937C9.96584 11.6043 10.6105 10.5568 11.8192 9.91211C13.1086 10.5568 13.6726 11.5238 13.7532 12.8937Z" fill="#2683FF"/>
<path class="white" d="M14.3174 13.832C17.0572 14.4767 19.0717 15.8466 19.0717 18.7476C17.1378 19.634 14.3174 20.0369 11.8193 20.0369C9.32126 20.0369 6.42029 19.634 4.56689 18.7476C4.56689 15.8466 6.50087 14.4767 9.32126 13.832" fill="white"/>
<path d="M11.7389 20.6001C8.8379 20.6001 6.01751 20.1166 4.24469 19.2302C4.08353 19.2302 3.92236 18.9884 3.92236 18.8273C3.92236 16.0069 5.6146 14.1535 9.07964 13.3477L9.32139 14.3952C6.58159 15.0399 5.21168 16.3292 5.05052 18.4244C6.74275 19.1496 9.24081 19.5525 11.8195 19.5525C14.3981 19.5525 16.8156 19.1496 18.5884 18.4244C18.4272 16.3292 17.0573 15.0399 14.3175 14.3952L14.5593 13.3477C17.9437 14.1535 19.7165 16.0069 19.7165 18.8273C19.7165 19.069 19.5554 19.2302 19.3942 19.3108C17.4602 20.1166 14.6398 20.6001 11.7389 20.6001Z" fill="#354049"/>
<path class="white" d="M11.7388 15.3628C10.5301 15.3628 9.16018 13.9929 8.5961 12.1395C8.11261 12.1395 7.79028 11.5754 7.7097 10.9308C7.62911 10.2861 7.95144 9.72205 8.35436 9.56088C8.43494 6.98224 9.16018 5.45117 11.7388 5.45117C14.3175 5.45117 15.0427 6.98224 15.1233 9.56088C15.5262 9.64146 15.8485 10.2055 15.7679 10.8502C15.6874 11.4949 15.365 12.0589 14.8815 12.0589C14.398 13.9929 12.9476 15.3628 11.7388 15.3628Z" fill="white"/>
<path d="M11.7387 15.926C10.2882 15.926 8.83776 14.5561 8.11251 12.6221C7.54844 12.3804 7.14552 11.7357 7.06494 11.0105C7.06494 10.2047 7.38727 9.56 7.79018 9.23767C7.95135 7.06194 8.59601 4.9668 11.7387 4.9668C14.8814 4.9668 15.5261 7.06194 15.6873 9.23767C16.1708 9.56 16.4125 10.2047 16.3319 10.9299C16.2513 11.7357 15.8484 12.3804 15.2844 12.5416C14.7203 14.4755 13.1892 15.926 11.7387 15.926ZM11.7387 6.01437C9.72416 6.01437 8.99892 6.98136 8.83776 9.56C8.83776 9.80175 8.67659 10.0435 8.43484 10.0435C8.35426 10.0435 8.1931 10.3658 8.1931 10.7687C8.1931 11.2522 8.43484 11.494 8.51543 11.494C8.75717 11.494 8.99892 11.6551 8.99892 11.8969C9.48242 13.6697 10.7717 14.7173 11.6581 14.7173C12.5446 14.7173 13.7533 13.6697 14.3174 11.8969C14.398 11.6551 14.5591 11.494 14.8009 11.494C14.8814 11.494 15.1232 11.2522 15.1232 10.7687C15.1232 10.2852 14.962 10.0435 14.8814 10.0435C14.6397 9.96291 14.4785 9.80175 14.4785 9.56C14.4785 6.98136 13.7533 6.01437 11.7387 6.01437Z" fill="#354049"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>`),
RouteConfig.ApiKeys.withIcon(
`<svg class="svg" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M18.6059 9.10806C18.6059 8.85201 18.6912 8.51062 18.6912 8.25458C18.6912 4.24322 15.3626 1 11.3513 1C7.33993 1 4.0967 4.24322 4.0967 8.25458C4.0967 8.51062 4.0967 8.85201 4.18205 9.10806C1.87766 9.27875 0 11.1564 0 13.5462C0 16.0212 1.963 17.9842 4.4381 17.9842H7.76667V21.9103L6.82784 21.0568C6.57179 20.8007 6.14506 20.8007 5.88901 21.0568C5.63297 21.3128 5.63297 21.7396 5.88901 21.9956L8.27875 24.2147L10.6685 21.9956C10.9245 21.7396 10.9245 21.3128 10.6685 21.0568C10.4125 20.8007 9.98571 20.8007 9.72967 21.0568L8.79084 21.9103V17.9842H13.3143V12.0952L12.3755 12.9487C12.1194 13.2048 11.6927 13.2048 11.4366 12.9487C11.1806 12.6927 11.1806 12.2659 11.4366 12.0099L13.8264 9.79084L16.2161 12.0099C16.4722 12.2659 16.4722 12.6927 16.2161 12.9487C16.1308 13.1194 15.8747 13.1194 15.704 13.1194C15.5333 13.1194 15.3626 13.0341 15.2773 12.9487L14.3385 12.0952V17.9842H17.8377C17.9231 17.9842 18.0084 17.9842 18.0084 17.8989C20.2275 17.6429 22.0198 15.7652 22.0198 13.4608C22.6172 11.2418 20.8249 9.3641 18.6059 9.10806Z" fill="#354049"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>`),
RouteConfig.Buckets.withIcon(
`<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>`),
];
public get isProjectNotSelected(): boolean {
return this.$store.state.projectsModule.selectedProject.id === '';
}
public onLogoClick(): void {
location.reload();
}
public get isProjectNotSelected(): boolean {
return this.$store.state.projectsModule.selectedProject.id === '';
}
}
</script>
<style src="./navigationArea.scss" lang="scss"></style>

View File

@ -10,14 +10,15 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { NavigationLink } from '@/types/navigation';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component({})
export default class TabNavigation extends Vue {
@Prop({default: new Array(NavigationLink)})
private navigation: NavigationLink[];
}
import { NavigationLink } from '@/types/navigation';
@Component({})
export default class TabNavigation extends Vue {
@Prop({default: new Array(NavigationLink)})
private navigation: NavigationLink[];
}
</script>
<style scoped lang="scss">

View File

@ -18,38 +18,39 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { DelayedNotification } from '@/types/DelayedNotification';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class Notification extends Vue {
@Prop({default: () => new DelayedNotification(() => { return; }, '', '')})
private notification: DelayedNotification;
import { DelayedNotification } from '@/types/DelayedNotification';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
public isClassActive = false;
@Component
export default class Notification extends Vue {
@Prop({default: () => new DelayedNotification(() => { return; }, '', '')})
private notification: DelayedNotification;
// Force delete notification
public onCloseClick(): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.DELETE, this.notification.id);
}
public isClassActive = false;
// Force notification to stay on page on mouse over it
public onMouseOver(): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.PAUSE, this.notification.id);
}
// Resume notification flow when mouse leaves notification
public onMouseLeave(): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.RESUME, this.notification.id);
}
public mounted() {
setTimeout(() => {
this.isClassActive = true;
}, 100);
}
// Force delete notification
public onCloseClick(): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.DELETE, this.notification.id);
}
// Force notification to stay on page on mouse over it
public onMouseOver(): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.PAUSE, this.notification.id);
}
// Resume notification flow when mouse leaves notification
public onMouseLeave(): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.RESUME, this.notification.id);
}
public mounted() {
setTimeout(() => {
this.isClassActive = true;
}, 100);
}
}
</script>
<style scoped lang="scss">

View File

@ -8,20 +8,22 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Notification from '@/components/notifications/Notification.vue';
import { DelayedNotification } from '@/types/DelayedNotification';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Notification,
}
})
export default class NotificationArea extends Vue {
public get notifications(): DelayedNotification[] {
return this.$store.state.notificationsModule.notificationQueue;
}
import Notification from '@/components/notifications/Notification.vue';
import { DelayedNotification } from '@/types/DelayedNotification';
@Component({
components: {
Notification,
}
})
export default class NotificationArea extends Vue {
public get notifications(): DelayedNotification[] {
return this.$store.state.notificationsModule.notificationQueue;
}
}
</script>
<style scoped lang="scss">

View File

@ -60,18 +60,19 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Checkbox from '@/components/common/Checkbox.vue';
import Button from '@/components/common/Button.vue';
@Component({
components: {
Checkbox,
Button,
}
})
export default class AddStripeCardPopup extends Vue {}
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import Checkbox from '@/components/common/Checkbox.vue';
@Component({
components: {
Checkbox,
Button,
}
})
export default class AddStripeCardPopup extends Vue {}
</script>
<style scoped lang="scss">

View File

@ -79,91 +79,92 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { NOTIFICATION_ACTIONS, PM_ACTIONS, APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { API_KEYS_ACTIONS } from '@/utils/constants/actionNames';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Button
}
})
export default class DeleteProjectPopup extends Vue {
private projectName: string = '';
private nameError: string = '';
private isLoading: boolean = false;
import Button from '@/components/common/Button.vue';
public resetError (): void {
this.nameError = '';
}
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { API_KEYS_ACTIONS, APP_STATE_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
public async onDeleteProjectClick(): Promise<void> {
if (this.isLoading) {
return;
}
if (!this.validateProjectName()) {
return;
}
this.isLoading = true;
try {
await this.$store.dispatch(PROJECTS_ACTIONS.DELETE, this.$store.getters.selectedProject.id);
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Project was successfully deleted');
await this.selectProject();
} catch (e) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
this.isLoading = false;
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_PROJ);
}
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_PROJ);
}
public get isDeleteButtonDisabled(): boolean {
return !this.projectName || !!this.nameError;
}
private validateProjectName(): boolean {
if (this.projectName === this.$store.getters.selectedProject.name) {
return true;
}
this.nameError = 'Name doesn\'t match with current project name';
this.isLoading = false;
return false;
}
private async selectProject(): Promise<void> {
if (this.$store.state.projectsModule.projects.length === 0) {
await this.$store.dispatch(PM_ACTIONS.CLEAR);
await this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
await this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.CLEAR);
return;
}
// TODO: reuse select project functionality
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, this.$store.state.projectsModule.projects[0].id);
await this.$store.dispatch(PM_ACTIONS.FETCH, 1);
await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, 1);
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
}
@Component({
components: {
Button
}
})
export default class DeleteProjectPopup extends Vue {
private projectName: string = '';
private nameError: string = '';
private isLoading: boolean = false;
public resetError (): void {
this.nameError = '';
}
public async onDeleteProjectClick(): Promise<void> {
if (this.isLoading) {
return;
}
if (!this.validateProjectName()) {
return;
}
this.isLoading = true;
try {
await this.$store.dispatch(PROJECTS_ACTIONS.DELETE, this.$store.getters.selectedProject.id);
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Project was successfully deleted');
await this.selectProject();
} catch (e) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
this.isLoading = false;
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_PROJ);
}
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_PROJ);
}
public get isDeleteButtonDisabled(): boolean {
return !this.projectName || !!this.nameError;
}
private validateProjectName(): boolean {
if (this.projectName === this.$store.getters.selectedProject.name) {
return true;
}
this.nameError = 'Name doesn\'t match with current project name';
this.isLoading = false;
return false;
}
private async selectProject(): Promise<void> {
if (this.$store.state.projectsModule.projects.length === 0) {
await this.$store.dispatch(PM_ACTIONS.CLEAR);
await this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
await this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.CLEAR);
return;
}
// TODO: reuse select project functionality
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, this.$store.state.projectsModule.projects[0].id);
await this.$store.dispatch(PM_ACTIONS.FETCH, 1);
await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, 1);
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
}
}
</script>
<style scoped lang="scss">

View File

@ -44,165 +44,167 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import {
API_KEYS_ACTIONS,
APP_STATE_ACTIONS,
NOTIFICATION_ACTIONS,
PM_ACTIONS,
} from '@/utils/constants/actionNames';
import Button from '@/components/common/Button.vue';
import Checkbox from '@/components/common/Checkbox.vue';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { CreateProjectModel, Project } from '@/types/projects';
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
HeaderedInput,
Checkbox,
Button,
}
})
export default class NewProjectPopup extends Vue {
private projectName: string = '';
private description: string = '';
private nameError: string = '';
private createdProjectId: string = '';
private isLoading: boolean = false;
import Button from '@/components/common/Button.vue';
import Checkbox from '@/components/common/Checkbox.vue';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
public setProjectName (value: string): void {
this.projectName = value;
this.nameError = '';
}
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { CreateProjectModel, Project } from '@/types/projects';
import {
API_KEYS_ACTIONS,
APP_STATE_ACTIONS,
NOTIFICATION_ACTIONS,
PM_ACTIONS,
} from '@/utils/constants/actionNames';
public setProjectDescription (value: string): void {
this.description = value;
}
public onCloseClick (): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
}
public async createProjectClick (): Promise<void> {
if (this.isLoading) {
return;
}
this.isLoading = true;
if (!this.validateProjectName()) {
this.isLoading = false;
return;
}
if (!await this.createProject()) {
this.isLoading = false;
return;
}
this.selectCreatedProject();
try {
await this.fetchProjectMembers();
} catch (e) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
this.clearApiKeys();
this.clearUsage();
this.clearBucketUsage();
this.checkIfsFirstProject();
this.isLoading = false;
}
private validateProjectName(): boolean {
this.projectName = this.projectName.trim();
const rgx = /^[^/]+$/;
if (!rgx.test(this.projectName)) {
this.nameError = 'Name for project is invalid!';
return false;
}
if (this.projectName.length > 20) {
this.nameError = 'Name should be less than 21 character!';
return false;
}
return true;
}
private async createProject(): Promise<Project> {
const project: CreateProjectModel = {
name: this.projectName,
description: this.description,
};
let newProject: Project = {} as Project;
try {
newProject = await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
} catch (error) {
this.notifyError(error.message);
}
this.createdProjectId = newProject.id;
return newProject;
}
private selectCreatedProject(): void {
this.$store.dispatch(PROJECTS_ACTIONS.SELECT, this.createdProjectId);
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
}
private checkIfsFirstProject(): void {
let isFirstProject = this.$store.state.projectsModule.projects.length === 1;
isFirstProject
? this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP)
: this.notifySuccess('Project created successfully!');
}
private async fetchProjectMembers(): Promise<void> {
await this.$store.dispatch(PM_ACTIONS.CLEAR);
const fistPage = 1;
await this.$store.dispatch(PM_ACTIONS.FETCH, fistPage);
}
private clearApiKeys(): void {
this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
}
private clearUsage(): void {
this.$store.dispatch(PROJECT_USAGE_ACTIONS.CLEAR);
}
private clearBucketUsage(): void {
this.$store.dispatch(BUCKET_ACTIONS.SET_SEARCH, '');
this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
}
private notifyError(message: string): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, message);
}
private notifySuccess(message: string): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, message);
}
@Component({
components: {
HeaderedInput,
Checkbox,
Button,
}
})
export default class NewProjectPopup extends Vue {
private projectName: string = '';
private description: string = '';
private nameError: string = '';
private createdProjectId: string = '';
private isLoading: boolean = false;
public setProjectName (value: string): void {
this.projectName = value;
this.nameError = '';
}
public setProjectDescription (value: string): void {
this.description = value;
}
public onCloseClick (): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
}
public async createProjectClick (): Promise<void> {
if (this.isLoading) {
return;
}
this.isLoading = true;
if (!this.validateProjectName()) {
this.isLoading = false;
return;
}
if (!await this.createProject()) {
this.isLoading = false;
return;
}
this.selectCreatedProject();
try {
await this.fetchProjectMembers();
} catch (e) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
this.clearApiKeys();
this.clearUsage();
this.clearBucketUsage();
this.checkIfsFirstProject();
this.isLoading = false;
}
private validateProjectName(): boolean {
this.projectName = this.projectName.trim();
const rgx = /^[^/]+$/;
if (!rgx.test(this.projectName)) {
this.nameError = 'Name for project is invalid!';
return false;
}
if (this.projectName.length > 20) {
this.nameError = 'Name should be less than 21 character!';
return false;
}
return true;
}
private async createProject(): Promise<Project> {
const project: CreateProjectModel = {
name: this.projectName,
description: this.description,
};
let newProject: Project = {} as Project;
try {
newProject = await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
} catch (error) {
this.notifyError(error.message);
}
this.createdProjectId = newProject.id;
return newProject;
}
private selectCreatedProject(): void {
this.$store.dispatch(PROJECTS_ACTIONS.SELECT, this.createdProjectId);
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
}
private checkIfsFirstProject(): void {
const isFirstProject = this.$store.state.projectsModule.projects.length === 1;
isFirstProject
? this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP)
: this.notifySuccess('Project created successfully!');
}
private async fetchProjectMembers(): Promise<void> {
await this.$store.dispatch(PM_ACTIONS.CLEAR);
const fistPage = 1;
await this.$store.dispatch(PM_ACTIONS.FETCH, fistPage);
}
private clearApiKeys(): void {
this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
}
private clearUsage(): void {
this.$store.dispatch(PROJECT_USAGE_ACTIONS.CLEAR);
}
private clearBucketUsage(): void {
this.$store.dispatch(BUCKET_ACTIONS.SET_SEARCH, '');
this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
}
private notifyError(message: string): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, message);
}
private notifySuccess(message: string): void {
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, message);
}
}
</script>
<style scoped lang="scss">

View File

@ -49,30 +49,32 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { RouteConfig } from '@/router';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Button,
}
})
export default class ProjectCreationSuccessPopup extends Vue {
private onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP);
}
import Button from '@/components/common/Button.vue';
public onCreateAPIKeyClick(): void {
this.$router.push(RouteConfig.ApiKeys.path);
this.onCloseClick();
}
import { RouteConfig } from '@/router';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isSuccessfulProjectCreationPopupShown;
}
@Component({
components: {
Button,
}
})
export default class ProjectCreationSuccessPopup extends Vue {
private onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP);
}
public onCreateAPIKeyClick(): void {
this.$router.push(RouteConfig.ApiKeys.path);
this.onCloseClick();
}
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isSuccessfulProjectCreationPopupShown;
}
}
</script>
<style scoped lang="scss">

View File

@ -47,83 +47,85 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import Checkbox from '@/components/common/Checkbox.vue';
import EmptyState from '@/components/common/EmptyStateArea.vue';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { RouteConfig } from '@/router';
import DeleteProjectPopup from '@/components/project/DeleteProjectPopup.vue';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { UpdateProjectModel } from '@/types/projects';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Button,
HeaderedInput,
Checkbox,
EmptyState,
DeleteProjectPopup,
}
})
export default class ProjectDetailsArea extends Vue {
private isEditing: boolean = false;
private newDescription: string = '';
import Button from '@/components/common/Button.vue';
import Checkbox from '@/components/common/Checkbox.vue';
import EmptyState from '@/components/common/EmptyStateArea.vue';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import DeleteProjectPopup from '@/components/project/DeleteProjectPopup.vue';
public async mounted(): Promise<void> {
try {
await this.$store.dispatch(PROJECTS_ACTIONS.FETCH);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
}
import { RouteConfig } from '@/router';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { UpdateProjectModel } from '@/types/projects';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
public get name(): string {
return this.$store.getters.selectedProject.name;
}
@Component({
components: {
Button,
HeaderedInput,
Checkbox,
EmptyState,
DeleteProjectPopup,
}
})
export default class ProjectDetailsArea extends Vue {
private isEditing: boolean = false;
private newDescription: string = '';
public get description(): string {
return this.$store.getters.selectedProject.description ?
this.$store.getters.selectedProject.description :
'No description yet. Please enter some information about the project if any.';
}
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isDeleteProjectPopupShown;
}
public setNewDescription(value: string): void {
this.newDescription = value;
}
public async onSaveButtonClick(): Promise<void> {
try {
await this.$store.dispatch(
PROJECTS_ACTIONS.UPDATE, new UpdateProjectModel(this.$store.getters.selectedProject.id, this.newDescription)
);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
this.toggleEditing();
await this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Project updated successfully!');
}
public toggleDeleteDialog(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_PROJ);
}
public onMoreClick(): void {
this.$router.push(RouteConfig.UsageReport.path);
}
private toggleEditing(): void {
this.isEditing = !this.isEditing;
// TODO: cache this value in future
this.newDescription = '';
public async mounted(): Promise<void> {
try {
await this.$store.dispatch(PROJECTS_ACTIONS.FETCH);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
}
public get name(): string {
return this.$store.getters.selectedProject.name;
}
public get description(): string {
return this.$store.getters.selectedProject.description ?
this.$store.getters.selectedProject.description :
'No description yet. Please enter some information about the project if any.';
}
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isDeleteProjectPopupShown;
}
public setNewDescription(value: string): void {
this.newDescription = value;
}
public async onSaveButtonClick(): Promise<void> {
try {
await this.$store.dispatch(
PROJECTS_ACTIONS.UPDATE, new UpdateProjectModel(this.$store.getters.selectedProject.id, this.newDescription)
);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
this.toggleEditing();
await this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Project updated successfully!');
}
public toggleDeleteDialog(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_DEL_PROJ);
}
public onMoreClick(): void {
this.$router.push(RouteConfig.UsageReport.path);
}
private toggleEditing(): void {
this.isEditing = !this.isEditing;
// TODO: cache this value in future
this.newDescription = '';
}
}
</script>
<style scoped lang="scss">

View File

@ -17,33 +17,35 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import EmptyState from '@/components/common/EmptyStateArea.vue';
import TabNavigation from '@/components/navigation/TabNavigation.vue';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import { NavigationLink } from '@/types/navigation';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
EmptyState,
TabNavigation,
}
})
export default class ProjectOverviewArea extends Vue {
// TODO: make type for project routes
public navigation: NavigationLink[] = [
new NavigationLink('/project-overview/details', 'Details'),
new NavigationLink('/project-overview/usage-report', 'Report'),
];
import EmptyState from '@/components/common/EmptyStateArea.vue';
import TabNavigation from '@/components/navigation/TabNavigation.vue';
public get isProjectSelected(): boolean {
return this.$store.getters.selectedProject.id !== '';
}
import { NavigationLink } from '@/types/navigation';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
public get emptyImage(): string {
return EMPTY_STATE_IMAGES.PROJECT;
}
@Component({
components: {
EmptyState,
TabNavigation,
}
})
export default class ProjectOverviewArea extends Vue {
// TODO: make type for project routes
public navigation: NavigationLink[] = [
new NavigationLink('/project-overview/details', 'Details'),
new NavigationLink('/project-overview/usage-report', 'Report'),
];
public get isProjectSelected(): boolean {
return this.$store.getters.selectedProject.id !== '';
}
public get emptyImage(): string {
return EMPTY_STATE_IMAGES.PROJECT;
}
}
</script>
<style scoped lang="scss">

View File

@ -14,21 +14,22 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import CardComponent from '@/components/project/paymentMethods/CardComponent.vue';
import NewPaymentMethodPopup from '@/components/project/paymentMethods/NewPaymentMethodComponent.vue';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
NewPaymentMethodPopup,
CardComponent,
}
})
export default class ProjectPaymentMethods extends Vue {
public get paymentMethods(): PaymentMethod[] {
return this.$store.state.projectPaymentsMethodsModule.paymentMethods;
}
import CardComponent from '@/components/project/paymentMethods/CardComponent.vue';
import NewPaymentMethodPopup from '@/components/project/paymentMethods/NewPaymentMethodComponent.vue';
@Component({
components: {
NewPaymentMethodPopup,
CardComponent,
}
})
export default class ProjectPaymentMethods extends Vue {
public get paymentMethods(): PaymentMethod[] {
return this.$store.state.projectPaymentsMethodsModule.paymentMethods;
}
}
</script>
<style scoped lang="scss">

View File

@ -57,182 +57,184 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import Datepicker from '@/components/project/DatePicker.vue';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { toUnixTimestamp } from '@/utils/time';
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { DateRange } from '@/types/usage';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
Datepicker,
}
})
export default class UsageReport extends Vue {
public startTime: any = {
time: '',
import Datepicker from '@/components/project/DatePicker.vue';
import { RouteConfig } from '@/router';
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { DateRange } from '@/types/usage';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { toUnixTimestamp } from '@/utils/time';
@Component({
components: {
Datepicker,
}
})
export default class UsageReport extends Vue {
public startTime: any = {
time: '',
};
private readonly dateRange: any;
public constructor() {
super();
const currentDate = new Date();
const previousDate = new Date();
previousDate.setMonth(currentDate.getMonth() - 1);
this.dateRange = {
startDate: previousDate,
endDate: currentDate,
};
private readonly dateRange: any;
}
public constructor() {
super();
public get startDate(): Date {
return this.$store.state.usageModule.startDate;
}
const currentDate = new Date();
const previousDate = new Date();
previousDate.setMonth(currentDate.getMonth() - 1);
public get endDate(): Date {
return this.$store.state.usageModule.endDate;
}
this.dateRange = {
startDate: previousDate,
endDate: currentDate,
};
}
public get storage(): string {
return this.$store.state.usageModule.projectUsage.storage.toPrecision(5);
}
public get startDate(): Date {
return this.$store.state.usageModule.startDate;
}
public get egress(): string {
return this.$store.state.usageModule.projectUsage.egress.toPrecision(5);
}
public get endDate(): Date {
return this.$store.state.usageModule.endDate;
}
public get objectsCount(): string {
return this.$store.state.usageModule.projectUsage.objectCount.toPrecision(5);
}
public get storage(): string {
return this.$store.state.usageModule.projectUsage.storage.toPrecision(5);
}
public get egress(): string {
return this.$store.state.usageModule.projectUsage.egress.toPrecision(5);
}
public get objectsCount(): string {
return this.$store.state.usageModule.projectUsage.objectCount.toPrecision(5);
}
public async mounted(): Promise<void> {
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
}
}
public async beforeRouteLeave(to, from, next): Promise<void> {
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP, this.dateRange);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
const buttons = [...(document as any).querySelectorAll('.usage-report-container__options-area__option')];
buttons.forEach(option => {
option.classList.remove('active');
});
buttons[0].classList.add('active');
next();
}
public onBackClick(): void {
this.$router.push(RouteConfig.ProjectOverview.path);
}
public async onCurrentRollupClick(event: any): Promise<void> {
this.onButtonClickAction(event);
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
}
}
public async onPreviousRollupClick(event: any): Promise<void> {
this.onButtonClickAction(event);
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_PREVIOUS_ROLLUP);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
}
}
public onCustomDateClick(event: any): void {
(this as any).$refs.datePicker.showCheck();
this.onButtonClickAction(event);
}
public onReportClick(): void {
const projectID = this.$store.getters.selectedProject.id;
const startDate = this.$store.state.usageModule.startDate;
const endDate = this.$store.state.usageModule.endDate;
let url = new URL(location.origin);
url.pathname = 'usage-report';
url.searchParams.append('projectID', projectID);
url.searchParams.append('since', toUnixTimestamp(startDate).toString());
url.searchParams.append('before', toUnixTimestamp(endDate).toString());
window.open(url.href, '_blank');
}
public async getDates(datesArray: string[]): Promise<void> {
const now = new Date();
const firstDate = new Date(datesArray[0]);
const secondDate = new Date(datesArray[1]);
const isInverted = firstDate > secondDate;
let startDate = isInverted ? secondDate : firstDate;
let endDate = isInverted ? firstDate : secondDate;
endDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate(), 23, 59, 59));
if (now.getUTCFullYear() === endDate.getUTCFullYear() &&
now.getUTCMonth() === endDate.getUTCMonth() &&
now.getUTCDate() === endDate.getUTCDate()) {
endDate = now;
}
const dateRange: DateRange = new DateRange(startDate, endDate);
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH, dateRange);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
}
}
public toLocaleDateString(date: Date): string {
return date.toLocaleDateString('en-US', {timeZone: 'UTC'});
}
private onButtonClickAction(event: any): void {
let eventTarget = event.target;
if (eventTarget.children.length === 0) {
eventTarget = eventTarget.parentNode;
}
if (eventTarget.classList.contains('active')) {
return;
}
this.changeActiveClass(eventTarget);
}
private changeActiveClass(target: any): void {
this.removeActiveClass();
target.classList.add('active');
}
private removeActiveClass(): void {
const buttons = [...(document as any).querySelectorAll('.usage-report-container__options-area__option')];
buttons.forEach(option => {
option.classList.remove('active');
});
public async mounted(): Promise<void> {
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
}
}
public async beforeRouteLeave(to, from, next): Promise<void> {
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP, this.dateRange);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, e.message);
}
const buttons = [...(document as any).querySelectorAll('.usage-report-container__options-area__option')];
buttons.forEach(option => {
option.classList.remove('active');
});
buttons[0].classList.add('active');
next();
}
public onBackClick(): void {
this.$router.push(RouteConfig.ProjectOverview.path);
}
public async onCurrentRollupClick(event: any): Promise<void> {
this.onButtonClickAction(event);
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
}
}
public async onPreviousRollupClick(event: any): Promise<void> {
this.onButtonClickAction(event);
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_PREVIOUS_ROLLUP);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
}
}
public onCustomDateClick(event: any): void {
(this as any).$refs.datePicker.showCheck();
this.onButtonClickAction(event);
}
public onReportClick(): void {
const projectID = this.$store.getters.selectedProject.id;
const startDate = this.$store.state.usageModule.startDate;
const endDate = this.$store.state.usageModule.endDate;
const url = new URL(location.origin);
url.pathname = 'usage-report';
url.searchParams.append('projectID', projectID);
url.searchParams.append('since', toUnixTimestamp(startDate).toString());
url.searchParams.append('before', toUnixTimestamp(endDate).toString());
window.open(url.href, '_blank');
}
public async getDates(datesArray: string[]): Promise<void> {
const now = new Date();
const firstDate = new Date(datesArray[0]);
const secondDate = new Date(datesArray[1]);
const isInverted = firstDate > secondDate;
const startDate = isInverted ? secondDate : firstDate;
let endDate = isInverted ? firstDate : secondDate;
endDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate(), 23, 59, 59));
if (now.getUTCFullYear() === endDate.getUTCFullYear() &&
now.getUTCMonth() === endDate.getUTCMonth() &&
now.getUTCDate() === endDate.getUTCDate()) {
endDate = now;
}
const dateRange: DateRange = new DateRange(startDate, endDate);
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH, dateRange);
} catch (e) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${e.message}`);
}
}
public toLocaleDateString(date: Date): string {
return date.toLocaleDateString('en-US', {timeZone: 'UTC'});
}
private onButtonClickAction(event: any): void {
let eventTarget = event.target;
if (eventTarget.children.length === 0) {
eventTarget = eventTarget.parentNode;
}
if (eventTarget.classList.contains('active')) {
return;
}
this.changeActiveClass(eventTarget);
}
private changeActiveClass(target: any): void {
this.removeActiveClass();
target.classList.add('active');
}
private removeActiveClass(): void {
const buttons = [...(document as any).querySelectorAll('.usage-report-container__options-area__option')];
buttons.forEach(option => {
option.classList.remove('active');
});
}
}
</script>
<style scoped lang="scss">

View File

@ -21,60 +21,61 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { CREDIT_USAGE_ACTIONS } from '@/store/modules/credits';
import { CreditUsage } from '@/types/credits';
import { Component, Vue } from 'vue-property-decorator';
class CreditDescription {
public title: string;
public description: string;
public symbol: string;
import { CREDIT_USAGE_ACTIONS } from '@/store/modules/credits';
import { CreditUsage } from '@/types/credits';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
// possibly we could add some 'style' type
public style: any;
class CreditDescription {
public title: string;
public description: string;
public symbol: string;
constructor(title: string, description: string, symbol: string, color: string) {
this.title = title;
this.description = description;
this.symbol = symbol;
this.style = { backgroundColor: color };
// possibly we could add some 'style' type
public style: any;
constructor(title: string, description: string, symbol: string, color: string) {
this.title = title;
this.description = description;
this.symbol = symbol;
this.style = { backgroundColor: color };
}
}
@Component
export default class ReferralStats extends Vue {
private readonly TITLE_SUFFIX: string = 'Here Are Your Referrals So Far';
private stats: CreditDescription[] = [
new CreditDescription('referrals made', 'People you referred who signed up', '', '#FFFFFF'),
new CreditDescription('earned credits', 'Free credits that will apply to your upcoming bill', '$', 'rgba(217, 225, 236, 0.5)'),
new CreditDescription('applied credits', 'Free credits that have already been applied to your bill', '$', '#D1D7E0'),
];
public async mounted() {
// TODO: we pre-fetch all data in /src/views/Dashboard, but this is tardigrade related, so could be here
await this.fetch();
}
public async fetch(): Promise<void> {
try {
await this.$store.dispatch(CREDIT_USAGE_ACTIONS.FETCH);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch credit usage: ' + error.message);
}
}
@Component
export default class ReferralStats extends Vue {
private readonly TITLE_SUFFIX: string = 'Here Are Your Referrals So Far';
private stats: CreditDescription[] = [
new CreditDescription('referrals made', 'People you referred who signed up', '', '#FFFFFF'),
new CreditDescription('earned credits', 'Free credits that will apply to your upcoming bill', '$', 'rgba(217, 225, 236, 0.5)'),
new CreditDescription('applied credits', 'Free credits that have already been applied to your bill', '$', '#D1D7E0'),
];
public get title(): string {
// TODO: not sure that we are able to create a user with empty name
const name = this.$store.state.usersModule.fullName || '' ;
public async mounted() {
// TODO: we pre-fetch all data in /src/views/Dashboard, but this is tardigrade related, so could be here
await this.fetch();
}
public async fetch(): Promise<void> {
try {
await this.$store.dispatch(CREDIT_USAGE_ACTIONS.FETCH);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch credit usage: ' + error.message);
}
}
public get title(): string {
// TODO: not sure that we are able to create a user with empty name
let name = this.$store.state.usersModule.fullName || '' ;
return name.length > 0 ? `${name}, ${this.TITLE_SUFFIX}` : this.TITLE_SUFFIX;
}
public get usage(): CreditUsage {
return this.$store.getters.credits;
}
return name.length > 0 ? `${name}, ${this.TITLE_SUFFIX}` : this.TITLE_SUFFIX;
}
public get usage(): CreditUsage {
return this.$store.getters.credits;
}
}
</script>
<style scoped lang="scss">

View File

@ -65,162 +65,163 @@
</template>
<script lang='ts'>
import { Component, Vue } from 'vue-property-decorator';
import { Component, Vue } from 'vue-property-decorator';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
import Button from '@/components/common/Button.vue';
import { EmailInput } from '@/types/EmailInput';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import { RouteConfig } from '@/router';
import { validateEmail } from '@/utils/validation';
import Button from '@/components/common/Button.vue';
@Component({
components: {
Button
import { RouteConfig } from '@/router';
import { EmailInput } from '@/types/EmailInput';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages';
import { validateEmail } from '@/utils/validation';
@Component({
components: {
Button
}
})
export default class AddUserPopup extends Vue {
public imageSource: string = EMPTY_STATE_IMAGES.ADD_USER;
private inputs: EmailInput[] = [new EmailInput(), new EmailInput(), new EmailInput()];
private formError: string = '';
private isLoading: boolean = false;
private FIRST_PAGE = 1;
public async onAddUsersClick(): Promise<void> {
if (this.isLoading) {
return;
}
})
export default class AddUserPopup extends Vue {
public imageSource: string = EMPTY_STATE_IMAGES.ADD_USER;
private inputs: EmailInput[] = [new EmailInput(), new EmailInput(), new EmailInput()];
private formError: string = '';
private isLoading: boolean = false;
private FIRST_PAGE = 1;
this.isLoading = true;
public async onAddUsersClick(): Promise<void> {
if (this.isLoading) {
return;
const length = this.inputs.length;
const newInputsArray: EmailInput[] = [];
let areAllEmailsValid = true;
const emailArray: string[] = [];
for (let i = 0; i < length; i++) {
const element = this.inputs[i];
const isEmail = validateEmail(element.value);
if (isEmail) {
emailArray.push(element.value);
}
this.isLoading = true;
if (isEmail || element.value === '') {
element.setError(false);
newInputsArray.push(element);
let length = this.inputs.length;
let newInputsArray: EmailInput[] = [];
let areAllEmailsValid = true;
let emailArray: string[] = [];
for (let i = 0; i < length; i++) {
let element = this.inputs[i];
let isEmail = validateEmail(element.value);
if (isEmail) {
emailArray.push(element.value);
}
if (isEmail || element.value === '') {
element.setError(false);
newInputsArray.push(element);
continue;
}
element.setError(true);
newInputsArray.unshift(element);
areAllEmailsValid = false;
this.formError = 'Field is required. Please enter a valid email address';
continue;
}
this.inputs = newInputsArray;
element.setError(true);
newInputsArray.unshift(element);
areAllEmailsValid = false;
if (length > 3) {
let scrollableDiv: any = document.querySelector('.add-user__form-container__inputs-group');
this.formError = 'Field is required. Please enter a valid email address';
}
if (scrollableDiv) {
let scrollableDivHeight = scrollableDiv.offsetHeight;
scrollableDiv.scroll(0, -scrollableDivHeight);
}
this.inputs = newInputsArray;
if (length > 3) {
const scrollableDiv: any = document.querySelector('.add-user__form-container__inputs-group');
if (scrollableDiv) {
const scrollableDivHeight = scrollableDiv.offsetHeight;
scrollableDiv.scroll(0, -scrollableDivHeight);
}
}
if (!areAllEmailsValid) {
this.isLoading = false;
return;
}
if (emailArray.includes(this.$store.state.usersModule.email)) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Error during adding project members. You can't add yourself to the project`);
this.isLoading = false;
return;
}
try {
await this.$store.dispatch(PM_ACTIONS.ADD, emailArray);
} catch (err) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Error during adding project members. ${err.message}`);
this.isLoading = false;
return;
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Members successfully added to project!');
this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${error.message}`);
}
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
if (!areAllEmailsValid) {
this.isLoading = false;
return;
}
public addInput(): void {
let inputsLength = this.inputs.length;
if (inputsLength < 10) {
this.inputs.push(new EmailInput());
}
if (emailArray.includes(this.$store.state.usersModule.email)) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Error during adding project members. You can't add yourself to the project`);
this.isLoading = false;
return;
}
public deleteInput(index): void {
if (this.inputs.length === 1) return;
try {
await this.$store.dispatch(PM_ACTIONS.ADD, emailArray);
} catch (err) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Error during adding project members. ${err.message}`);
this.isLoading = false;
this.resetFormErrors(index);
this.$delete(this.inputs, index);
return;
}
public onClose(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Members successfully added to project!');
this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${error.message}`);
}
public get isMaxInputsCount(): boolean {
return this.inputs.length > 9;
}
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
public get isButtonActive(): boolean {
if (this.formError) return false;
this.isLoading = false;
}
let length = this.inputs.length;
for (let i = 0; i < length; i++) {
if (this.inputs[i].value !== '') return true;
}
return false;
}
public get registerPath(): string {
return location.host + RouteConfig.Register.path;
}
private resetFormErrors(index): void {
this.inputs[index].setError(false);
if (!this.hasInputError()) {
this.formError = '';
}
}
private hasInputError(): boolean {
return this.inputs.some((element: EmailInput) => {
return element.error;
});
public addInput(): void {
const inputsLength = this.inputs.length;
if (inputsLength < 10) {
this.inputs.push(new EmailInput());
}
}
public deleteInput(index): void {
if (this.inputs.length === 1) return;
this.resetFormErrors(index);
this.$delete(this.inputs, index);
}
public onClose(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
}
public get isMaxInputsCount(): boolean {
return this.inputs.length > 9;
}
public get isButtonActive(): boolean {
if (this.formError) return false;
const length = this.inputs.length;
for (let i = 0; i < length; i++) {
if (this.inputs[i].value !== '') return true;
}
return false;
}
public get registerPath(): string {
return location.host + RouteConfig.Register.path;
}
private resetFormErrors(index): void {
this.inputs[index].setError(false);
if (!this.hasInputError()) {
this.formError = '';
}
}
private hasInputError(): boolean {
return this.inputs.some((element: EmailInput) => {
return element.error;
});
}
}
</script>
<style scoped lang='scss'>

View File

@ -30,94 +30,95 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { Component, Prop, Vue } from 'vue-property-decorator';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
import Button from '@/components/common/Button.vue';
import HeaderComponent from '@/components/common/HeaderComponent.vue';
import { ProjectMember, ProjectMemberHeaderState } from '@/types/projectMembers';
import AddUserPopup from '@/components/team/AddUserPopup.vue';
import Button from '@/components/common/Button.vue';
import HeaderComponent from '@/components/common/HeaderComponent.vue';
import AddUserPopup from '@/components/team/AddUserPopup.vue';
declare interface ClearSearch {
clearSearch: () => void;
import { ProjectMember, ProjectMemberHeaderState } from '@/types/projectMembers';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
declare interface ClearSearch {
clearSearch: () => void;
}
@Component({
components: {
Button,
HeaderComponent,
AddUserPopup,
}
})
export default class HeaderArea extends Vue {
@Prop({default: ProjectMemberHeaderState.DEFAULT})
private readonly headerState: ProjectMemberHeaderState;
@Prop({default: 0})
public readonly selectedProjectMembersCount: number;
private FIRST_PAGE = 1;
public isDeleteClicked: boolean = false;
public $refs!: {
headerComponent: HeaderComponent & ClearSearch;
};
public get userCountTitle(): string {
if (this.selectedProjectMembersCount === 1) {
return 'user';
}
return 'users';
}
@Component({
components: {
Button,
HeaderComponent,
AddUserPopup,
}
})
export default class HeaderArea extends Vue {
@Prop({default: ProjectMemberHeaderState.DEFAULT})
private readonly headerState: ProjectMemberHeaderState;
@Prop({default: 0})
public readonly selectedProjectMembersCount: number;
public onAddUsersClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
}
private FIRST_PAGE = 1;
public onFirstDeleteClick(): void {
this.isDeleteClicked = true;
}
public isDeleteClicked: boolean = false;
public onClearSelection(): void {
this.$store.dispatch(PM_ACTIONS.CLEAR_SELECTION);
this.isDeleteClicked = false;
public $refs!: {
headerComponent: HeaderComponent & ClearSearch
};
this.$refs.headerComponent.clearSearch();
}
public get userCountTitle(): string {
if (this.selectedProjectMembersCount === 1) {
return 'user';
}
public async onDelete(): Promise<void> {
const projectMemberEmails: string[] = this.$store.getters.selectedProjectMembers.map((member: ProjectMember) => {
return member.user.email;
});
return 'users';
try {
await this.$store.dispatch(PM_ACTIONS.DELETE, projectMemberEmails);
} catch (err) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Error while deleting users from projectMembers. ${err.message}`);
return;
}
public onAddUsersClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Members was successfully removed from project');
this.isDeleteClicked = false;
public onFirstDeleteClick(): void {
this.isDeleteClicked = true;
}
this.$refs.headerComponent.clearSearch();
}
public onClearSelection(): void {
this.$store.dispatch(PM_ACTIONS.CLEAR_SELECTION);
this.isDeleteClicked = false;
this.$refs.headerComponent.clearSearch();
}
public async onDelete(): Promise<void> {
const projectMemberEmails: string[] = this.$store.getters.selectedProjectMembers.map((member: ProjectMember) => {
return member.user.email;
});
try {
await this.$store.dispatch(PM_ACTIONS.DELETE, projectMemberEmails);
} catch (err) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Error while deleting users from projectMembers. ${err.message}`);
return;
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Members was successfully removed from project');
this.isDeleteClicked = false;
this.$refs.headerComponent.clearSearch();
}
public async processSearchQuery(search: string): Promise<void> {
this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, search);
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (err) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${err.message}`);
}
}
public get isAddTeamMembersPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isAddTeamMembersPopupShown;
public async processSearchQuery(search: string): Promise<void> {
this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, search);
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (err) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${err.message}`);
}
}
public get isAddTeamMembersPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isAddTeamMembersPopupShown;
}
}
</script>
<style scoped lang="scss">

View File

@ -17,30 +17,31 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { getColor } from '@/utils/avatarColorManager';
import { ProjectMember } from '@/types/projectMembers';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class ProjectMemberListItem extends Vue {
@Prop({default: new ProjectMember('', '', '', '', '')})
public itemData: ProjectMember;
import { ProjectMember } from '@/types/projectMembers';
import { getColor } from '@/utils/avatarColorManager';
public get avatarData(): object {
let fullName: string = this.itemData.user.getFullName();
@Component
export default class ProjectMemberListItem extends Vue {
@Prop({default: new ProjectMember('', '', '', '', '')})
public itemData: ProjectMember;
const letter = fullName.slice(0, 1).toLocaleUpperCase();
public get avatarData(): object {
const fullName: string = this.itemData.user.getFullName();
const style = {
background: getColor(letter)
};
const letter = fullName.slice(0, 1).toLocaleUpperCase();
return {
letter,
style
};
}
const style = {
background: getColor(letter)
};
return {
letter,
style
};
}
}
</script>
<style scoped lang="scss">

View File

@ -57,88 +57,89 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Component, Vue } from 'vue-property-decorator';
import List from '@/components/common/List.vue';
import Pagination from '@/components/common/Pagination.vue';
import HeaderArea from '@/components/team/HeaderArea.vue';
import ProjectMemberListItem from '@/components/team/ProjectMemberListItem.vue';
import SortingListHeader from '@/components/team/SortingListHeader.vue';
import { ProjectMember, ProjectMemberHeaderState, ProjectMemberOrderBy } from '@/types/projectMembers';
import { SortDirection } from '@/types/common';
import { NOTIFICATION_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
import List from '@/components/common/List.vue';
import Pagination from '@/components/common/Pagination.vue';
import HeaderArea from '@/components/team/HeaderArea.vue';
import ProjectMemberListItem from '@/components/team/ProjectMemberListItem.vue';
import SortingListHeader from '@/components/team/SortingListHeader.vue';
@Component({
components: {
HeaderArea,
List,
Pagination,
SortingListHeader,
}
})
export default class ProjectMembersArea extends Vue {
private FIRST_PAGE = 1;
import { SortDirection } from '@/types/common';
import { ProjectMember, ProjectMemberHeaderState, ProjectMemberOrderBy } from '@/types/projectMembers';
import { NOTIFICATION_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
public async beforeDestroy(): Promise<void> {
await this.$store.dispatch(PM_ACTIONS.CLEAR_SELECTION);
@Component({
components: {
HeaderArea,
List,
Pagination,
SortingListHeader,
}
})
export default class ProjectMembersArea extends Vue {
private FIRST_PAGE = 1;
public async beforeDestroy(): Promise<void> {
await this.$store.dispatch(PM_ACTIONS.CLEAR_SELECTION);
}
public onMemberClick(member: ProjectMember): void {
this.$store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, member.user.id);
}
public get projectMembers(): ProjectMember[] {
return this.$store.state.projectMembersModule.page.projectMembers;
}
public get getItemComponent() {
return ProjectMemberListItem;
}
public get projectMembersTotalCount(): number {
return this.$store.state.projectMembersModule.page.totalCount;
}
public get projectMembersCount(): number {
return this.$store.state.projectMembersModule.page.projectMembers.length;
}
public get totalPageCount(): number {
return this.$store.state.projectMembersModule.page.pageCount;
}
public get selectedProjectMembers(): ProjectMember[] {
return this.$store.getters.selectedProjectMembers;
}
public get headerState(): number {
if (this.selectedProjectMembers.length > 0) {
return ProjectMemberHeaderState.ON_SELECT;
}
public onMemberClick(member: ProjectMember): void {
this.$store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, member.user.id);
}
return ProjectMemberHeaderState.DEFAULT;
}
public get projectMembers(): ProjectMember[] {
return this.$store.state.projectMembersModule.page.projectMembers;
}
public get getItemComponent() {
return ProjectMemberListItem;
}
public get projectMembersTotalCount(): number {
return this.$store.state.projectMembersModule.page.totalCount;
}
public get projectMembersCount(): number {
return this.$store.state.projectMembersModule.page.projectMembers.length;
}
public get totalPageCount(): number {
return this.$store.state.projectMembersModule.page.pageCount;
}
public get selectedProjectMembers(): ProjectMember[] {
return this.$store.getters.selectedProjectMembers;
}
public get headerState(): number {
if (this.selectedProjectMembers.length > 0) {
return ProjectMemberHeaderState.ON_SELECT;
}
return ProjectMemberHeaderState.DEFAULT;
}
public async onPageClick(index: number):Promise<void> {
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, index);
} catch (err) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${err.message}`);
}
}
public async onHeaderSectionClickCallback(sortBy: ProjectMemberOrderBy, sortDirection: SortDirection): Promise<void> {
this.$store.dispatch(PM_ACTIONS.SET_SORT_BY, sortBy);
this.$store.dispatch(PM_ACTIONS.SET_SORT_DIRECTION, sortDirection);
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${error.message}`);
}
(this.$refs.pagination as Pagination).resetPageIndex();
public async onPageClick(index: number):Promise<void> {
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, index);
} catch (err) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${err.message}`);
}
}
public async onHeaderSectionClickCallback(sortBy: ProjectMemberOrderBy, sortDirection: SortDirection): Promise<void> {
this.$store.dispatch(PM_ACTIONS.SET_SORT_BY, sortBy);
this.$store.dispatch(PM_ACTIONS.SET_SORT_DIRECTION, sortDirection);
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${error.message}`);
}
(this.$refs.pagination as Pagination).resetPageIndex();
}
}
</script>
<style scoped lang="scss">

View File

@ -25,56 +25,58 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { OnHeaderClickCallback, ProjectMemberOrderBy } from '@/types/projectMembers';
import { SortDirection } from '@/types/common';
import VerticalArrows from '@/components/common/VerticalArrows.vue';
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component({
components: {
VerticalArrows,
},
})
export default class SortingListHeader extends Vue {
@Prop({default: () => { return new Promise(() => false); }})
private readonly onHeaderClickCallback: OnHeaderClickCallback;
import VerticalArrows from '@/components/common/VerticalArrows.vue';
public ProjectMemberOrderBy = ProjectMemberOrderBy;
import { SortDirection } from '@/types/common';
import { OnHeaderClickCallback, ProjectMemberOrderBy } from '@/types/projectMembers';
public sortBy: ProjectMemberOrderBy = ProjectMemberOrderBy.NAME;
public sortDirection: SortDirection = SortDirection.ASCENDING;
@Component({
components: {
VerticalArrows,
},
})
export default class SortingListHeader extends Vue {
@Prop({default: () => new Promise(() => false)})
private readonly onHeaderClickCallback: OnHeaderClickCallback;
public get getSortDirection() {
if (this.sortDirection === SortDirection.DESCENDING) {
return SortDirection.ASCENDING;
}
public ProjectMemberOrderBy = ProjectMemberOrderBy;
return SortDirection.DESCENDING;
public sortBy: ProjectMemberOrderBy = ProjectMemberOrderBy.NAME;
public sortDirection: SortDirection = SortDirection.ASCENDING;
public get getSortDirection() {
if (this.sortDirection === SortDirection.DESCENDING) {
return SortDirection.ASCENDING;
}
public get getSortBy() {
return this.sortBy;
}
return SortDirection.DESCENDING;
}
public async onHeaderItemClick(sortBy: ProjectMemberOrderBy): Promise<void> {
if (this.sortBy != sortBy) {
this.sortBy = sortBy;
this.sortDirection = SortDirection.ASCENDING;
public get getSortBy() {
return this.sortBy;
}
await this.onHeaderClickCallback(this.sortBy, this.sortDirection);
return;
}
if (this.sortDirection === SortDirection.DESCENDING) {
this.sortDirection = SortDirection.ASCENDING;
} else {
this.sortDirection = SortDirection.DESCENDING;
}
public async onHeaderItemClick(sortBy: ProjectMemberOrderBy): Promise<void> {
if (this.sortBy !== sortBy) {
this.sortBy = sortBy;
this.sortDirection = SortDirection.ASCENDING;
await this.onHeaderClickCallback(this.sortBy, this.sortDirection);
return;
}
if (this.sortDirection === SortDirection.DESCENDING) {
this.sortDirection = SortDirection.ASCENDING;
} else {
this.sortDirection = SortDirection.DESCENDING;
}
await this.onHeaderClickCallback(this.sortBy, this.sortDirection);
}
}
</script>
<style scoped lang="scss">

View File

@ -2,10 +2,11 @@
// See LICENSE for copying information.
import Vue from 'vue';
import App from './App.vue';
import Analytics from './plugins/analytics';
import router from './router';
import store from './store';
import Analytics from './plugins/analytics';
Vue.config.devtools = true;
Vue.config.performance = true;

View File

@ -2,10 +2,12 @@
// See LICENSE for copying information.
import VueSegmentAnalytics from 'vue-segment-analytics';
import { isDoNotTrackEnabled } from '@/utils/doNotTrack';
const Analytics = {
install(Vue, options) {
const Qlwe = 1;
const isDoNotTrack = isDoNotTrackEnabled();
const hasSegmentID = options.id && options.id.length > 0;
@ -13,13 +15,13 @@ const Analytics = {
options.id = 'fake id';
}
if ((isDoNotTrack || !hasSegmentID) && options.router != undefined) {
if ((isDoNotTrack || !hasSegmentID) && options.router !== undefined) {
delete options.router;
}
VueSegmentAnalytics.install(Vue, options);
/* tslint:disable-next-line */
// tslint:disable-next-line
if (isDoNotTrack || !hasSegmentID) {
Vue.$segment.forEach(method => {
Vue.$segment[method] = () => undefined;

View File

@ -2,24 +2,26 @@
// See LICENSE for copying information.
import Vue from 'vue';
import Router, { RouteRecord } from 'vue-router';
import AccountArea from '@/components/account/AccountArea.vue';
import AccountBilling from '@/components/account/billing/BillingArea.vue';
import AccountPaymentMethods from '@/components/account/AccountPaymentMethods.vue';
import AccountBilling from '@/components/account/billing/BillingArea.vue';
import Profile from '@/components/account/Profile.vue';
import ApiKeysArea from '@/components/apiKeys/ApiKeysArea.vue';
import { AuthToken } from '@/utils/authToken';
import BucketArea from '@/components/buckets/BucketArea.vue';
import Page404 from '@/components/errors/Page404.vue';
import ProjectDetails from '@/components/project/ProjectDetails.vue';
import ProjectOverviewArea from '@/components/project/ProjectOverviewArea.vue';
import UsageReport from '@/components/project/UsageReport.vue';
import ProjectMembersArea from '@/components/team/ProjectMembersArea.vue';
import { NavigationLink } from '@/types/navigation';
import { AuthToken } from '@/utils/authToken';
import Dashboard from '@/views/Dashboard.vue';
import ForgotPassword from '@/views/forgotPassword/ForgotPassword.vue';
import Login from '@/views/login/Login.vue';
import Page404 from '@/components/errors/Page404.vue';
import Profile from '@/components/account/Profile.vue';
import ProjectDetails from '@/components/project/ProjectDetails.vue';
import ProjectMembersArea from '@/components/team/ProjectMembersArea.vue';
import ProjectOverviewArea from '@/components/project/ProjectOverviewArea.vue';
import Register from '@/views/register/Register.vue';
import Router, { RouteRecord } from 'vue-router';
import UsageReport from '@/components/project/UsageReport.vue';
import { NavigationLink } from '@/types/navigation';
Vue.use(Router);
@ -177,7 +179,7 @@ router.beforeEach((to, from, next) => {
* @param subTabRoute - default sub route of the tabNavigator
*/
function navigateToFirstSubTab(routes: RouteRecord[], tabRoute: NavigationLink, subTabRoute: NavigationLink): boolean {
return routes.length == 2 && (routes[1].name as string) === tabRoute.name;
return routes.length === 2 && (routes[1].name as string) === tabRoute.name;
}
export default router;

View File

@ -4,23 +4,23 @@
import Vue from 'vue';
import Vuex from 'vuex';
import { makeNotificationsModule } from '@/store/modules/notifications';
import { ApiKeysApiGql } from '@/api/apiKeys';
import { BucketsApiGql } from '@/api/buckets';
import { CreditsApiGql } from '@/api/credits';
import { ProjectMembersApiGql } from '@/api/projectMembers';
import { ProjectsApiGql } from '@/api/projects';
import { ProjectUsageApiGql } from '@/api/usage';
import { UsersApiGql } from '@/api/users';
import { appStateModule } from '@/store/modules/appState';
import { makeApiKeysModule } from '@/store/modules/apiKeys';
import { makeCreditsModule } from '@/store/modules/credits';
import { appStateModule } from '@/store/modules/appState';
import { makeBucketsModule } from '@/store/modules/buckets';
import { makeCreditsModule } from '@/store/modules/credits';
import { makeNotificationsModule } from '@/store/modules/notifications';
import { projectPaymentsMethodsModule } from '@/store/modules/paymentMethods';
import { makeProjectMembersModule } from '@/store/modules/projectMembers';
import { makeProjectsModule } from '@/store/modules/projects';
import { makeUsageModule } from '@/store/modules/usage';
import { makeUsersModule } from '@/store/modules/users';
import { ProjectsApiGql } from '@/api/projects';
import { ProjectUsageApiGql } from '@/api/usage';
Vue.use(Vuex);

View File

@ -1,9 +1,10 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { API_KEYS_MUTATIONS } from '../mutationConstants';
import { ApiKey, ApiKeysApi } from '@/types/apiKeys';
import { StoreModule } from '@/store';
import { ApiKey, ApiKeysApi } from '@/types/apiKeys';
import { API_KEYS_MUTATIONS } from '../mutationConstants';
const {
FETCH,
@ -63,7 +64,7 @@ export function makeApiKeysModule(api: ApiKeysApi): StoreModule<ApiKeysState> {
setAPIKeys: async function ({commit, rootGetters}): Promise<ApiKey[]> {
const projectId = rootGetters.selectedProject.id;
let apiKeys = await api.get(projectId);
const apiKeys = await api.get(projectId);
commit(FETCH, apiKeys);
@ -72,14 +73,14 @@ export function makeApiKeysModule(api: ApiKeysApi): StoreModule<ApiKeysState> {
createAPIKey: async function ({commit, rootGetters}: any, name: string): Promise<ApiKey> {
const projectId = rootGetters.selectedProject.id;
let apiKey = await api.create(projectId, name);
const apiKey = await api.create(projectId, name);
commit(ADD, apiKey);
return apiKey;
},
deleteAPIKey: async function({commit}: any, ids: string[]): Promise<null> {
let result = await api.delete(ids);
const result = await api.delete(ids);
commit(DELETE, ids);
@ -97,8 +98,8 @@ export function makeApiKeysModule(api: ApiKeysApi): StoreModule<ApiKeysState> {
},
getters: {
selectedAPIKeys: function (state: any): ApiKey[] {
let keys: ApiKey[] = state.apiKeys;
let selectedKeys: ApiKey[] = [];
const keys: ApiKey[] = state.apiKeys;
const selectedKeys: ApiKey[] = [];
for (let i = 0; i < keys.length; i++ ) {
if (keys[i].isSelected) {

View File

@ -1,10 +1,11 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { APP_STATE_MUTATIONS } from '../mutationConstants';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { AppState } from '@/utils/constants/appStateEnum';
import { APP_STATE_MUTATIONS } from '../mutationConstants';
export const appStateModule = {
state: {
// Object that contains all states of views

View File

@ -1,8 +1,8 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { Bucket, BucketCursor, BucketPage, BucketsApi } from '@/types/buckets';
import { StoreModule } from '@/store';
import { Bucket, BucketCursor, BucketPage, BucketsApi } from '@/types/buckets';
export const BUCKET_ACTIONS = {
FETCH: 'setBuckets',
@ -66,7 +66,7 @@ export function makeBucketsModule(api: BucketsApi): StoreModule<BucketsState> {
commit(SET_PAGE, page);
let result = await api.get(projectID, before, state.cursor);
const result = await api.get(projectID, before, state.cursor);
commit(SET, result);

View File

@ -36,7 +36,7 @@ export function makeCreditsModule(api: CreditsApi): StoreModule<CreditUsage> {
actions: {
[FETCH]: async function({commit}: any): Promise<CreditUsage> {
let credits = await api.get();
const credits = await api.get();
commit(SET, credits);

View File

@ -1,11 +1,12 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { NOTIFICATION_MUTATIONS } from '../mutationConstants';
import { NOTIFICATION_TYPES } from '@/utils/constants/notification';
import { StoreModule } from '@/store';
import { DelayedNotification } from '@/types/DelayedNotification';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { StoreModule } from '@/store';
import { NOTIFICATION_TYPES } from '@/utils/constants/notification';
import { NOTIFICATION_MUTATIONS } from '../mutationConstants';
class NotificationsState {
public notificationQueue: DelayedNotification[] = [];

View File

@ -1,15 +1,15 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { PROJECT_PAYMENT_METHODS_MUTATIONS } from '@/store/mutationConstants';
import { PROJECT_PAYMENT_METHODS_ACTIONS } from '@/utils/constants/actionNames';
import {
addProjectPaymentMethodRequest,
deletePaymentMethodRequest,
fetchProjectPaymentMethods,
setDefaultPaymentMethodRequest
} from '@/api/paymentMethods';
import { PROJECT_PAYMENT_METHODS_MUTATIONS } from '@/store/mutationConstants';
import { RequestResponse } from '@/types/response';
import { PROJECT_PAYMENT_METHODS_ACTIONS } from '@/utils/constants/actionNames';
export const projectPaymentsMethodsModule = {
state: {
@ -26,7 +26,7 @@ export const projectPaymentsMethodsModule = {
actions: {
[PROJECT_PAYMENT_METHODS_ACTIONS.ADD]: async function ({commit, rootGetters, state}, input: AddPaymentMethodInput): Promise<RequestResponse<null>> {
const projectID = rootGetters.selectedProject.id;
if (state.paymentMethods.length == 0) {
if (state.paymentMethods.length === 0) {
input.makeDefault = true;
}
@ -35,7 +35,7 @@ export const projectPaymentsMethodsModule = {
[PROJECT_PAYMENT_METHODS_ACTIONS.FETCH]: async function ({commit, rootGetters}): Promise<RequestResponse<PaymentMethod[]>> {
const projectId = rootGetters.selectedProject.id;
let result = await fetchProjectPaymentMethods(projectId);
const result = await fetchProjectPaymentMethods(projectId);
if (result.isSuccess) {
commit(PROJECT_PAYMENT_METHODS_MUTATIONS.FETCH, result.data);
}

View File

@ -1,6 +1,8 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { StoreModule } from '@/store';
import { SortDirection } from '@/types/common';
import {
ProjectMember,
ProjectMemberCursor,
@ -8,8 +10,6 @@ import {
ProjectMembersApi,
ProjectMembersPage,
} from '@/types/projectMembers';
import { SortDirection } from '@/types/common';
import { StoreModule } from '@/store';
export const PROJECT_MEMBER_MUTATIONS = {
FETCH: 'fetchProjectMembers',

View File

@ -1,8 +1,8 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { CreateProjectModel, Project, ProjectsApi, UpdateProjectModel } from '@/types/projects';
import { StoreModule } from '@/store';
import { CreateProjectModel, Project, ProjectsApi, UpdateProjectModel } from '@/types/projects';
export const PROJECTS_ACTIONS = {
FETCH: 'fetchProjects',
@ -61,10 +61,10 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule<ProjectsState>
return;
}
let projectsCount = state.projects.length;
const projectsCount = state.projects.length;
for (let i = 0; i < projectsCount; i++) {
let project = state.projects[i];
const project = state.projects[i];
if (project.id !== state.selectedProject.id) {
continue;
@ -108,14 +108,14 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule<ProjectsState>
},
actions: {
[FETCH]: async function ({commit}: any): Promise<Project[]> {
let projects = await api.get();
const projects = await api.get();
commit(SET_PROJECTS, projects);
return projects;
},
[CREATE]: async function ({commit}: any, createProjectModel: CreateProjectModel): Promise<Project> {
let project = await api.create(createProjectModel);
const project = await api.create(createProjectModel);
commit(ADD, project);

View File

@ -3,7 +3,7 @@
import { ProjectUsageApiGql } from '@/api/usage';
import { StoreModule } from '@/store';
import { ProjectUsage, DateRange } from '@/types/usage';
import { DateRange, ProjectUsage } from '@/types/usage';
export const PROJECT_USAGE_ACTIONS = {
FETCH: 'fetchProjectUsage',
@ -47,7 +47,7 @@ export function makeUsageModule(api: ProjectUsageApiGql): StoreModule<UsageState
[PROJECT_USAGE_ACTIONS.FETCH]: async function({commit, rootGetters}: any, dateRange: DateRange): Promise<ProjectUsage> {
const projectID = rootGetters.selectedProject.id;
let usage: ProjectUsage = await api.get(projectID, dateRange.startDate, dateRange.endDate);
const usage: ProjectUsage = await api.get(projectID, dateRange.startDate, dateRange.endDate);
commit(PROJECT_USAGE_MUTATIONS.SET_DATE, dateRange);
commit(PROJECT_USAGE_MUTATIONS.SET_PROJECT_USAGE, usage);
@ -61,7 +61,7 @@ export function makeUsageModule(api: ProjectUsageApiGql): StoreModule<UsageState
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), 1));
const dateRange = new DateRange(startDate, endDate);
let usage: ProjectUsage = await api.get(projectID, dateRange.startDate, dateRange.endDate);
const usage: ProjectUsage = await api.get(projectID, dateRange.startDate, dateRange.endDate);
commit(PROJECT_USAGE_MUTATIONS.SET_DATE, dateRange);
commit(PROJECT_USAGE_MUTATIONS.SET_PROJECT_USAGE, usage);
@ -76,7 +76,7 @@ export function makeUsageModule(api: ProjectUsageApiGql): StoreModule<UsageState
const endDate = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 0, 23, 59, 59));
const dateRange = new DateRange(startDate, endDate);
let usage: ProjectUsage = await api.get(projectID, dateRange.startDate, dateRange.endDate);
const usage: ProjectUsage = await api.get(projectID, dateRange.startDate, dateRange.endDate);
commit(PROJECT_USAGE_MUTATIONS.SET_DATE, dateRange);
commit(PROJECT_USAGE_MUTATIONS.SET_PROJECT_USAGE, usage);

View File

@ -1,8 +1,8 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { UpdatedUser, User, UsersApi } from '@/types/users';
import { StoreModule } from '@/store';
import { UpdatedUser, User, UsersApi } from '@/types/users';
export const USER_ACTIONS = {
UPDATE: 'updateUser',
@ -66,7 +66,7 @@ export function makeUsersModule(api: UsersApi): StoreModule<User> {
commit(UPDATE_USER, userInfo);
},
[GET]: async function ({commit}: any): Promise<User> {
let user = await api.get();
const user = await api.get();
commit(SET_USER, user);

View File

@ -1,8 +1,8 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { getId } from '@/utils/idGenerator';
import { NOTIFICATION_IMAGES, NOTIFICATION_TYPES } from '@/utils/constants/notification';
import { getId } from '@/utils/idGenerator';
export class DelayedNotification {
private readonly successColor: string = '#DBF1D3';

View File

@ -48,7 +48,7 @@ export class ApiKey {
}
public formattedName(): string {
let name = this.name;
const name = this.name;
if (name.length < 12) {
return name;

View File

@ -3,17 +3,17 @@
// PaymentMethod holds card information to display
declare type PaymentMethod = {
id: string,
expYear: number,
expMonth: number,
brand: string,
lastFour: string,
holderName: string,
addedAt: Date,
isDefault: boolean,
id: string;
expYear: number;
expMonth: number;
brand: string;
lastFour: string;
holderName: string;
addedAt: Date;
isDefault: boolean;
};
declare type AddPaymentMethodInput = {
token: string,
makeDefault: boolean,
token: string;
makeDefault: boolean;
};

View File

@ -2,8 +2,8 @@
// See LICENSE for copying information.
// ProjectMember stores needed info about user info to show it on UI
import { User } from '@/types/users';
import { SortDirection } from '@/types/common';
import { User } from '@/types/users';
export type OnHeaderClickCallback = (sortBy: ProjectMemberOrderBy, sortDirection: SortDirection) => Promise<void>;
@ -125,4 +125,3 @@ export class ProjectMember {
return new Date(this.joinedAt).toLocaleDateString();
}
}

View File

@ -2,15 +2,15 @@
// See LICENSE for copying information.
declare type RegisterData = {
firstName: string,
firstNameError: string,
lastName: string,
email: string,
emailError: string,
password: string,
passwordError: string,
repeatedPassword: string,
repeatedPasswordError: string,
isTermsAccepted: boolean,
isTermsAcceptedError: boolean,
firstName: string;
firstNameError: string;
lastName: string;
email: string;
emailError: string;
password: string;
passwordError: string;
repeatedPassword: string;
repeatedPasswordError: string;
isTermsAccepted: boolean;
isTermsAcceptedError: boolean;
};

View File

@ -2,13 +2,13 @@
// See LICENSE for copying information.
declare type Reward = {
id: number,
awardCreditInCent?: number,
inviteeCreditInCents: number,
redeemableCap: number,
awardCreditDurationDays?: number,
inviteeCreditDurationDays: number,
type: number,
status: number,
expiresAt: string,
id: number;
awardCreditInCent?: number;
inviteeCreditInCents: number;
redeemableCap: number;
awardCreditDurationDays?: number;
inviteeCreditDurationDays: number;
type: number;
status: number;
expiresAt: string;
};

View File

@ -1,10 +1,11 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { HttpLink } from 'apollo-link-http';
import ApolloClient from 'apollo-client/ApolloClient';
import { InMemoryCache } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client/ApolloClient';
import { setContext } from 'apollo-link-context';
import { HttpLink } from 'apollo-link-http';
import { AuthToken } from '@/utils/authToken';
// Satellite url

View File

@ -23,9 +23,9 @@ export class AuthToken {
}
private static getCookie(cname: string): string {
let name: string = cname + '=';
let decodedCookie: string = decodeURIComponent(document.cookie);
let ca: string[] = decodedCookie.split(';');
const name: string = cname + '=';
const decodedCookie: string = decodeURIComponent(document.cookie);
const ca: string[] = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];

View File

@ -8,5 +8,5 @@ export function validateEmail(email: string): boolean {
}
export function validatePassword(password: string): boolean {
return typeof password != 'undefined' && password.length >= 6;
return typeof password !== 'undefined' && password.length >= 6;
}

View File

@ -19,113 +19,115 @@
</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 {
API_KEYS_ACTIONS,
APP_STATE_ACTIONS,
NOTIFICATION_ACTIONS,
PM_ACTIONS,
PROJECT_PAYMENT_METHODS_ACTIONS,
} from '@/utils/constants/actionNames';
import { USER_ACTIONS } from '@/store/modules/users';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { AppState } from '@/utils/constants/appStateEnum';
import { AuthToken } from '@/utils/authToken';
import { Project } from '@/types/projects';
import { RouteConfig } from '@/router';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
NavigationArea,
DashboardHeader,
}
})
export default class Dashboard extends Vue {
public async mounted(): Promise<void> {
setTimeout(async () => {
// TODO: combine all project related requests in one
try {
await this.$store.dispatch(USER_ACTIONS.GET);
} catch (error) {
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.ERROR);
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
await this.$router.push(RouteConfig.Login.path);
AuthToken.remove();
import DashboardHeader from '@/components/header/Header.vue';
import NavigationArea from '@/components/navigation/NavigationArea.vue';
return;
}
import { RouteConfig } from '@/router';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { USER_ACTIONS } from '@/store/modules/users';
import { Project } from '@/types/projects';
import { AuthToken } from '@/utils/authToken';
import {
API_KEYS_ACTIONS,
APP_STATE_ACTIONS,
NOTIFICATION_ACTIONS,
PM_ACTIONS,
PROJECT_PAYMENT_METHODS_ACTIONS,
} from '@/utils/constants/actionNames';
import { AppState } from '@/utils/constants/appStateEnum';
let projects: Project[] = [];
try {
projects = await this.$store.dispatch(PROJECTS_ACTIONS.FETCH);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
return;
}
if (!projects.length) {
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED_EMPTY);
if (!this.isCurrentRouteIsAccount) {
await this.$router.push(RouteConfig.ProjectOverview.path);
return;
}
await this.$router.push(RouteConfig.ProjectOverview.path);
}
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, projects[0].id);
await this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, 1);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${error.message}`);
}
try {
await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch api keys');
}
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${error.message}`);
}
try {
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, 1);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + error.message);
}
const paymentMethodsResponse = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.FETCH);
if (!paymentMethodsResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch payment methods: ' + paymentMethodsResponse.errorMessage);
}
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED);
}, 800);
}
public get isLoading(): boolean {
return this.$store.state.appStateModule.appState.fetchState === AppState.LOADING;
}
public get isCurrentRouteIsAccount(): boolean {
const segments = this.$route.path.split('/').map(segment => segment.toLowerCase());
return segments.includes(RouteConfig.Account.name.toLowerCase());
}
@Component({
components: {
NavigationArea,
DashboardHeader,
}
})
export default class Dashboard extends Vue {
public mounted(): void {
setTimeout(async () => {
// TODO: combine all project related requests in one
try {
await this.$store.dispatch(USER_ACTIONS.GET);
} catch (error) {
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.ERROR);
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
await this.$router.push(RouteConfig.Login.path);
AuthToken.remove();
return;
}
let projects: Project[] = [];
try {
projects = await this.$store.dispatch(PROJECTS_ACTIONS.FETCH);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
return;
}
if (!projects.length) {
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED_EMPTY);
if (!this.isCurrentRouteIsAccount) {
await this.$router.push(RouteConfig.ProjectOverview.path);
return;
}
await this.$router.push(RouteConfig.ProjectOverview.path);
}
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, projects[0].id);
await this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
try {
await this.$store.dispatch(PM_ACTIONS.FETCH, 1);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${error.message}`);
}
try {
await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch api keys');
}
try {
await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
} catch (error) {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project usage. ${error.message}`);
}
try {
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, 1);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + error.message);
}
const paymentMethodsResponse = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.FETCH);
if (!paymentMethodsResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch payment methods: ' + paymentMethodsResponse.errorMessage);
}
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED);
}, 800);
}
public get isLoading(): boolean {
return this.$store.state.appStateModule.appState.fetchState === AppState.LOADING;
}
public get isCurrentRouteIsAccount(): boolean {
const segments = this.$route.path.split('/').map(segment => segment.toLowerCase());
return segments.includes(RouteConfig.Account.name.toLowerCase());
}
}
</script>
<style scoped lang="scss">

View File

@ -4,67 +4,69 @@
<template src="./forgotPassword.html"></template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
import { LOADING_CLASSES } from '@/utils/constants/classConstants';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { RouteConfig } from '@/router';
import { validateEmail } from '@/utils/validation';
import EVENTS from '@/utils/constants/analyticsEventNames';
import { AuthApi } from '@/api/auth';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
HeaderlessInput,
},
})
export default class ForgotPassword extends Vue {
public loadingClassName: string = LOADING_CLASSES.LOADING_OVERLAY;
private email: string = '';
private emailError: string = '';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
private readonly auth: AuthApi = new AuthApi();
import { AuthApi } from '@/api/auth';
import { RouteConfig } from '@/router';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import EVENTS from '@/utils/constants/analyticsEventNames';
import { LOADING_CLASSES } from '@/utils/constants/classConstants';
import { validateEmail } from '@/utils/validation';
public setEmail(value: string): void {
this.email = value;
this.emailError = '';
@Component({
components: {
HeaderlessInput,
},
})
export default class ForgotPassword extends Vue {
public loadingClassName: string = LOADING_CLASSES.LOADING_OVERLAY;
private email: string = '';
private emailError: string = '';
private readonly auth: AuthApi = new AuthApi();
public setEmail(value: string): void {
this.email = value;
this.emailError = '';
}
public async onSendConfigurations(): Promise<void> {
const self = this;
if (!self.validateFields()) {
return;
}
public async onSendConfigurations(): Promise<void> {
let self = this;
if (!self.validateFields()) {
return;
}
try {
await this.auth.forgotPassword(this.email);
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Please look for instructions at your email');
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
}
}
public onBackToLoginClick(): void {
this.$segment.track(EVENTS.CLICKED_BACK_TO_LOGIN);
this.$router.push(RouteConfig.Login.path);
}
public onLogoClick(): void {
this.$segment.track(EVENTS.CLICKED_LOGO);
location.reload();
}
private validateFields(): boolean {
const isEmailValid = validateEmail(this.email.trim());
if (!isEmailValid) {
this.emailError = 'Invalid Email';
}
return isEmailValid;
try {
await this.auth.forgotPassword(this.email);
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Please look for instructions at your email');
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
}
}
public onBackToLoginClick(): void {
this.$segment.track(EVENTS.CLICKED_BACK_TO_LOGIN);
this.$router.push(RouteConfig.Login.path);
}
public onLogoClick(): void {
this.$segment.track(EVENTS.CLICKED_LOGO);
location.reload();
}
private validateFields(): boolean {
const isEmailValid = validateEmail(this.email.trim());
if (!isEmailValid) {
this.emailError = 'Invalid Email';
}
return isEmailValid;
}
}
</script>
<style src="./forgotPassword.scss" scoped lang="scss"></style>

View File

@ -4,101 +4,103 @@
<template src="./login.html"></template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
import Button from '@/components/common/Button.vue';
import { AuthToken } from '@/utils/authToken';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { AuthApi } from '@/api/auth';
import { LOADING_CLASSES } from '@/utils/constants/classConstants';
import { AppState } from '@/utils/constants/appStateEnum';
import { validateEmail, validatePassword } from '@/utils/validation';
import EVENTS from '@/utils/constants/analyticsEventNames';
import { RouteConfig } from '@/router';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
HeaderlessInput,
Button
}
})
export default class Login extends Vue {
private email: string = '';
private password: string = '';
private authToken: string = '';
import Button from '@/components/common/Button.vue';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
private readonly forgotPasswordPath: string = RouteConfig.ForgotPassword.path;
private loadingClassName: string = LOADING_CLASSES.LOADING_OVERLAY;
private loadingLogoClassName: string = LOADING_CLASSES.LOADING_LOGO;
private emailError: string = '';
private passwordError: string = '';
import { AuthApi } from '@/api/auth';
import { RouteConfig } from '@/router';
import { AuthToken } from '@/utils/authToken';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import EVENTS from '@/utils/constants/analyticsEventNames';
import { AppState } from '@/utils/constants/appStateEnum';
import { LOADING_CLASSES } from '@/utils/constants/classConstants';
import { validateEmail, validatePassword } from '@/utils/validation';
private readonly auth: AuthApi = new AuthApi();
public onLogoClick(): void {
location.reload();
}
public setEmail(value: string): void {
this.email = value;
this.emailError = '';
}
public setPassword(value: string): void {
this.password = value;
this.passwordError = '';
}
public onSignUpClick(): void {
this.$router.push(RouteConfig.Register.path);
}
public async onLogin(): Promise<void> {
let self = this;
this.$segment.track(EVENTS.CLICKED_LOGIN);
if (!self.validateFields()) {
return;
}
try {
this.authToken = await this.auth.token(this.email, this.password);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
return;
}
this.activateLoadingOverlay();
setTimeout(() => {
AuthToken.set(this.authToken);
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADING);
this.$router.push(RouteConfig.ProjectOverview.with(RouteConfig.ProjectDetails).path);
}, 2000);
}
private validateFields(): boolean {
let isNoErrors = true;
if (!validateEmail(this.email.trim())) {
this.emailError = 'Invalid Email';
isNoErrors = false;
}
if (!validatePassword(this.password)) {
this.passwordError = 'Invalid Password';
isNoErrors = false;
}
return isNoErrors;
}
private activateLoadingOverlay(): void {
this.loadingClassName = LOADING_CLASSES.LOADING_OVERLAY_ACTIVE;
this.loadingLogoClassName = LOADING_CLASSES.LOADING_LOGO_ACTIVE;
}
@Component({
components: {
HeaderlessInput,
Button
}
})
export default class Login extends Vue {
private email: string = '';
private password: string = '';
private authToken: string = '';
private readonly forgotPasswordPath: string = RouteConfig.ForgotPassword.path;
private loadingClassName: string = LOADING_CLASSES.LOADING_OVERLAY;
private loadingLogoClassName: string = LOADING_CLASSES.LOADING_LOGO;
private emailError: string = '';
private passwordError: string = '';
private readonly auth: AuthApi = new AuthApi();
public onLogoClick(): void {
location.reload();
}
public setEmail(value: string): void {
this.email = value;
this.emailError = '';
}
public setPassword(value: string): void {
this.password = value;
this.passwordError = '';
}
public onSignUpClick(): void {
this.$router.push(RouteConfig.Register.path);
}
public async onLogin(): Promise<void> {
const self = this;
this.$segment.track(EVENTS.CLICKED_LOGIN);
if (!self.validateFields()) {
return;
}
try {
this.authToken = await this.auth.token(this.email, this.password);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
return;
}
this.activateLoadingOverlay();
setTimeout(() => {
AuthToken.set(this.authToken);
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADING);
this.$router.push(RouteConfig.ProjectOverview.with(RouteConfig.ProjectDetails).path);
}, 2000);
}
private validateFields(): boolean {
let isNoErrors = true;
if (!validateEmail(this.email.trim())) {
this.emailError = 'Invalid Email';
isNoErrors = false;
}
if (!validatePassword(this.password)) {
this.passwordError = 'Invalid Password';
isNoErrors = false;
}
return isNoErrors;
}
private activateLoadingOverlay(): void {
this.loadingClassName = LOADING_CLASSES.LOADING_OVERLAY_ACTIVE;
this.loadingLogoClassName = LOADING_CLASSES.LOADING_LOGO_ACTIVE;
}
}
</script>
<style src="./login.scss" scoped lang="scss"></style>

View File

@ -4,157 +4,159 @@
<template src="./register.html"></template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
import RegistrationSuccessPopup from '@/components/common/RegistrationSuccessPopup.vue';
import { validateEmail, validatePassword } from '@/utils/validation';
import { RouteConfig } from '@/router';
import EVENTS from '@/utils/constants/analyticsEventNames';
import { LOADING_CLASSES } from '@/utils/constants/classConstants';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { AuthApi } from '@/api/auth';
import { setUserId } from '@/utils/consoleLocalStorage';
import { User } from '@/types/users';
import InfoComponent from '@/components/common/InfoComponent.vue';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
HeaderlessInput,
RegistrationSuccessPopup,
InfoComponent,
},
})
export default class Register extends Vue {
private readonly user = new User();
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
import InfoComponent from '@/components/common/InfoComponent.vue';
import RegistrationSuccessPopup from '@/components/common/RegistrationSuccessPopup.vue';
// tardigrade logic
private secret: string = '';
private refUserId: string = '';
import { AuthApi } from '@/api/auth';
import { RouteConfig } from '@/router';
import { User } from '@/types/users';
import { setUserId } from '@/utils/consoleLocalStorage';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import EVENTS from '@/utils/constants/analyticsEventNames';
import { LOADING_CLASSES } from '@/utils/constants/classConstants';
import { validateEmail, validatePassword } from '@/utils/validation';
private userId: string = '';
private isTermsAccepted: boolean = false;
private password: string = '';
private repeatedPassword: string = '';
@Component({
components: {
HeaderlessInput,
RegistrationSuccessPopup,
InfoComponent,
},
})
export default class Register extends Vue {
private readonly user = new User();
private fullNameError: string = '';
private emailError: string = '';
private passwordError: string = '';
private repeatedPasswordError: string = '';
private isTermsAcceptedError: boolean = false;
// tardigrade logic
private secret: string = '';
private refUserId: string = '';
private loadingClassName: string = LOADING_CLASSES.LOADING_OVERLAY;
private userId: string = '';
private isTermsAccepted: boolean = false;
private password: string = '';
private repeatedPassword: string = '';
private readonly auth: AuthApi = new AuthApi();
private fullNameError: string = '';
private emailError: string = '';
private passwordError: string = '';
private repeatedPasswordError: string = '';
private isTermsAcceptedError: boolean = false;
mounted(): void {
if (this.$route.query.token) {
this.secret = this.$route.query.token.toString();
}
private loadingClassName: string = LOADING_CLASSES.LOADING_OVERLAY;
let { ids = '' } = this.$route.params;
let decoded = '';
try {
decoded = atob(ids);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Invalid Referral URL');
this.loadingClassName = LOADING_CLASSES.LOADING_OVERLAY;
private readonly auth: AuthApi = new AuthApi();
return;
}
let referralIds = ids ? JSON.parse(decoded) : undefined;
if (referralIds) {
this.user.partnerId = referralIds.partnerId;
this.refUserId = referralIds.userId;
}
mounted(): void {
if (this.$route.query.token) {
this.secret = this.$route.query.token.toString();
}
public onCreateClick(): void {
if (!this.validateFields()) {
return;
}
this.loadingClassName = LOADING_CLASSES.LOADING_OVERLAY_ACTIVE;
this.createUser();
const { ids = '' } = this.$route.params;
let decoded = '';
try {
decoded = atob(ids);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Invalid Referral URL');
this.loadingClassName = LOADING_CLASSES.LOADING_OVERLAY;
return;
}
public onLogoClick(): void {
this.$segment.track(EVENTS.CLICKED_LOGO);
location.reload();
}
public onLoginClick(): void {
this.$segment.track(EVENTS.CLICKED_LOGIN);
this.$router.push(RouteConfig.Login.path);
}
public setEmail(value: string): void {
this.user.email = value.trim();
this.emailError = '';
}
public setFullName(value: string): void {
this.user.fullName = value.trim();
this.fullNameError = '';
}
public setShortName(value: string): void {
this.user.shortName = value.trim();
}
public setPassword(value: string): void {
this.password = value;
this.passwordError = '';
}
public setRepeatedPassword(value: string): void {
this.repeatedPassword = value;
this.repeatedPasswordError = '';
}
private validateFields(): boolean {
let isNoErrors = true;
if (!this.user.fullName.trim()) {
this.fullNameError = 'Invalid Name';
isNoErrors = false;
}
if (!validateEmail(this.user.email.trim())) {
this.emailError = 'Invalid Email';
isNoErrors = false;
}
if (!validatePassword(this.password)) {
this.passwordError = 'Invalid Password';
isNoErrors = false;
}
if (this.repeatedPassword !== this.password) {
this.repeatedPasswordError = 'Password doesn\'t match';
isNoErrors = false;
}
if (!this.isTermsAccepted) {
this.isTermsAcceptedError = true;
isNoErrors = false;
}
return isNoErrors;
}
private async createUser(): Promise<void> {
try {
this.userId = await this.auth.create(this.user, this.password , this.secret, this.refUserId);
setUserId(this.userId);
// TODO: improve it
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_REGISTRATION_POPUP);
if (this.$refs['register_success_popup'] !== null) {
(this.$refs['register_success_popup'] as any).startResendEmailCountdown();
}
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
this.loadingClassName = LOADING_CLASSES.LOADING_OVERLAY;
}
const referralIds = ids ? JSON.parse(decoded) : undefined;
if (referralIds) {
this.user.partnerId = referralIds.partnerId;
this.refUserId = referralIds.userId;
}
}
public onCreateClick(): void {
if (!this.validateFields()) {
return;
}
this.loadingClassName = LOADING_CLASSES.LOADING_OVERLAY_ACTIVE;
this.createUser();
this.loadingClassName = LOADING_CLASSES.LOADING_OVERLAY;
}
public onLogoClick(): void {
this.$segment.track(EVENTS.CLICKED_LOGO);
location.reload();
}
public onLoginClick(): void {
this.$segment.track(EVENTS.CLICKED_LOGIN);
this.$router.push(RouteConfig.Login.path);
}
public setEmail(value: string): void {
this.user.email = value.trim();
this.emailError = '';
}
public setFullName(value: string): void {
this.user.fullName = value.trim();
this.fullNameError = '';
}
public setShortName(value: string): void {
this.user.shortName = value.trim();
}
public setPassword(value: string): void {
this.password = value;
this.passwordError = '';
}
public setRepeatedPassword(value: string): void {
this.repeatedPassword = value;
this.repeatedPasswordError = '';
}
private validateFields(): boolean {
let isNoErrors = true;
if (!this.user.fullName.trim()) {
this.fullNameError = 'Invalid Name';
isNoErrors = false;
}
if (!validateEmail(this.user.email.trim())) {
this.emailError = 'Invalid Email';
isNoErrors = false;
}
if (!validatePassword(this.password)) {
this.passwordError = 'Invalid Password';
isNoErrors = false;
}
if (this.repeatedPassword !== this.password) {
this.repeatedPasswordError = 'Password doesn\'t match';
isNoErrors = false;
}
if (!this.isTermsAccepted) {
this.isTermsAcceptedError = true;
isNoErrors = false;
}
return isNoErrors;
}
private async createUser(): Promise<void> {
try {
this.userId = await this.auth.create(this.user, this.password , this.secret, this.refUserId);
setUserId(this.userId);
// TODO: improve it
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_REGISTRATION_POPUP);
if (this.$refs['register_success_popup'] !== null) {
(this.$refs['register_success_popup'] as any).startResendEmailCountdown();
}
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
this.loadingClassName = LOADING_CLASSES.LOADING_OVERLAY;
}
}
}
</script>
<style src="./register.scss" scoped lang="scss"></style>

View File

@ -1,14 +1,16 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
import ApiKeysArea from '@/components/apiKeys/ApiKeysArea.vue';
import { ApiKey } from '@/types/apiKeys';
import { ApiKeysApiGql } from '@/api/apiKeys';
import { makeApiKeysModule } from '@/store/modules/apiKeys';
import { makeNotificationsModule } from '@/store/modules/notifications';
import { API_KEYS_MUTATIONS } from '@/store/mutationConstants';
import { ApiKeysApiGql } from '@/api/apiKeys';
import { ApiKey } from '@/types/apiKeys';
import { createLocalVue, mount } from '@vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
@ -19,8 +21,8 @@ const ADD = API_KEYS_MUTATIONS.ADD;
const store = new Vuex.Store({ modules: { apiKeysModule, notificationsModule }});
describe('ApiKeysArea', () => {
let apiKey = new ApiKey('testId', 'test', 'test', 'test');
let apiKey1 = new ApiKey('testId1', 'test1', 'test1', 'test1');
const apiKey = new ApiKey('testId', 'test', 'test', 'test');
const apiKey1 = new ApiKey('testId1', 'test1', 'test1', 'test1');
it('renders correctly', () => {
const wrapper = mount(ApiKeysArea, {
@ -164,7 +166,7 @@ describe('ApiKeysArea', () => {
localVue,
});
let testSecret = 'testSecret';
const testSecret = 'testSecret';
wrapper.vm.showCopyApiKeyPopup(testSecret);
@ -183,7 +185,7 @@ describe('ApiKeysArea', () => {
expect(wrapper.vm.$data.isCopyApiKeyPopupShown).toBe(false);
});
it('action on onDelete with name works correctly', async () => {
it('action on onDelete with name works correctly', () => {
const wrapper = mount(ApiKeysArea, {
store,
localVue,

View File

@ -1,12 +1,14 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
import ApiKeysCopyPopup from '@/components/apiKeys/ApiKeysCopyPopup.vue';
import { ApiKeysApiGql } from '@/api/apiKeys';
import { makeApiKeysModule } from '@/store/modules/apiKeys';
import { makeNotificationsModule } from '@/store/modules/notifications';
import { ApiKeysApiGql } from '@/api/apiKeys';
import { createLocalVue, mount } from '@vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
@ -26,7 +28,6 @@ describe('ApiKeysCopyPopup', () => {
expect(wrapper).toMatchSnapshot();
});
it('function onCloseClick works correctly', () => {
const wrapper = mount(ApiKeysCopyPopup, {
store,

View File

@ -1,17 +1,19 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
import ApiKeysCreationPopup from '@/components/apiKeys/ApiKeysCreationPopup.vue';
import { ApiKey } from '@/types/apiKeys';
import { ApiKeysApiGql } from '@/api/apiKeys';
import { ProjectsApiGql } from '@/api/projects';
import { makeApiKeysModule } from '@/store/modules/apiKeys';
import { makeNotificationsModule } from '@/store/modules/notifications';
import { makeProjectsModule } from '@/store/modules/projects';
import { API_KEYS_ACTIONS } from '@/utils/constants/actionNames';
import { ApiKey } from '@/types/apiKeys';
import { Project } from '@/types/projects';
import { ApiKeysApiGql } from '@/api/apiKeys';
import { ProjectsApiGql } from '@/api/projects';
import { API_KEYS_ACTIONS } from '@/utils/constants/actionNames';
import { createLocalVue, mount } from '@vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
@ -30,7 +32,7 @@ const CREATE = API_KEYS_ACTIONS.CREATE;
const store = new Vuex.Store({ modules: { projectsModule, apiKeysModule, notificationsModule }});
describe('ApiKeysCreationPopup', () => {
let value = 'testValue';
const value = 'testValue';
it('renders correctly', () => {
const wrapper = mount(ApiKeysCreationPopup, {
@ -80,7 +82,7 @@ describe('ApiKeysCreationPopup', () => {
});
it('action on onNextClick with name works correctly', async () => {
let testApiKey = new ApiKey('testId', 'testName', 'testCreatedAt', 'test');
const testApiKey = new ApiKey('testId', 'testName', 'testCreatedAt', 'test');
jest.spyOn(apiKeysApi, 'create').mockReturnValue(
Promise.resolve(testApiKey));
@ -95,7 +97,7 @@ describe('ApiKeysCreationPopup', () => {
wrapper.vm.onNextClick();
let result = await store.dispatch(CREATE, 'testName');
const result = await store.dispatch(CREATE, 'testName');
expect(wrapper.vm.$data.key).toBe(result.secret);
expect(wrapper.vm.$data.isLoading).toBe(false);

View File

@ -1,9 +1,10 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { mount } from '@vue/test-utils';
import ApiKeysItem from '@/components/apiKeys/ApiKeysItem.vue';
import { mount } from '@vue/test-utils';
describe('ApiKeysItem.vue', () => {
it('renders correctly', () => {
const wrapper = mount(ApiKeysItem);

View File

@ -1,12 +1,13 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { mount, shallowMount } from '@vue/test-utils';
import Button from '@/components/common/Button.vue';
import * as sinon from 'sinon';
describe('Button.vue', () => {
import Button from '@/components/common/Button.vue';
import { mount, shallowMount } from '@vue/test-utils';
describe('Button.vue', () => {
it('renders correctly', () => {
const wrapper = shallowMount(Button, {
propsData: {
@ -40,9 +41,9 @@ describe('Button.vue', () => {
});
it('renders correctly with size and label props', () => {
let label = 'testLabel';
let width = '30px';
let height = '20px';
const label = 'testLabel';
const width = '30px';
const height = '20px';
const wrapper = shallowMount(Button, {
propsData: {
@ -71,7 +72,7 @@ describe('Button.vue', () => {
});
it('trigger onPress correctly', () => {
let onPressSpy = sinon.spy();
const onPressSpy = sinon.spy();
const wrapper = mount(Button, {
propsData: {

View File

@ -1,11 +1,11 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { mount, shallowMount } from '@vue/test-utils';
import Checkbox from '@/components/common/Checkbox.vue';
describe('Checkbox.vue', () => {
import { mount, shallowMount } from '@vue/test-utils';
describe('Checkbox.vue', () => {
it('renders correctly', () => {
const wrapper = shallowMount(Checkbox);

View File

@ -1,10 +1,12 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { mount, shallowMount } from '@vue/test-utils';
import * as sinon from 'sinon';
import HeaderComponent from '@/components/common/HeaderComponent.vue';
import { mount, shallowMount } from '@vue/test-utils';
describe('HeaderComponent.vue', () => {
it('renders correctly', () => {
const wrapper = shallowMount(HeaderComponent);
@ -20,7 +22,7 @@ describe('HeaderComponent.vue', () => {
});
it('function clearSearch works correctly', () => {
let clearSearchSpy = sinon.spy();
const clearSearchSpy = sinon.spy();
const wrapper = mount(HeaderComponent);

View File

@ -1,11 +1,11 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { mount, shallowMount } from '@vue/test-utils';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
describe('HeaderedInput.vue', () => {
import { mount, shallowMount } from '@vue/test-utils';
describe('HeaderedInput.vue', () => {
it('renders correctly with default props', () => {
const wrapper = shallowMount(HeaderedInput);
@ -25,10 +25,10 @@ describe('HeaderedInput.vue', () => {
});
it('renders correctly with props', () => {
let label = 'testLabel';
let additionalLabel = 'addLabel';
let width = '30px';
let height = '20px';
const label = 'testLabel';
const additionalLabel = 'addLabel';
const width = '30px';
const height = '20px';
const wrapper = shallowMount(HeaderedInput, {
propsData: {label, width, height, additionalLabel}
@ -52,7 +52,7 @@ describe('HeaderedInput.vue', () => {
});
it('renders correctly with input error', () => {
let error = 'testError';
const error = 'testError';
const wrapper = shallowMount(HeaderedInput, {
propsData: {
@ -65,7 +65,7 @@ describe('HeaderedInput.vue', () => {
});
it('emit setData on input correctly', () => {
let testData = 'testData';
const testData = 'testData';
const wrapper = mount(HeaderedInput);

View File

@ -1,11 +1,11 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { mount, shallowMount } from '@vue/test-utils';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
describe('HeaderlessInput.vue', () => {
import { mount, shallowMount } from '@vue/test-utils';
describe('HeaderlessInput.vue', () => {
it('renders correctly with default props', () => {
const wrapper = shallowMount(HeaderlessInput);
@ -14,9 +14,9 @@ describe('HeaderlessInput.vue', () => {
});
it('renders correctly with size props', () => {
let placeholder = 'test';
let width = '30px';
let height = '20px';
const placeholder = 'test';
const width = '30px';
const height = '20px';
const wrapper = shallowMount(HeaderlessInput, {
propsData: {placeholder, width, height}
@ -36,7 +36,7 @@ describe('HeaderlessInput.vue', () => {
});
it('emit setData on input correctly', () => {
let testData = 'testData';
const testData = 'testData';
const wrapper = mount(HeaderlessInput);

View File

@ -1,10 +1,12 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { mount, shallowMount } from '@vue/test-utils';
import * as sinon from 'sinon';
import PagesBlock from '@/components/common/PagesBlock.vue';
import { Page } from '@/types/pagination';
import { mount, shallowMount } from '@vue/test-utils';
describe('Pagination.vue', () => {
it('renders correctly without props', () => {
@ -34,9 +36,9 @@ describe('Pagination.vue', () => {
expect(wrapper.findAll('span').at(2).classes().includes('selected')).toBe(true);
});
it('behaves correctly on page click', async () => {
it('behaves correctly on page click', () => {
const callbackSpy = sinon.spy();
let pagesArray: Page[] = [];
const pagesArray: Page[] = [];
for (let i = 1; i <= 3; i++) {
pagesArray.push(new Page(i, callbackSpy));

View File

@ -1,10 +1,12 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { mount, shallowMount } from '@vue/test-utils';
import * as sinon from 'sinon';
import Pagination from '@/components/common/Pagination.vue';
import { mount, shallowMount } from '@vue/test-utils';
describe('Pagination.vue', () => {
it('renders correctly', () => {
const wrapper = shallowMount(Pagination);

View File

@ -1,10 +1,12 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { mount, shallowMount } from '@vue/test-utils';
import * as sinon from 'sinon';
import SearchComponent from '@/components/common/SearchComponent.vue';
import { mount, shallowMount } from '@vue/test-utils';
describe('SearchComponent.vue', () => {
it('renders correctly', () => {
const wrapper = shallowMount(SearchComponent);
@ -33,7 +35,7 @@ describe('SearchComponent.vue', () => {
});
it('function clearSearch works correctly', () => {
let processSearchQuerySpy = sinon.spy();
const processSearchQuerySpy = sinon.spy();
const wrapper = mount(SearchComponent, {
methods: {

View File

@ -1,10 +1,12 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { mount } from '@vue/test-utils';
import TestList from '@/components/common/test/TestList.vue';
import sinon from 'sinon';
import TestList from '@/components/common/test/TestList.vue';
import { mount } from '@vue/test-utils';
describe('TestList.vue', () => {
it('should render list of primitive types', function () {
const wrapper = mount(TestList, {
@ -16,7 +18,7 @@ describe('TestList.vue', () => {
});
it('should retrieve callback', function () {
let onPressSpy = sinon.spy();
const onPressSpy = sinon.spy();
const wrapper = mount(TestList, {
propsData: {

Some files were not shown because too many files have changed in this diff Show More