satellite/{web, console}: remove old access grant and billing flows

Removed old flows along with feature flags.

Issue: https://github.com/storj/storj/issues/5694

Change-Id: Ib3a1cbb12435500bdc2c540bb67615c64ca19e5e
This commit is contained in:
Vitalii 2023-03-23 16:14:26 +02:00 committed by Vitalii Shpital
parent 8668e0c716
commit e4d9f8686d
113 changed files with 107 additions and 6924 deletions

View File

@ -34,8 +34,6 @@ type FrontendConfig struct {
AllProjectsDashboard bool `json:"allProjectsDashboard"`
DefaultPaidStorageLimit memory.Size `json:"defaultPaidStorageLimit"`
DefaultPaidBandwidthLimit memory.Size `json:"defaultPaidBandwidthLimit"`
NewBillingScreen bool `json:"newBillingScreen"`
NewAccessGrantFlow bool `json:"newAccessGrantFlow"`
InactivityTimerEnabled bool `json:"inactivityTimerEnabled"`
InactivityTimerDuration int `json:"inactivityTimerDuration"`
InactivityTimerViewerEnabled bool `json:"inactivityTimerViewerEnabled"`

View File

@ -92,8 +92,6 @@ type Config struct {
LinksharingURL string `help:"url link for linksharing requests" default:"https://link.storjshare.io" devDefault:"http://localhost:8001"`
PathwayOverviewEnabled bool `help:"indicates if the overview onboarding step should render with pathways" default:"true"`
AllProjectsDashboard bool `help:"indicates if all projects dashboard should be used" default:"false"`
NewBillingScreen bool `help:"indicates if new billing screens should be used" default:"true"`
NewAccessGrantFlow bool `help:"indicates if new access grant flow should be used" default:"true"`
GeneratedAPIEnabled bool `help:"indicates if generated console api should be used" default:"false"`
OptionalSignupSuccessURL string `help:"optional url to external registration success page" default:""`
HomepageURL string `help:"url link to storj.io homepage" default:"https://www.storj.io"`
@ -473,8 +471,6 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
AllProjectsDashboard bool
DefaultPaidStorageLimit memory.Size
DefaultPaidBandwidthLimit memory.Size
NewBillingScreen bool
NewAccessGrantFlow bool
InactivityTimerEnabled bool
InactivityTimerDuration int
InactivityTimerViewerEnabled bool
@ -516,7 +512,6 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
data.LoginHcaptchaEnabled = server.config.Captcha.Login.Hcaptcha.Enabled
data.LoginHcaptchaSiteKey = server.config.Captcha.Login.Hcaptcha.SiteKey
data.AllProjectsDashboard = server.config.AllProjectsDashboard
data.NewBillingScreen = server.config.NewBillingScreen
data.InactivityTimerEnabled = server.config.Session.InactivityTimerEnabled
data.InactivityTimerDuration = server.config.Session.InactivityTimerDuration
data.InactivityTimerViewerEnabled = server.config.Session.InactivityTimerViewerEnabled
@ -526,7 +521,6 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
data.PasswordMinimumLength = console.PasswordMinimumLength
data.PasswordMaximumLength = console.PasswordMaximumLength
data.ABTestingEnabled = server.config.ABTesting.Enabled
data.NewAccessGrantFlow = server.config.NewAccessGrantFlow
data.PricingPackagesEnabled = server.config.PricingPackagesEnabled
templates, err := server.loadTemplates()
@ -685,7 +679,6 @@ func (server *Server) frontendConfigHandler(w http.ResponseWriter, r *http.Reque
DefaultPaidBandwidthLimit: server.config.UsageLimits.Bandwidth.Paid,
Captcha: server.config.Captcha,
AllProjectsDashboard: server.config.AllProjectsDashboard,
NewBillingScreen: server.config.NewBillingScreen,
InactivityTimerEnabled: server.config.Session.InactivityTimerEnabled,
InactivityTimerDuration: server.config.Session.InactivityTimerDuration,
InactivityTimerViewerEnabled: server.config.Session.InactivityTimerViewerEnabled,
@ -695,7 +688,6 @@ func (server *Server) frontendConfigHandler(w http.ResponseWriter, r *http.Reque
PasswordMinimumLength: console.PasswordMinimumLength,
PasswordMaximumLength: console.PasswordMaximumLength,
ABTestingEnabled: server.config.ABTesting.Enabled,
NewAccessGrantFlow: server.config.NewAccessGrantFlow,
}
err := json.NewEncoder(w).Encode(&cfg)

View File

@ -262,12 +262,6 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
# indicates if storj native token payments system is enabled
# console.native-token-payments-enabled: false
# indicates if new access grant flow should be used
# console.new-access-grant-flow: true
# indicates if new billing screens should be used
# console.new-billing-screen: true
# how long oauth access tokens are issued for
# console.oauth-access-token-expiry: 24h0m0s

View File

@ -31,8 +31,6 @@
<meta name="all-projects-dashboard" content="{{ .AllProjectsDashboard }}">
<meta name="default-paid-storage-limit" content="{{ .DefaultPaidStorageLimit }}">
<meta name="default-paid-bandwidth-limit" content="{{ .DefaultPaidBandwidthLimit }}">
<meta name="new-billing-screen" content="{{ .NewBillingScreen }}">
<meta name="new-access-grant-flow" content="{{ .NewAccessGrantFlow }}">
<meta name="inactivity-timer-enabled" content="{{ .InactivityTimerEnabled }}">
<meta name="inactivity-timer-duration" content="{{ .InactivityTimerDuration }}">
<meta name="inactivity-timer-viewer-enabled" content="{{ .InactivityTimerViewerEnabled }}">

View File

@ -48,7 +48,6 @@ export default class App extends Vue {
const isBetaSatellite = MetaUtils.getMetaContent('is-beta-satellite');
const couponCodeBillingUIEnabled = MetaUtils.getMetaContent('coupon-code-billing-ui-enabled');
const couponCodeSignupUIEnabled = MetaUtils.getMetaContent('coupon-code-signup-ui-enabled');
const isNewAccessGrantFlow = MetaUtils.getMetaContent('new-access-grant-flow');
if (satelliteName) {
this.$store.dispatch(APP_STATE_ACTIONS.SET_SATELLITE_NAME, satelliteName);
@ -77,10 +76,6 @@ export default class App extends Vue {
this.$store.dispatch(APP_STATE_ACTIONS.SET_COUPON_CODE_SIGNUP_UI_STATUS, couponCodeSignupUIEnabled === 'true');
}
if (isNewAccessGrantFlow) {
this.$store.commit(APP_STATE_MUTATIONS.SET_ACCESS_GRANT_FLOW_STATUS, isNewAccessGrantFlow === 'true');
}
this.fixViewportHeight();
}

View File

@ -245,13 +245,6 @@ const emptyStateLabel = computed((): string => {
return searchQuery.value ? noSearchResults : noGrants;
});
/**
* Indicates if new access grant flow should be used.
*/
const isNewAccessGrantFlow = computed((): boolean => {
return store.state.appStateModule.isNewAccessGrantFlow;
});
/**
* Fetches access grants page by clicked index.
* @param index
@ -311,20 +304,10 @@ async function fetch(searchQuery: string): Promise<void> {
*/
function accessGrantClick(): void {
analytics.eventTriggered(AnalyticsEvent.CREATE_ACCESS_GRANT_CLICKED);
if (isNewAccessGrantFlow.value) {
trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.NewCreateAccessModal).path);
router.push({
name: RouteConfig.NewCreateAccessModal.name,
params: { accessType: AccessType.AccessGrant },
});
return;
}
trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).path);
router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).name,
params: { accessType: 'access' },
name: RouteConfig.CreateAccessModal.name,
params: { accessType: AccessType.AccessGrant },
});
}
@ -333,20 +316,10 @@ function accessGrantClick(): void {
*/
function s3Click(): void {
analytics.eventTriggered(AnalyticsEvent.CREATE_S3_CREDENTIALS_CLICKED);
if (isNewAccessGrantFlow.value) {
trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.NewCreateAccessModal).path);
router.push({
name: RouteConfig.NewCreateAccessModal.name,
params: { accessType: AccessType.S3 },
});
return;
}
trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).path);
router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).name,
params: { accessType: 's3' },
name: RouteConfig.CreateAccessModal.name,
params: { accessType: AccessType.S3 },
});
}
@ -355,20 +328,10 @@ function s3Click(): void {
*/
function cliClick(): void {
analytics.eventTriggered(AnalyticsEvent.CREATE_KEYS_FOR_CLI_CLICKED);
if (isNewAccessGrantFlow.value) {
trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.NewCreateAccessModal).path);
router.push({
name: RouteConfig.NewCreateAccessModal.name,
params: { accessType: AccessType.APIKey },
});
return;
}
trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).path);
router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).name,
params: { accessType: 'api' },
name: RouteConfig.CreateAccessModal.name,
params: { accessType: AccessType.APIKey },
});
}

View File

@ -1,317 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="onCloseClick">
<template #content>
<CreateForm
v-if="isCreateStep"
:checked-type="checkedType"
@encrypt="encryptStep"
@propagateInfo="createAccessGrantHelper"
/>
<EncryptionInfoForm
v-if="isEncryptInfoStep"
@back="onBackFromEncryptionInfo"
@continue="onContinueFromEncryptionInfo"
/>
<EncryptForm
v-if="isEncryptStep"
@apply-passphrase="applyPassphrase"
@create-access="createAccessGrant"
@backAction="backAction"
/>
<GrantCreatedForm
v-if="isGrantCreatedStep"
:checked-type="checkedType"
:restricted-key="restrictedKey"
:access="access"
:access-name="accessName"
/>
</template>
</VModal>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { AccessGrant } from '@/types/accessGrants';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { MetaUtils } from '@/utils/meta';
import { RouteConfig } from '@/router';
import { LocalData } from '@/utils/localData';
import VModal from '@/components/common/VModal.vue';
import EncryptionInfoForm from '@/components/accessGrants/modals/EncryptionInfoForm.vue';
import GrantCreatedForm from '@/components/accessGrants/modals/GrantCreatedForm.vue';
import EncryptForm from '@/components/accessGrants/modals/EncryptForm.vue';
import CreateForm from '@/components/accessGrants/modals/CreateForm.vue';
// TODO: a lot of code can be refactored/reused/split into modules
// @vue/component
@Component({
components: {
VModal,
CreateForm,
EncryptForm,
GrantCreatedForm,
EncryptionInfoForm,
},
})
export default class CreateAccessModal extends Vue {
@Prop({ default: 'Default' })
private readonly label: string;
@Prop({ default: 'Default' })
private readonly defaultType: string;
public areBucketNamesFetching = true;
public areKeysVisible = false;
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
private readonly FIRST_PAGE = 1;
/**
* Stores access type that is selected and text changes based on type.
*/
private checkedType = '';
/**
* Global isLoading Variable
**/
private isLoading = false;
/**
* Handles permission types, which have been selected, and determining if all have been selected.
*/
private selectedPermissions : string[] = [];
/**
* Handles business logic for options on each step after create access.
*/
private passphrase = '';
private accessName = '';
private accessGrantStep = 'create';
/**
* Created Access Grant
*/
private access = '';
private worker: Worker;
private restrictedKey = '';
public satelliteAddress: string = MetaUtils.getMetaContent('satellite-nodeurl');
public beforeMount(): void {
this.checkedType = this.$route.params.accessType;
}
/**
* Checks which type was selected and retrieves buckets on mount.
*/
public async mounted(): Promise<void> {
this.setWorker();
try {
await this.$store.dispatch(BUCKET_ACTIONS.FETCH_ALL_BUCKET_NAMES);
this.areBucketNamesFetching = false;
} catch (error) {
await this.$notify.error(`Unable to fetch all bucket names. ${error.message}`, AnalyticsErrorEventSource.CREATE_AG_MODAL);
}
}
public encryptStep(): void {
if (!LocalData.getServerSideEncryptionModalHidden() && this.checkedType.includes('s3')) {
this.accessGrantStep = 'encryptInfo';
return;
}
this.accessGrantStep = 'encrypt';
}
public applyPassphrase(passphrase: string) {
this.passphrase = passphrase;
}
public onBackFromEncryptionInfo() {
this.accessGrantStep = 'create';
}
public onContinueFromEncryptionInfo() {
this.accessGrantStep = 'encrypt';
}
/**
* Sets local worker with worker instantiated in store.
* Also sets worker's onmessage and onerror logic.
*/
public setWorker(): void {
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker;
this.worker.onerror = (error: ErrorEvent) => {
this.$notify.error(error.message, AnalyticsErrorEventSource.CREATE_AG_MODAL);
};
}
/**
* Grabs data from child for createAccessGrant
*/
public async createAccessGrantHelper(data, type): Promise<void> {
this.checkedType = data.checkedType;
this.accessName = data.accessName;
this.selectedPermissions = data.selectedPermissions;
if (type === 'api') {
await this.createAccessGrant();
}
}
/**
* Creates Access Grant
*/
public async createAccessGrant(): Promise<void> {
if (this.$store.getters.projects.length === 0) {
try {
await this.$store.dispatch(PROJECTS_ACTIONS.CREATE_DEFAULT_PROJECT);
} catch (error) {
this.$notify.error(error.message, AnalyticsErrorEventSource.CREATE_AG_MODAL);
this.isLoading = false;
return;
}
}
// creates restricted key
let cleanAPIKey: AccessGrant;
try {
cleanAPIKey = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, this.accessName);
} catch (error) {
await this.$notify.error(error.message, AnalyticsErrorEventSource.CREATE_AG_MODAL);
return;
}
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
await this.$notify.error(`Unable to fetch Access Grants. ${error.message}`, AnalyticsErrorEventSource.CREATE_AG_MODAL);
this.isLoading = false;
}
let permissionsMsg = {
'type': 'SetPermission',
'buckets': this.selectedBucketNames,
'apiKey': cleanAPIKey.secret,
'isDownload': this.selectedPermissions.includes('Read'),
'isUpload': this.selectedPermissions.includes('Write'),
'isList': this.selectedPermissions.includes('List'),
'isDelete': this.selectedPermissions.includes('Delete'),
};
if (this.notBeforePermission) permissionsMsg = Object.assign(permissionsMsg, { 'notBefore': this.notBeforePermission.toISOString() });
if (this.notAfterPermission) permissionsMsg = Object.assign(permissionsMsg, { 'notAfter': this.notAfterPermission.toISOString() });
await this.worker.postMessage(permissionsMsg);
const grantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
if (grantEvent.data.error) {
await this.$notify.error(grantEvent.data.error, AnalyticsErrorEventSource.CREATE_AG_MODAL);
this.isLoading = false;
return;
}
this.restrictedKey = grantEvent.data.value;
// creates access credentials
const satelliteNodeURL = MetaUtils.getMetaContent('satellite-nodeurl');
const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id);
this.worker.postMessage({
'type': 'GenerateAccess',
'apiKey': this.restrictedKey,
'passphrase': this.passphrase,
'salt': salt,
'satelliteNodeURL': satelliteNodeURL,
});
const accessEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
if (accessEvent.data.error) {
await this.$notify.error(accessEvent.data.error, AnalyticsErrorEventSource.CREATE_AG_MODAL);
this.isLoading = false;
return;
}
this.access = accessEvent.data.value;
await this.$notify.success('Access Grant was generated successfully');
if (this.checkedType === 's3' || (this.checkedType.includes('s3') && this.checkedType.includes('access'))) {
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, { accessGrant: this.access });
await this.analytics.eventTriggered(AnalyticsEvent.GATEWAY_CREDENTIALS_CREATED);
await this.$notify.success('Gateway credentials were generated successfully');
this.areKeysVisible = true;
} catch (error) {
await this.$notify.error(error.message, AnalyticsErrorEventSource.CREATE_AG_MODAL);
}
} else {
await this.analytics.eventTriggered(AnalyticsEvent.API_ACCESS_CREATED);
}
this.analytics.eventTriggered(AnalyticsEvent.ACCESS_GRANT_CREATED);
this.accessGrantStep = 'grantCreated';
}
public backAction(): void {
this.accessGrantStep = 'create';
this.passphrase = '';
}
/**
* Closes modal.
*/
public onCloseClick(): void {
this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR_SELECTION);
this.$router.push(RouteConfig.AccessGrants.path);
}
/**
* Retrieves selected buckets for bucket bullets.
*/
public get selectedBucketNames(): string[] {
return this.$store.state.accessGrantsModule.selectedBucketNames;
}
/**
* Returns not before date permission from store.
*/
private get notBeforePermission(): Date | null {
return this.$store.state.accessGrantsModule.permissionNotBefore;
}
/**
* Returns not after date permission from store.
*/
private get notAfterPermission(): Date | null {
return this.$store.state.accessGrantsModule.permissionNotAfter;
}
/**
* Returns which step should be rendered.
*/
public get isCreateStep(): boolean {
return this.accessGrantStep === 'create';
}
public get isEncryptInfoStep(): boolean {
return this.accessGrantStep === 'encryptInfo';
}
public get isEncryptStep(): boolean {
return this.accessGrantStep === 'encrypt';
}
public get isGrantCreatedStep(): boolean {
return this.accessGrantStep === 'grantCreated';
}
}
</script>

View File

@ -130,16 +130,16 @@ import { AnalyticsHttpApi } from '@/api/analytics';
import { OBJECTS_MUTATIONS } from '@/store/modules/objects';
import VModal from '@/components/common/VModal.vue';
import CreateNewAccessStep from '@/components/accessGrants/newCreateFlow/steps/CreateNewAccessStep.vue';
import ChoosePermissionStep from '@/components/accessGrants/newCreateFlow/steps/ChoosePermissionStep.vue';
import AccessEncryptionStep from '@/components/accessGrants/newCreateFlow/steps/AccessEncryptionStep.vue';
import EnterPassphraseStep from '@/components/accessGrants/newCreateFlow/steps/EnterPassphraseStep.vue';
import PassphraseGeneratedStep from '@/components/accessGrants/newCreateFlow/steps/PassphraseGeneratedStep.vue';
import EncryptionInfoStep from '@/components/accessGrants/newCreateFlow/steps/EncryptionInfoStep.vue';
import AccessCreatedStep from '@/components/accessGrants/newCreateFlow/steps/AccessCreatedStep.vue';
import CLIAccessCreatedStep from '@/components/accessGrants/newCreateFlow/steps/CLIAccessCreatedStep.vue';
import S3CredentialsCreatedStep from '@/components/accessGrants/newCreateFlow/steps/S3CredentialsCreatedStep.vue';
import ConfirmDetailsStep from '@/components/accessGrants/newCreateFlow/steps/ConfirmDetailsStep.vue';
import CreateNewAccessStep from '@/components/accessGrants/createFlow/steps/CreateNewAccessStep.vue';
import ChoosePermissionStep from '@/components/accessGrants/createFlow/steps/ChoosePermissionStep.vue';
import AccessEncryptionStep from '@/components/accessGrants/createFlow/steps/AccessEncryptionStep.vue';
import EnterPassphraseStep from '@/components/accessGrants/createFlow/steps/EnterPassphraseStep.vue';
import PassphraseGeneratedStep from '@/components/accessGrants/createFlow/steps/PassphraseGeneratedStep.vue';
import EncryptionInfoStep from '@/components/accessGrants/createFlow/steps/EncryptionInfoStep.vue';
import AccessCreatedStep from '@/components/accessGrants/createFlow/steps/AccessCreatedStep.vue';
import CLIAccessCreatedStep from '@/components/accessGrants/createFlow/steps/CLIAccessCreatedStep.vue';
import S3CredentialsCreatedStep from '@/components/accessGrants/createFlow/steps/S3CredentialsCreatedStep.vue';
import ConfirmDetailsStep from '@/components/accessGrants/createFlow/steps/ConfirmDetailsStep.vue';
const router = useRouter();
const route = useRoute();
@ -167,7 +167,7 @@ const storedPassphrase = computed((): string => {
return store.state.objectsModule.passphrase;
});
const worker = ref<Worker>();
const worker = ref<Worker| null>(null);
const isLoading = ref<boolean>(false);
const step = ref<CreateAccessStep>(CreateAccessStep.CreateNewAccess);
const selectedAccessTypes = ref<AccessType[]>([]);

View File

@ -72,9 +72,9 @@ import { AccessType } from '@/types/createAccessGrant';
import { RouteConfig } from '@/router';
import VButton from '@/components/common/VButton.vue';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import ValueWithBlur from '@/components/accessGrants/newCreateFlow/components/ValueWithBlur.vue';
import LinkButton from '@/components/accessGrants/newCreateFlow/components/LinkButton.vue';
import ButtonsContainer from '@/components/accessGrants/createFlow/components/ButtonsContainer.vue';
import ValueWithBlur from '@/components/accessGrants/createFlow/components/ValueWithBlur.vue';
import LinkButton from '@/components/accessGrants/createFlow/components/LinkButton.vue';
const props = defineProps<{
accessTypes: AccessType[];

View File

@ -102,9 +102,9 @@ import {
} from '@/types/createAccessGrant';
import { useStore } from '@/utils/hooks';
import ContainerWithIcon from '@/components/accessGrants/newCreateFlow/components/ContainerWithIcon.vue';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import Radio from '@/components/accessGrants/newCreateFlow/components/Radio.vue';
import ContainerWithIcon from '@/components/accessGrants/createFlow/components/ContainerWithIcon.vue';
import ButtonsContainer from '@/components/accessGrants/createFlow/components/ButtonsContainer.vue';
import Radio from '@/components/accessGrants/createFlow/components/Radio.vue';
import VButton from '@/components/common/VButton.vue';
import ChevronIcon from '@/../static/images/accessGrants/newCreateFlow/chevron.svg';

View File

@ -78,9 +78,9 @@ import { RouteConfig } from '@/router';
import { MetaUtils } from '@/utils/meta';
import VButton from '@/components/common/VButton.vue';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import ValueWithBlur from '@/components/accessGrants/newCreateFlow/components/ValueWithBlur.vue';
import LinkButton from '@/components/accessGrants/newCreateFlow/components/LinkButton.vue';
import ButtonsContainer from '@/components/accessGrants/createFlow/components/ButtonsContainer.vue';
import ValueWithBlur from '@/components/accessGrants/createFlow/components/ValueWithBlur.vue';
import LinkButton from '@/components/accessGrants/createFlow/components/LinkButton.vue';
const props = defineProps<{
name: string;

View File

@ -138,10 +138,10 @@ import {
import { useNotify, useStore } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import ContainerWithIcon from '@/components/accessGrants/newCreateFlow/components/ContainerWithIcon.vue';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import EndDateSelection from '@/components/accessGrants/newCreateFlow/components/EndDateSelection.vue';
import Toggle from '@/components/accessGrants/newCreateFlow/components/Toggle.vue';
import ContainerWithIcon from '@/components/accessGrants/createFlow/components/ContainerWithIcon.vue';
import ButtonsContainer from '@/components/accessGrants/createFlow/components/ButtonsContainer.vue';
import EndDateSelection from '@/components/accessGrants/createFlow/components/EndDateSelection.vue';
import Toggle from '@/components/accessGrants/createFlow/components/Toggle.vue';
import VButton from '@/components/common/VButton.vue';
import SearchIcon from '@/../static/images/accessGrants/newCreateFlow/search.svg';

View File

@ -62,8 +62,8 @@ import {
Permission,
} from '@/types/createAccessGrant';
import ContainerWithIcon from '@/components/accessGrants/newCreateFlow/components/ContainerWithIcon.vue';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import ContainerWithIcon from '@/components/accessGrants/createFlow/components/ContainerWithIcon.vue';
import ButtonsContainer from '@/components/accessGrants/createFlow/components/ButtonsContainer.vue';
import VButton from '@/components/common/VButton.vue';
const props = defineProps<{

View File

@ -104,10 +104,10 @@ import { computed } from 'vue';
import { AccessType, FUNCTIONAL_CONTAINER_ICON_AND_TITLE, FunctionalContainer } from '@/types/createAccessGrant';
import { AnalyticsHttpApi } from '@/api/analytics';
import ContainerWithIcon from '@/components/accessGrants/newCreateFlow/components/ContainerWithIcon.vue';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import LinkButton from '@/components/accessGrants/newCreateFlow/components/LinkButton.vue';
import Toggle from '@/components/accessGrants/newCreateFlow/components/Toggle.vue';
import ContainerWithIcon from '@/components/accessGrants/createFlow/components/ContainerWithIcon.vue';
import ButtonsContainer from '@/components/accessGrants/createFlow/components/ButtonsContainer.vue';
import LinkButton from '@/components/accessGrants/createFlow/components/LinkButton.vue';
import Toggle from '@/components/accessGrants/createFlow/components/Toggle.vue';
import VInput from '@/components/common/VInput.vue';
import VButton from '@/components/common/VButton.vue';

View File

@ -49,8 +49,8 @@ import { useStore } from '@/utils/hooks';
import { LocalData } from '@/utils/localData';
import { AnalyticsHttpApi } from '@/api/analytics';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import Toggle from '@/components/accessGrants/newCreateFlow/components/Toggle.vue';
import ButtonsContainer from '@/components/accessGrants/createFlow/components/ButtonsContainer.vue';
import Toggle from '@/components/accessGrants/createFlow/components/Toggle.vue';
import VButton from '@/components/common/VButton.vue';
const props = defineProps<{

View File

@ -50,8 +50,8 @@ import { computed, ref } from 'vue';
import { useStore } from '@/utils/hooks';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import Toggle from '@/components/accessGrants/newCreateFlow/components/Toggle.vue';
import ButtonsContainer from '@/components/accessGrants/createFlow/components/ButtonsContainer.vue';
import Toggle from '@/components/accessGrants/createFlow/components/Toggle.vue';
import VButton from '@/components/common/VButton.vue';
import VInput from '@/components/common/VInput.vue';

View File

@ -80,9 +80,9 @@ import { Download } from '@/utils/download';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { AnalyticsHttpApi } from '@/api/analytics';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import ValueWithBlur from '@/components/accessGrants/newCreateFlow/components/ValueWithBlur.vue';
import Toggle from '@/components/accessGrants/newCreateFlow/components/Toggle.vue';
import ButtonsContainer from '@/components/accessGrants/createFlow/components/ButtonsContainer.vue';
import ValueWithBlur from '@/components/accessGrants/createFlow/components/ValueWithBlur.vue';
import Toggle from '@/components/accessGrants/createFlow/components/Toggle.vue';
import VButton from '@/components/common/VButton.vue';
const props = defineProps<{

View File

@ -85,9 +85,9 @@ import { RouteConfig } from '@/router';
import { EdgeCredentials } from '@/types/accessGrants';
import VButton from '@/components/common/VButton.vue';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import ValueWithBlur from '@/components/accessGrants/newCreateFlow/components/ValueWithBlur.vue';
import LinkButton from '@/components/accessGrants/newCreateFlow/components/LinkButton.vue';
import ButtonsContainer from '@/components/accessGrants/createFlow/components/ButtonsContainer.vue';
import ValueWithBlur from '@/components/accessGrants/createFlow/components/ValueWithBlur.vue';
import LinkButton from '@/components/accessGrants/createFlow/components/LinkButton.vue';
const props = defineProps<{
name: string;

View File

@ -1,702 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="create-access">
<h2 class="create-access__title">Create Access</h2>
<div class="create-access__fragment">
<TypesIcon />
<div class="create-access__fragment__wrap">
<p class="create-access__fragment__wrap__label">Type</p>
<div class="create-access__fragment__wrap__type-container">
<label class="checkmark-container">
<input
id="access-grant-check"
type="checkbox"
:checked="getIsChecked('access')"
@change="event => checkChanged(event, 'access')"
>
<span class="checkmark" />
</label>
<label for="access-grant-check">
Access Grant
</label>
<img
class="tooltip-icon"
src="/static/static/images/accessGrants/create-access_information.png"
alt="tooltip icon"
@mouseover="toggleTooltipHover('access','over')"
@mouseleave="toggleTooltipHover('access','leave')"
>
<div
v-if="tooltipHover === 'access'"
class="access-tooltip"
@mouseover="toggleTooltipHover('access','over')"
@mouseleave="toggleTooltipHover('access','leave')"
>
<span class="tooltip-text">Keys to upload, delete, and view your project's data. <a class="tooltip-link" href="https://docs.storj.io/dcs/concepts/access/access-grants" target="_blank" rel="noreferrer noopener" @click="trackPageVisit('https://docs.storj.io/dcs/concepts/access/access-grants')">Learn More</a></span>
</div>
</div>
<div class="create-access__fragment__wrap__type-container">
<label class="checkmark-container">
<input
id="s3-check"
type="checkbox"
:checked="getIsChecked('s3')"
@change="event => checkChanged(event, 's3')"
>
<span class="checkmark" />
</label>
<label for="s3-check">
S3 Credentials
</label>
<img
class="tooltip-icon"
src="/static/static/images/accessGrants/create-access_information.png"
alt="tooltip icon"
@mouseover="toggleTooltipHover('s3','over')"
@mouseleave="toggleTooltipHover('s3','leave')"
>
<div
v-if="tooltipHover === 's3'"
class="s3-tooltip"
@mouseover="toggleTooltipHover('s3','over')"
@mouseleave="toggleTooltipHover('s3','leave')"
>
<span class="tooltip-text">Generates access key, secret key, and endpoint to use in your S3-supporting application. <a class="tooltip-link" href="https://docs.storj.io/dcs/api-reference/s3-compatible-gateway" target="_blank" rel="noreferrer noopener" @click="trackPageVisit('https://docs.storj.io/dcs/api-reference/s3-compatible-gateway')">Learn More</a></span>
</div>
</div>
<div class="create-access__fragment__wrap__type-container">
<label class="checkmark-container">
<input
id="api-check"
type="checkbox"
:checked="getIsChecked('api')"
@change="event => checkChanged(event, 'api')"
>
<span class="checkmark" />
</label>
<label for="api-check">
API Access
</label>
<img
class="tooltip-icon"
src="/static/static/images/accessGrants/create-access_information.png"
alt="tooltip icon"
@mouseover="toggleTooltipHover('api','over')"
@mouseleave="toggleTooltipHover('api','leave')"
>
<div
v-if="tooltipHover === 'api'"
class="api-tooltip"
@mouseover="toggleTooltipHover('api','over')"
@mouseleave="toggleTooltipHover('api','leave')"
>
<span class="tooltip-text">Creates access grant to run in the command line. <a class="tooltip-link" href="https://docs.storj.io/dcs/getting-started/quickstart-uplink-cli/generate-access-grants-and-tokens/generate-a-token" target="_blank" rel="noreferrer noopener" @click="trackPageVisit('https://docs.storj.io/dcs/getting-started/quickstart-uplink-cli/generate-access-grants-and-tokens/generate-a-token')">Learn More</a></span>
</div>
</div>
</div>
</div>
<div class="create-access__fragment">
<NameIcon />
<div class="create-access__fragment__wrap">
<p class="create-access__fragment__wrap__label">Name</p>
<input
v-model="accessName"
type="text"
placeholder="Input Access Name" class="create-access__fragment__wrap__input"
>
</div>
</div>
<div class="create-access__fragment">
<PermissionsIcon />
<div class="create-access__fragment__wrap">
<p class="create-access__fragment__wrap__label">Permissions</p>
<div class="create-access__fragment__wrap__permission">
<label class="checkmark-container">
<input
id="permissions__all-check"
type="checkbox"
:checked="allPermissionsClicked"
@click="toggleAllPermission('all')"
>
<span class="checkmark" />
</label>
<label for="permissions__all-check">
All
</label>
<Chevron :class="`permissions-chevron-${showAllPermissions.position}`" @click="togglePermissions" />
</div>
<div v-if="showAllPermissions.show">
<div v-for="item in permissionsList" :key="item" class="create-access__fragment__wrap__permission">
<label class="checkmark-container">
<input
:id="`permissions__${item}-check`"
type="checkbox"
:value="item"
:checked="checkedPermissions[item]"
@click="toggleAllPermission(item)"
>
<span class="checkmark" />
</label>
<label :for="`permissions__${item}-check`">{{ item }}</label>
</div>
</div>
</div>
</div>
<div class="create-access__fragment">
<BucketsIcon />
<div class="create-access__fragment__wrap">
<p class="create-access__fragment__wrap__label">Buckets</p>
<div>
<BucketsSelection
class="access-bucket-container"
:show-scrollbar="true"
/>
</div>
<div class="create-access__fragment__wrap__bucket-bullets">
<div
v-for="(name, index) in selectedBucketNames"
:key="index"
class="create-access__fragment__wrap__bucket-bullets__container"
>
<BucketNameBullet :name="name" />
</div>
</div>
</div>
</div>
<div class="create-access__fragment">
<DateIcon />
<div class="create-access__fragment__wrap">
<p class="create-access__fragment__wrap__label">Duration</p>
<div v-if="addDateSelected">
<DurationSelection
container-style="access-date-container"
text-style="access-date-text"
picker-style="__access-date-container"
/>
</div>
<div
v-else
class="create-access__fragment__wrap__text"
@click="addDateSelected = true"
>
Add Date (optional)
</div>
</div>
</div>
<div class="create-access__buttons">
<a href="https://docs.storj.io/dcs/concepts/access/access-grants/api-key" target="_blank" rel="noopener noreferrer" @click="trackPageVisit('https://docs.storj.io/dcs/concepts/access/access-grants/api-key')">
<v-button
label="Learn More"
height="48px"
:is-transparent="true"
font-size="14px"
class="create-access__buttons__button"
/>
</a>
<v-button
:label="checkedTypes.includes('api') ? 'Create Keys ⟶' : 'Encrypt My Access ⟶'"
font-size="14px"
height="48px"
:on-press="checkedTypes.includes('api') ? propagateInfo : encryptClickAction"
:is-disabled="!selectedPermissions.length || !accessName || !checkedTypes.length"
class="create-access__buttons__button"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeMount, onMounted, reactive, ref } from 'vue';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { AccessGrant } from '@/types/accessGrants';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { useNotify, useStore } from '@/utils/hooks';
import VButton from '@/components/common/VButton.vue';
import BucketsSelection from '@/components/accessGrants/permissions/BucketsSelection.vue';
import BucketNameBullet from '@/components/accessGrants/permissions/BucketNameBullet.vue';
import DurationSelection from '@/components/accessGrants/permissions/DurationSelection.vue';
import DateIcon from '@/../static/images/accessGrants/create-access_date.svg';
import TypesIcon from '@/../static/images/accessGrants/create-access_type.svg';
import NameIcon from '@/../static/images/accessGrants/create-access_name.svg';
import PermissionsIcon from '@/../static/images/accessGrants/create-access_permissions.svg';
import Chevron from '@/../static/images/accessGrants/chevron.svg';
import BucketsIcon from '@/../static/images/accessGrants/create-access_buckets.svg';
type ShowPermissions = {
show: boolean,
position: string
}
type Permissions = {
Read: boolean,
Write: boolean,
List: boolean,
Delete: boolean
}
const props = withDefaults(defineProps<{ checkedType?: string; }>(), { checkedType: '' });
const emit = defineEmits(['close-modal', 'propagateInfo', 'encrypt']);
const store = useStore();
const notify = useNotify();
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const checkedTypes = ref<string[]>([]);
const accessName = ref<string>('');
const selectedPermissions = ref<string[]>([]);
const allPermissionsClicked = ref<boolean>(false);
const permissionsList = ref<string[]>(['Read', 'Write', 'List', 'Delete']);
const addDateSelected = ref<boolean>(false);
const tooltipHover = ref<string>('');
const tooltipVisibilityTimer = ref<ReturnType<typeof setTimeout> | null>();
let checkedPermissions = reactive<Permissions>({ Read: false, Write: false, List: false, Delete: false });
let showAllPermissions = reactive<ShowPermissions>({ show: false, position: 'up' });
const accessGrantsList = computed((): AccessGrant[] => {
return store.state.accessGrantsModule.page.accessGrants;
});
/**
* Retrieves selected buckets for bucket bullets.
*/
const selectedBucketNames = computed((): string[] => {
return store.state.accessGrantsModule.selectedBucketNames;
});
function onCloseClick(): void {
store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR_SELECTION);
emit('close-modal');
}
/**
* Whether some type of access is selected
* @param type
*/
function getIsChecked(type: string): boolean {
return checkedTypes.value.includes(type);
}
function checkChanged(event: { target: { checked: boolean } }, type: string): void {
const isSelected = event.target.checked;
if (type === 'api') {
if (isSelected) {
checkedTypes.value = ['api'];
} else {
checkedTypes.value = checkedTypes.value.filter(t => t !== 'api');
}
} else {
if (isSelected) {
checkedTypes.value = checkedTypes.value.filter(t => t !== 'api');
checkedTypes.value.push(type);
} else {
checkedTypes.value = checkedTypes.value.filter(t => t !== type);
}
}
}
/**
* propagates selected info to parent on flow progression.
*/
function propagateInfo(): void {
if (!checkedTypes.value.length) return;
const payloadObject = {
'checkedType': checkedTypes.value.join(','),
'accessName': accessName.value,
'selectedPermissions': selectedPermissions.value,
};
emit('propagateInfo', payloadObject, checkedTypes.value.join(','));
}
/**
* Toggles permissions list visibility.
*/
function togglePermissions(): void {
showAllPermissions.show = !showAllPermissions.show;
showAllPermissions.position = showAllPermissions.show ? 'up' : 'down';
}
function encryptClickAction(): void {
let mappedList = accessGrantsList.value.map((key) => (key.name));
if (mappedList.includes(accessName.value)) {
notify.error(`validation: An API Key with this name already exists in this project, please use a different name`, AnalyticsErrorEventSource.CREATE_AG_FORM);
return;
} else if (!checkedTypes.value.includes('api')) {
// emit event here
propagateInfo();
emit('encrypt');
}
analytics.eventTriggered(AnalyticsEvent.ENCRYPT_MY_ACCESS_CLICKED);
}
function toggleAllPermission(type): void {
if (type === 'all') {
allPermissionsClicked.value = !allPermissionsClicked.value;
selectedPermissions.value = allPermissionsClicked.value ? permissionsList.value : [];
for (const permission in checkedPermissions) {
checkedPermissions[permission] = allPermissionsClicked.value;
}
return;
}
if (checkedPermissions[type]) {
checkedPermissions[type] = false;
allPermissionsClicked.value = false;
selectedPermissions.value = selectedPermissions.value.filter(t => t !== type);
} else {
checkedPermissions[type] = true;
selectedPermissions.value.push(type);
if (checkedPermissions.Read && checkedPermissions.Write && checkedPermissions.List && checkedPermissions.Delete) {
allPermissionsClicked.value = true;
selectedPermissions.value = permissionsList.value;
}
}
}
/**
* Toggles tooltip visibility.
*/
function toggleTooltipHover(type, action): void {
if (tooltipHover.value === '' && action === 'over') {
tooltipHover.value = type;
return;
} else if (tooltipHover.value === type && action === 'leave') {
tooltipVisibilityTimer.value = setTimeout(() => {
tooltipHover.value = '';
}, 750);
return;
} else if (tooltipHover.value === type && action === 'over') {
tooltipVisibilityTimer.value && clearTimeout(tooltipVisibilityTimer.value);
return;
} else if (tooltipHover.value !== type) {
tooltipVisibilityTimer.value && clearTimeout(tooltipVisibilityTimer.value);
tooltipHover.value = type;
}
}
/**
* Sends "trackPageVisit" event to segment and opens link.
*/
function trackPageVisit(link: string): void {
analytics.pageVisit(link);
}
onMounted(() => {
showAllPermissions.show = false;
showAllPermissions.position = 'down';
});
onBeforeMount(() => {
if (props.checkedType) checkedTypes.value = [props.checkedType];
});
</script>
<style scoped lang="scss">
@mixin tooltip-container {
position: absolute;
background: var(--c-grey-6);
border-radius: 6px;
width: 253px;
color: #fff;
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 8px;
z-index: 1;
transition: 250ms;
}
@mixin tooltip-arrow {
content: '';
position: absolute;
bottom: 0;
width: 0;
height: 0;
border: 6px solid transparent;
border-top-color: var(--c-grey-6);
border-bottom: 0;
margin-left: -20px;
margin-bottom: -20px;
}
p {
font-weight: bold;
padding-bottom: 10px;
}
svg {
min-width: 40px;
}
label {
padding-right: 10px;
}
@mixin chevron {
padding-left: 4px;
transition: transform 0.3s;
min-width: unset;
}
.permissions-chevron-up {
@include chevron;
transform: rotate(-180deg);
}
.permissions-chevron-down {
@include chevron;
}
.tooltip-icon {
display: flex;
width: 14px;
height: 14px;
cursor: pointer;
}
.tooltip-text {
text-align: center;
font-weight: 500;
}
.create-access {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 32px;
font-family: 'font_regular', sans-serif;
max-width: 346px;
@media screen and (max-width: 390px) {
padding: 32px 12px;
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;
line-height: 36px;
letter-spacing: -0.02em;
color: #000;
margin-bottom: 24px;
}
&__fragment {
display: flex;
align-items: flex-start;
margin-bottom: 16px;
width: 100%;
&__wrap {
display: flex;
flex-direction: column;
margin-left: 16px;
width: 100%;
&__label {
font-family: 'font_medium', sans-serif;
font-size: 14px;
line-height: 20px;
color: #000;
text-align: left;
}
&__type-container {
display: flex;
align-items: center;
margin-bottom: 10px;
}
&__input {
background: #fff;
border: 1px solid var(--c-grey-4);
box-sizing: border-box;
border-radius: 6px;
font-size: 14px;
padding: 10px;
width: 100%;
}
&__input:focus {
border-color: #2683ff;
}
&__permission {
display: flex;
align-items: center;
margin-bottom: 10px;
}
&__bucket-bullets {
display: flex;
align-items: center;
max-width: 100%;
flex-wrap: wrap;
&__container {
display: flex;
margin-top: 5px;
}
}
&__text {
color: var(--c-grey-5);
text-decoration: underline;
cursor: pointer;
text-align: left;
}
}
}
&__buttons {
display: flex;
width: 100%;
justify-content: flex-start;
margin-top: 16px;
column-gap: 8px;
@media screen and (max-width: 390px) {
flex-direction: column;
column-gap: unset;
row-gap: 8px;
}
&__button {
padding: 0 15px;
@media screen and (max-width: 390px) {
width: unset !important;
}
}
}
}
:deep(.buckets-selection) {
margin-left: 0;
height: 40px;
border: 1px solid var(--c-grey-4);
}
:deep(.buckets-selection__toggle-container) {
padding: 10px 20px;
}
:deep(.buckets-dropdown__container__all) {
text-align: left;
}
.access-tooltip {
top: 66px;
left: 104px;
@include tooltip-container;
&:after {
left: 50%;
top: 100%;
@include tooltip-arrow;
}
}
.s3-tooltip {
top: 182px;
left: 113px;
@include tooltip-container;
&:after {
left: 50%;
top: -8%;
transform: rotate(180deg);
@include tooltip-arrow;
}
}
.api-tooltip {
top: 215px;
left: 90px;
@include tooltip-container;
&:after {
left: 50%;
top: -11%;
transform: rotate(180deg);
@include tooltip-arrow;
}
}
.checkmark-container {
position: relative;
height: 21px;
width: 21px;
cursor: pointer;
font-size: 22px;
user-select: none;
outline: none;
}
.checkmark-container input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 21px;
width: 21px;
border: 2px solid #afb7c1;
border-radius: 4px;
}
.checkmark-container:hover input ~ .checkmark {
background-color: white;
}
.checkmark-container input:checked ~ .checkmark {
border: 2px solid #376fff;
background-color: var(--c-blue-3);
}
.checkmark:after {
content: '';
position: absolute;
display: none;
}
.checkmark-container .checkmark:after {
left: 7px;
top: 3px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
transform: rotate(45deg);
}
.checkmark-container input:checked ~ .checkmark:after {
display: block;
}
</style>

View File

@ -1,403 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="encrypt">
<h2 class="encrypt__title">Select Encryption</h2>
<div
v-if="!(encryptSelect === 'create' && (isPassphraseDownloaded || isPassphraseCopied))"
class="encrypt__item"
>
<div class="encrypt__item__left-area">
<AccessKeyIcon
class="encrypt__item__left-area__icon"
:class="{ selected: encryptSelect === 'generate' }"
/>
<div class="encrypt__item__left-area__text">
<h3>Generate Passphrase</h3>
<p>Automatically Generate Seed</p>
</div>
</div>
<div class="encrypt__item__radio">
<input
id="generate-check"
v-model="encryptSelect"
value="generate"
type="radio"
name="type"
@change="onRadioInput"
>
</div>
</div>
<div
v-if="encryptSelect === 'generate'"
class="encrypt__generated-passphrase"
>
{{ passphrase }}
</div>
<div
v-if="!(encryptSelect && (isPassphraseDownloaded || isPassphraseCopied))"
id="divider"
class="encrypt__divider"
:class="{ 'in-middle': encryptSelect === 'generate' }"
/>
<div
v-if="!(encryptSelect === 'generate' && (isPassphraseDownloaded || isPassphraseCopied))"
id="own"
:class="{ 'in-middle': encryptSelect === 'generate' }"
class="encrypt__item"
>
<div class="encrypt__item__left-area">
<ThumbPrintIcon
class="encrypt__item__left-area__icon"
:class="{ selected: encryptSelect === 'create' }"
/>
<div class="encrypt__item__left-area__text">
<h3>Create My Own Passphrase</h3>
<p>Make it Personalized</p>
</div>
</div>
<div class="encrypt__item__radio">
<input
id="create-check"
v-model="encryptSelect"
value="create"
type="radio"
name="type"
@change="onRadioInput"
>
</div>
</div>
<input
v-if="encryptSelect === 'create'"
v-model="passphrase"
type="text"
placeholder="Input Your Passphrase"
class="encrypt__passphrase" :disabled="encryptSelect === 'generate'"
@input="resetSavedStatus"
>
<div
class="encrypt__footer-container"
:class="{ 'in-middle': encryptSelect === 'generate' }"
>
<div class="encrypt__footer-container__buttons">
<v-button
v-clipboard:copy="passphrase"
:label="isPassphraseCopied ? 'Copied' : 'Copy to clipboard'"
height="50px"
:is-transparent="!isPassphraseCopied"
:is-white-green="isPassphraseCopied"
class="encrypt__footer-container__buttons__copy-button"
font-size="14px"
:on-press="onCopyPassphraseClick"
:is-disabled="passphrase.length < 1"
>
<template #icon>
<copy-icon v-if="!isPassphraseCopied" :class="{'copy-icon': !!passphrase.length}" />
<check-icon v-else class="check-icon" />
</template>
</v-button>
<v-button
label="Download .txt"
font-size="14px"
height="50px"
class="encrypt__footer-container__buttons__download-button"
:is-green="isPassphraseDownloaded"
:on-press="downloadPassphrase"
:is-disabled="passphrase.length < 1"
>
<template #icon>
<download-icon v-if="!isPassphraseDownloaded" :class="{'download-icon': !passphrase.length}" />
<check-icon v-else />
</template>
</v-button>
</div>
<div v-if="isPassphraseDownloaded || isPassphraseCopied" :class="`encrypt__footer-container__acknowledgement-container ${acknowledgementCheck ? 'blue-background' : ''}`">
<input
id="acknowledgement"
v-model="acknowledgementCheck"
type="checkbox"
class="encrypt__footer-container__acknowledgement-container__check"
>
<label for="acknowledgement" class="encrypt__footer-container__acknowledgement-container__text">I understand that Storj does not know or store my encryption passphrase. If I lose it, I won't be able to recover files.</label>
</div>
<div
v-if="isPassphraseDownloaded || isPassphraseCopied"
class="encrypt__footer-container__buttons"
>
<v-button
label="Back"
height="50px"
:is-transparent="true"
class="encrypt__footer-container__buttons__copy-button"
font-size="14px"
:on-press="backAction"
/>
<v-button
label="Create my Access ⟶"
font-size="14px"
height="50px"
class="encrypt__footer-container__buttons__download-button"
:is-disabled="!acknowledgementCheck"
:on-press="createAccessGrant"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { generateMnemonic } from 'bip39';
import { ref, watch } from 'vue';
import { Download } from '@/utils/download';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { useNotify } from '@/utils/hooks';
import VButton from '@/components/common/VButton.vue';
import CopyIcon from '@/../static/images/common/copy.svg';
import CheckIcon from '@/../static/images/common/check.svg';
import DownloadIcon from '@/../static/images/common/download.svg';
import AccessKeyIcon from '@/../static/images/accessGrants/accessKeyIcon.svg';
import ThumbPrintIcon from '@/../static/images/accessGrants/thumbPrintIcon.svg';
const notify = useNotify();
const emit = defineEmits(['apply-passphrase', 'create-access', 'close-modal', 'backAction']);
const encryptSelect = ref<'create' | 'generate'>('create');
const isPassphraseCopied = ref<boolean>(false);
const isPassphraseDownloaded = ref<boolean>(false);
const acknowledgementCheck = ref<boolean>(false);
const passphrase = ref<string>('');
const currentDate = new Date().toISOString();
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
function createAccessGrant(): void {
emit('create-access');
}
function onCloseClick(): void {
emit('close-modal');
}
function onRadioInput(): void {
isPassphraseCopied.value = false;
isPassphraseDownloaded.value = false;
passphrase.value = '';
if (encryptSelect.value === 'generate') {
passphrase.value = generateMnemonic();
}
}
function backAction(): void {
emit('backAction');
}
function resetSavedStatus(): void {
isPassphraseCopied.value = false;
isPassphraseDownloaded.value = false;
}
function onCopyPassphraseClick(): void {
isPassphraseCopied.value = true;
analytics.eventTriggered(AnalyticsEvent.COPY_TO_CLIPBOARD_CLICKED);
notify.success(`Passphrase was copied successfully`);
}
/**
* Downloads passphrase to .txt file
*/
function downloadPassphrase(): void {
isPassphraseDownloaded.value = true;
Download.file(passphrase.value, `passphrase-${currentDate}.txt`);
analytics.eventTriggered(AnalyticsEvent.DOWNLOAD_TXT_CLICKED);
}
watch(passphrase, (newPassphrase) => {
emit('apply-passphrase', newPassphrase);
});
</script>
<style scoped lang="scss">
.copy-icon {
:deep(rect),
:deep(path) {
stroke: var(--c-grey-6);
}
}
.check-icon :deep(path) {
fill: var(--c-green-5);
}
.download-icon :deep(path) {
fill: #acb0bc;
}
.encrypt {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
font-family: 'font_regular', sans-serif;
padding: 32px;
max-width: 350px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;
line-height: 36px;
letter-spacing: -0.02em;
color: #000;
margin-bottom: 32px;
}
&__divider {
width: 100%;
height: 1px;
background: var(--c-grey-2);
margin: 16px 0;
&.in-middle {
order: 4;
}
}
&__item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
box-sizing: border-box;
margin-top: 10px;
&__left-area {
display: flex;
align-items: center;
justify-content: flex-start;
&__icon {
margin-right: 8px;
&.selected {
:deep(circle) {
fill: var(--c-blue-1) !important;
}
:deep(path) {
fill: var(--c-blue-4) !important;
}
}
}
&__text {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
font-family: 'font_regular', sans-serif;
font-size: 12px;
h3 {
margin: 0 0 8px;
font-family: 'font_bold', sans-serif;
font-size: 14px;
}
p {
padding: 0;
}
}
}
&__radio {
display: flex;
align-items: center;
justify-content: center;
width: 10px;
height: 10px;
}
}
&__generated-passphrase {
margin-top: 20px;
margin-bottom: 20px;
align-items: center;
padding: 10px 16px;
background: var(--c-grey-2);
border: 1px solid var(--c-grey-4);
border-radius: 7px;
text-align: left;
}
&__passphrase {
margin-top: 20px;
width: 100%;
background: #fff;
border: 1px solid var(--c-grey-4);
box-sizing: border-box;
border-radius: 4px;
font-size: 14px;
padding: 10px;
}
&__footer-container {
display: flex;
flex-direction: column;
width: 100%;
justify-content: flex-start;
margin-top: 16px;
&__buttons {
display: flex;
width: 100%;
margin-top: 25px;
column-gap: 8px;
@media screen and (max-width: 390px) {
flex-direction: column;
column-gap: unset;
row-gap: 8px;
}
&__copy-button,
&__download-button {
padding: 0 15px;
@media screen and (max-width: 390px) {
width: unset !important;
}
}
}
&__acknowledgement-container {
border: 1px solid var(--c-grey-4);
border-radius: 6px;
display: grid;
grid-template-columns: 1fr 6fr;
padding: 10px;
margin-top: 25px;
height: 80px;
align-content: center;
&__check {
margin: 0 auto auto;
border-radius: 4px;
height: 16px;
width: 16px;
}
&__text {
text-align: left;
}
}
}
}
</style>

View File

@ -1,136 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="info">
<EncryptInfoIcon />
<h1 class="info__title">Encryption Information</h1>
<p class="info__info">
By generating S3 credentials, you are opting in to <a class="info__info__link" href="https://docs.storj.io/dcs/concepts/encryption-key/design-decision-server-side-encryption/" target="_blank" rel="noopener noreferrer">server-side encryption</a>.
</p>
<VCheckbox
class="info__checkbox"
label="Dont show this again."
@setData="toggleCheckbox"
/>
<div class="info__buttons">
<VButton
label="Go Back"
height="48px"
border-radius="8px"
:is-transparent="true"
:on-press="onBackClick"
/>
<VButton
label="Continue"
height="48px"
border-radius="8px"
:on-press="onContinueClick"
/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { LocalData } from '@/utils/localData';
import VButton from '@/components/common/VButton.vue';
import VCheckbox from '@/components/common/VCheckbox.vue';
import EncryptInfoIcon from '@/../static/images/accessGrants/encyptInfoIcon.svg';
// @vue/component
@Component({
components: {
EncryptInfoIcon,
VButton,
VCheckbox,
},
})
export default class EncryptionInfoFormModal extends Vue {
public isDontShow = false;
/**
* Toggles checkbox.
*/
public toggleCheckbox(value: boolean): void {
this.isDontShow = value;
}
/**
* Holds on back button click logic.
* Emits back event.
*/
public onBackClick(): void {
this.$emit('back');
}
/**
* Holds on continue button click logic.
* Emits continue event.
*/
public onContinueClick(): void {
if (this.isDontShow) {
LocalData.setServerSideEncryptionModalHidden(true);
}
this.$emit('continue');
}
}
</script>
<style scoped lang="scss">
.info {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 32px;
max-width: 350px;
font-family: 'font_regular', sans-serif;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;
line-height: 36px;
letter-spacing: -0.02em;
color: #000;
margin: 16px 0;
text-align: left;
}
&__info {
font-size: 16px;
line-height: 24px;
color: #1b2533;
margin-bottom: 16px;
text-align: left;
&__link {
color: #1b2533;
text-decoration: underline !important;
text-underline-position: under;
&:visited {
color: #1b2533;
}
}
}
&__buttons {
width: 100%;
display: flex;
align-items: center;
column-gap: 8px;
margin-top: 16px;
@media screen and (max-width: 390px) {
flex-direction: column-reverse;
column-gap: unset;
row-gap: 8px;
}
}
}
</style>

View File

@ -1,538 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="grants">
<AccessGrantsIcon v-if="accessSelected" />
<S3Icon v-if="s3Selected" />
<CLIIcon v-if="apiKeySelected" />
<h2 class="grants__title">{{ accessName }}&nbsp;Created</h2>
<p v-if="!s3AndAccessSelected" class="grants__created">Now copy and save the {{ checkedText[checkedType][0] }} will only appear once. Click on the {{ checkedText[checkedType][1] }}</p>
<p v-else class="grants__created">Now copy and save the Access Grant and S3 Credentials as they will only appear once.</p>
<template v-if="accessSelected">
<div class="grants__label first">
<span class="grants__label__text">
Access Grant
</span>
<a
href="https://docs.storj.io/dcs/concepts/access/access-grants"
target="_blank"
rel="noopener noreferrer"
>
<img
class="tooltip-icon"
alt="tooltip icon"
src="/static/static/images/accessGrants/create-access_information.png"
>
</a>
</div>
<div
class="grants__generated-credentials"
>
<span class="grants__generated-credentials__text">
{{ access }}
</span>
<img
class="clickable-image"
alt="copy icon"
src="/static/static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(access)"
>
</div>
</template>
<template v-if="s3Selected">
<div class="grants__label first">
<span class="grants__label__text">
Access Key
</span>
</div>
<div class="grants__generated-credentials">
<span class="grants__generated-credentials__text">
{{ gatewayCredentials.accessKeyId }}
</span>
<img
class="clickable-image"
alt="copy icon"
src="/static/static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(gatewayCredentials.accessKeyId)"
>
</div>
<div class="grants__label">
<span class="grants__label__text">
Secret Key
</span>
</div>
<div class="grants__generated-credentials">
<span class="grants__generated-credentials__text">
{{ gatewayCredentials.secretKey }}
</span>
<img
class="clickable-image"
alt="copy icon"
src="/static/static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(gatewayCredentials.secretKey)"
>
</div>
<div class="grants__label">
<span class="grants__label__text">
Endpoint
</span>
</div>
<div class="grants__generated-credentials">
<span class="grants__generated-credentials__text">
{{ gatewayCredentials.endpoint }}
</span>
<img
class="clickable-image"
src="/static/static/images/accessGrants/create-access_copy-icon.png"
alt="copy"
@click="onCopyClick(gatewayCredentials.endpoint)"
>
</div>
</template>
<template v-if="apiKeySelected">
<div class="grants__label first">
<span class="grants__label__text">
Satellite Address
</span>
<a
href="https://docs.storj.io/dcs/concepts/satellite"
target="_blank"
rel="noopener noreferrer"
>
<img
class="tooltip-icon"
alt="tooltip icon"
src="/static/static/images/accessGrants/create-access_information.png"
>
</a>
</div>
<div class="grants__generated-credentials">
<span class="grants__generated-credentials__text">
{{ satelliteAddress }}
</span>
<img
class="clickable-image"
src="/static/static/images/accessGrants/create-access_copy-icon.png"
alt="copy icon"
@click="onCopyClick(satelliteAddress)"
>
</div>
<div class="grants__label">
<span class="grants__label__text">
API Key
</span>
<a
href="https://docs.storj.io/dcs/concepts/access/access-grants/api-key"
target="_blank"
rel="noopener noreferrer"
>
<img
class="tooltip-icon"
alt="tooltip icon"
src="/static/static/images/accessGrants/create-access_information.png"
>
</a>
</div>
<div class="grants__generated-credentials">
<span class="grants__generated-credentials__text">
{{ restrictedKey }}
</span>
<img
class="clickable-image"
alt="copy icon"
src="/static/static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(restrictedKey)"
>
</div>
</template>
<template v-if="s3AndAccessSelected" class="multiple-section">
<div class="multiple-section__access">
<AccessGrantsIcon />
<div class="grants__label first">
<span class="grants__label__text">
Access Grant
</span>
<a
href="https://docs.storj.io/dcs/concepts/access/access-grants/"
target="_blank"
rel="noopener noreferrer"
>
<img
class="tooltip-icon"
alt="tooltip icon"
src="/static/static/images/accessGrants/create-access_information.png"
>
</a>
</div>
<div class="grants__generated-credentials">
<span class="grants__generated-credentials__text">
{{ access }}
</span>
<img
class="clickable-image"
alt="copy icon"
src="/static/static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(access)"
>
</div>
</div>
<div class="multiple-section__s3">
<S3Icon />
<div class="grants__label first">
<span class="grants__label__text">
Access Key
</span>
</div>
<div class="grants__generated-credentials">
<span class="grants__generated-credentials__text">
{{ gatewayCredentials.accessKeyId }}
</span>
<img
class="clickable-image"
alt="copy icon"
src="/static/static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(gatewayCredentials.accessKeyId)"
>
</div>
<div class="grants__label">
<span class="grants__label__text">
Secret Key
</span>
</div>
<div class="grants__generated-credentials">
<span class="grants__generated-credentials__text">
{{ gatewayCredentials.secretKey }}
</span>
<img
class="clickable-image"
alt="copy icon"
src="/static/static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(gatewayCredentials.secretKey)"
>
</div>
<div class="grants__label">
<span class="grants__label__text">
Endpoint
</span>
</div>
<div class="grants__generated-credentials">
<span class="grants__generated-credentials__text">
{{ gatewayCredentials.endpoint }}
</span>
<img
class="clickable-image"
src="/static/static/images/accessGrants/create-access_copy-icon.png"
alt="copy"
@click="onCopyClick(gatewayCredentials.endpoint)"
>
</div>
</div>
</template>
<div v-if="s3Included" class="grants__buttons">
<a
class="link"
href="https://docs.storj.io/dcs/api-reference/s3-compatible-gateway"
target="_blank"
rel="noopener noreferrer"
@click="trackPageVisit('https://docs.storj.io/dcs/api-reference/s3-compatible-gateway')"
>
<v-button
label="Learn More"
height="48px"
:is-transparent="true"
font-size="14px"
class="grants__buttons__learn-more"
/>
</a>
<v-button
label="Download .txt"
font-size="14px"
height="48px"
class="grants__buttons__download-button"
:is-green="areCredentialsDownloaded"
:on-press="downloadCredentials"
>
<template v-if="areCredentialsDownloaded" #icon>
<check-icon />
</template>
</v-button>
</div>
<div v-else class="grants__buttons">
<v-button
label="Download .txt"
font-size="14px"
width="182px"
height="48px"
class="grants__buttons__download-button"
:is-green="areCredentialsDownloaded"
:on-press="downloadCredentials"
>
<template v-if="areCredentialsDownloaded" #icon>
<check-icon />
</template>
</v-button>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { MetaUtils } from '@/utils/meta';
import { Download } from '@/utils/download';
import { EdgeCredentials } from '@/types/accessGrants';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import VButton from '@/components/common/VButton.vue';
import AccessGrantsIcon from '@/../static/images/accessGrants/accessGrantsIcon.svg';
import CheckIcon from '@/../static/images/common/check.svg';
import CLIIcon from '@/../static/images/accessGrants/cli.svg';
import S3Icon from '@/../static/images/accessGrants/s3.svg';
// @vue/component
@Component({
components: {
AccessGrantsIcon,
CheckIcon,
CLIIcon,
S3Icon,
VButton,
},
})
export default class GrantCreated extends Vue {
@Prop({ default: 'Default' })
private readonly checkedType: string;
@Prop({ default: 'Default' })
private readonly restrictedKey: string;
@Prop({ default: 'Default' })
private readonly accessName: string;
@Prop({ default: 'Default' })
private readonly access: string;
private areCredentialsDownloaded = false;
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
private checkedText: Record<string, string[]> = { access: ['Access Grant as it', 'information icon to learn more.'], s3: ['S3 credentials as they', 'Learn More button to access the documentation.'], api: ['Satellite Address and API Key as they', 'information icons to learn more.'] };
public currentDate = new Date().toISOString();
public satelliteAddress: string = MetaUtils.getMetaContent('satellite-nodeurl');
public onCopyClick(item): void {
this.$copyText(item);
this.$notify.success(`Credential was copied successfully.`);
}
/**
* Returns generated gateway credentials from store.
*/
public get gatewayCredentials(): EdgeCredentials {
return this.$store.state.accessGrantsModule.gatewayCredentials;
}
/**
* Whether api is selected
* */
public get apiKeySelected(): boolean {
return this.checkedType === 'api';
}
/**
* Whether access is selected
* */
public get accessSelected(): boolean {
return this.checkedType === 'access';
}
/**
* Whether s3 is selected
* */
public get s3Selected(): boolean {
return this.checkedType === 's3';
}
/**
* Whether s3 access is what is/part of selected types
**/
public get s3Included(): boolean {
return this.checkedType.includes('s3');
}
/**
* Whether multiple access types are being created
* */
public get s3AndAccessSelected(): boolean {
return this.s3Included && this.checkedType.includes('access');
}
/**
* Downloads credentials to .txt file
*/
public downloadCredentials(): void {
let type = this.checkedType;
if (this.s3AndAccessSelected)
type = 's3Access';
const credentialMap = {
access: [this.access],
s3: [`access key: ${this.gatewayCredentials.accessKeyId}\nsecret key: ${this.gatewayCredentials.secretKey}\nendpoint: ${this.gatewayCredentials.endpoint}`],
api: [`satellite address: ${this.satelliteAddress}\nrestricted key: ${this.restrictedKey}`],
s3Access: [`access grant: ${this.access}\naccess key: ${this.gatewayCredentials.accessKeyId}\nsecret key: ${this.gatewayCredentials.secretKey}\nendpoint: ${this.gatewayCredentials.endpoint}`],
};
this.areCredentialsDownloaded = true;
Download.file(credentialMap[type], `${this.checkedType}-credentials-${this.currentDate}.txt`);
this.analytics.eventTriggered(AnalyticsEvent.DOWNLOAD_TXT_CLICKED);
}
/**
* Sends "trackPageVisit" event to segment and opens link.
*/
public trackPageVisit(link: string): void {
this.analytics.pageVisit(link);
}
}
</script>
<style scoped lang="scss">
.grants {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
font-family: 'font_regular', sans-serif;
padding: 32px;
max-width: 350px;
@media screen and (max-width: 470px) {
max-width: 300px;
padding: 32px 16px;
}
@media screen and (max-width: 380px) {
max-width: 250px;
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;
line-height: 36px;
letter-spacing: -0.02em;
color: #14142b;
text-align: left;
margin-top: 10px;
word-break: break-word;
}
&__label {
display: flex;
margin-top: 24px;
align-items: center;
&.first {
margin-top: 8px;
}
&__text {
font-size: 14px;
font-weight: 700;
line-height: 20px;
letter-spacing: 0;
text-align: left;
padding: 0 6px 0 0;
}
}
&__generated-credentials {
margin-top: 10px;
align-items: center;
padding: 10px 16px;
background: var(--c-grey-2);
border: 1px solid var(--c-grey-4);
border-radius: 7px;
display: flex;
justify-content: space-between;
max-width: calc(100% - 32px);
width: calc(100% - 32px);
&__text {
width: 90%;
text-align: left;
text-overflow: ellipsis;
overflow-x: hidden;
white-space: nowrap;
}
}
&__buttons {
display: flex;
align-items: center;
margin-top: 32px;
width: 100%;
column-gap: 8px;
@media screen and (max-width: 470px) {
flex-direction: column;
column-gap: unset;
row-gap: 8px;
}
&__learn-more,
&__download-button {
padding: 0 15px;
@media screen and (max-width: 470px) {
width: calc(100% - 30px) !important;
}
}
}
&__created {
font-size: 14px;
line-height: 20px;
overflow-wrap: break-word;
text-align: left;
margin-top: 32px;
}
}
.multiple-section {
&__access {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
margin-top: 20px;
}
&__s3 {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
border-top: 1px solid #e5e7eb;
margin-top: 20px;
padding-top: 20px;
}
}
.clickable-image {
cursor: pointer;
}
.tooltip-icon {
display: flex;
width: 14px;
height: 14px;
cursor: pointer;
}
.link {
@media screen and (max-width: 470px) {
width: 100%;
}
}
</style>

View File

@ -87,7 +87,6 @@ import { computed, onMounted } from 'vue';
import { USER_ACTIONS } from '@/store/modules/users';
import { User } from '@/types/users';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { useNotify, useStore } from '@/utils/hooks';

View File

@ -3,7 +3,7 @@
<template>
<div class="account-billing-area">
<div v-if="isNewBillingScreen" class="account-billing-area__header__div">
<div class="account-billing-area__header__div">
<div class="account-billing-area__title">
<h1 class="account-billing-area__title__text">Billing</h1>
</div>
@ -35,54 +35,6 @@
</div>
<div class="account-billing-area__divider" />
</div>
<div v-if="!isNewBillingScreen">
<div v-if="hasNoCreditCard" class="account-billing-area__notification-container">
<div v-if="isBalanceNegative" class="account-billing-area__notification-container__negative-balance">
<NegativeBalanceIcon />
<p class="account-billing-area__notification-container__negative-balance__text">
Your usage charges exceed your account balance. Please add STORJ Tokens or a debit/credit card to
prevent data loss.
</p>
</div>
<div v-if="isBalanceLow" class="account-billing-area__notification-container__low-balance">
<LowBalanceIcon />
<p class="account-billing-area__notification-container__low-balance__text">
Your account balance is running low. Please add STORJ Tokens or a debit/credit card to prevent data loss.
</p>
</div>
</div>
<div v-if="userHasOwnProject" class="account-billing-area__title-area" :class="{ 'custom-position': hasNoCreditCard && (isBalanceLow || isBalanceNegative) }">
<div class="account-billing-area__title-area__balance-area">
<div class="account-billing-area__title-area__balance-area__free-credits">
<p class="account-billing-area__title-area__balance-area__free-credits__label">Free Credits:</p>
<VLoader v-if="isBalanceFetching" width="20px" height="20px" />
<p v-else>{{ balance.freeCredits | centsToDollars }}</p>
</div>
<div class="account-billing-area__title-area__balance-area__tokens-area" @click.stop="toggleBalanceDropdown">
<p class="account-billing-area__title-area__balance-area__tokens-area__label" :style="{ color: balanceColor }">
Available Balance:
</p>
<VLoader v-if="isBalanceFetching" width="20px" height="20px" />
<p v-else>
{{ balance.coins | centsToDollars }}
</p>
<HideIcon v-if="isBalanceDropdownShown" class="icon" />
<ExpandIcon v-else class="icon" />
<HistoryDropdown
v-show="isBalanceDropdownShown"
label="Balance History"
:route="balanceHistoryRoute"
@close="closeDropdown"
/>
</div>
</div>
<PeriodSelection v-if="userHasOwnProject" />
</div>
<EstimatedCostsAndCredits v-if="isSummaryVisible" />
<PaymentMethods />
<SmallDepositHistory />
<CouponArea />
</div>
<router-view />
</div>
</template>
@ -90,7 +42,6 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { MetaUtils } from '@/utils/meta';
import { RouteConfig } from '@/router';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { AccountBalance } from '@/types/payments';
@ -100,37 +51,9 @@ import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames
import { APP_STATE_DROPDOWNS } from '@/utils/constants/appStatePopUps';
import { NavigationLink } from '@/types/navigation';
import PeriodSelection from '@/components/account/billing/depositAndBillingHistory/PeriodSelection.vue';
import SmallDepositHistory from '@/components/account/billing/depositAndBillingHistory/SmallDepositHistory.vue';
import EstimatedCostsAndCredits from '@/components/account/billing/estimatedCostsAndCredits/EstimatedCostsAndCredits.vue';
import CouponArea from '@/components/account/billing/coupons/CouponArea.vue';
import HistoryDropdown from '@/components/account/billing/HistoryDropdown.vue';
import PaymentMethods from '@/components/account/billing/paymentMethods/PaymentMethods.vue';
import VLoader from '@/components/common/VLoader.vue';
import ExpandIcon from '@/../static/images/account/billing/expand.svg';
import HideIcon from '@/../static/images/account/billing/hide.svg';
import LowBalanceIcon from '@/../static/images/account/billing/lowBalance.svg';
import NegativeBalanceIcon from '@/../static/images/account/billing/negativeBalance.svg';
// @vue/component
@Component({
components: {
PeriodSelection,
SmallDepositHistory,
EstimatedCostsAndCredits,
PaymentMethods,
LowBalanceIcon,
NegativeBalanceIcon,
HistoryDropdown,
ExpandIcon,
HideIcon,
CouponArea,
VLoader,
},
})
@Component
export default class BillingArea extends Vue {
public readonly balanceHistoryRoute: string = RouteConfig.Account.with(RouteConfig.DepositHistory).path;
public isBalanceFetching = true;
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
@ -149,12 +72,6 @@ export default class BillingArea extends Vue {
}
}
/**
* Holds minimum safe balance in cents.
* If balance is lower - yellow notification should appear.
*/
private readonly CRITICAL_AMOUNT: number = 1000;
/**
* Indicates if free credits dropdown shown.
*/
@ -176,50 +93,6 @@ export default class BillingArea extends Vue {
return this.$store.state.paymentsModule.balance;
}
/**
* Indicates if isEstimatedCostsAndCredits component is visible.
*/
public get isSummaryVisible(): boolean {
const isBalancePositive: boolean = this.balance.sum > 0;
return isBalancePositive || this.userHasOwnProject;
}
/**
* Indicates if no credit cards attached to account.
*/
public get hasNoCreditCard(): boolean {
return this.$store.state.paymentsModule.creditCards.length === 0;
}
/**
* Indicates if balance is below zero.
*/
public get isBalanceNegative(): boolean {
return this.balance.sum < 0;
}
/**
* Indicates if balance is not below zero but lower then CRITICAL_AMOUNT.
*/
public get isBalanceLow(): boolean {
return this.balance.coins > 0 && this.balance.sum < this.CRITICAL_AMOUNT;
}
/**
* Returns if balance color red if balance below zero and grey if not.
*/
public get balanceColor(): string {
return this.balance.sum < 0 ? '#ff0000' : '#768394';
}
/**
* Indicates if user has own project.
*/
public get userHasOwnProject(): boolean {
return this.$store.getters.projectsCount > 0;
}
/**
* Returns the base account route based on if we're on all projects dashboard.
*/
@ -237,13 +110,6 @@ export default class BillingArea extends Vue {
return !!this.$route.name?.toLowerCase().includes(term);
}
/**
* Toggles available balance dropdown visibility.
*/
public toggleBalanceDropdown(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACTIVE_DROPDOWN, APP_STATE_DROPDOWNS.AVAILABLE_BALANCE);
}
/**
* Closes free credits and balance dropdowns.
*/
@ -273,7 +139,7 @@ export default class BillingArea extends Vue {
}
public routeToBillingHistory(): void {
const billingPath = this.baseAccountRoute.with(RouteConfig.Billing).with(RouteConfig.BillingHistory2).path;
const billingPath = this.baseAccountRoute.with(RouteConfig.Billing).with(RouteConfig.BillingHistory).path;
if (this.$route.path !== billingPath) {
this.analytics.pageVisit(billingPath);
this.$router.push(billingPath);
@ -287,15 +153,6 @@ export default class BillingArea extends Vue {
this.$router.push(couponsPath);
}
}
/**
* Indicates if tabs options are hidden.
*/
public get isNewBillingScreen(): boolean {
const isNewBillingScreen = MetaUtils.getMetaContent('new-billing-screen');
return isNewBillingScreen === 'true';
}
}
</script>
@ -389,7 +246,7 @@ export default class BillingArea extends Vue {
padding-top: 20px;
&__text {
font-family: sans-serif;
font-family: 'font_regular', sans-serif;
}
}
@ -419,7 +276,7 @@ export default class BillingArea extends Vue {
}
&__tab {
font-family: sans-serif;
font-family: 'font_regular', sans-serif;
color: var(--c-grey-6);
font-size: 16px;
height: auto;

View File

@ -1,75 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div v-click-outside="closeDropdown" class="history-dropdown">
<div class="history-dropdown__link-container" @click="redirect">
<span class="history-dropdown__link-container__link">{{ label }}</span>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { AnalyticsHttpApi } from '@/api/analytics';
// @vue/component
@Component
export default class HistoryDropdown extends Vue {
@Prop({ default: '' })
public readonly label: string;
@Prop({ default: '' })
public readonly route: string;
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Holds logic to redirect user to history page.
*/
public redirect(): void {
this.analytics.pageVisit(this.route);
this.$router.push(this.route);
}
/**
* Closes dropdown.
*/
public closeDropdown(): void {
this.$emit('close');
}
}
</script>
<style scoped lang="scss">
.history-dropdown {
z-index: 120;
position: absolute;
left: 0;
top: 35px;
background-color: #fff;
border-radius: 6px;
border: 1px solid #c5cbdb;
box-shadow: 0 8px 34px rgb(161 173 185 / 41%);
width: 210px;
&__link-container {
width: calc(100% - 30px);
height: 50px;
padding: 0 15px;
display: flex;
align-items: center;
border-radius: 6px;
&:hover {
background-color: #f5f5f7;
}
&__link {
font-size: 14px;
line-height: 19px;
color: #7e8b9c;
}
}
}
</style>

View File

@ -61,7 +61,7 @@ onMounted(() => {
margin-top: 2rem;
&__title {
font-family: sans-serif;
font-family: 'font_regular', sans-serif;
font-size: 1.5rem;
}

View File

@ -167,6 +167,7 @@ function toggleSelection(): void {
grid-template-columns: 4fr 2fr;
grid-template-rows: 1fr 0fr 1fr 1fr;
height: 100%;
font-family: 'font_regular', sans-serif;
&__function-buttons {
grid-column: 1;
@ -188,9 +189,7 @@ function toggleSelection(): void {
&__card-text {
grid-column: 1;
grid-row: 2;
font-family: sans-serif;
font-style: normal;
font-weight: 700;
font-family: 'font_bold', sans-serif;
font-size: 12px;
line-height: 18px;
color: var(--c-grey-6);
@ -199,9 +198,7 @@ function toggleSelection(): void {
&__expiration-text {
grid-column: 2;
grid-row: 2;
font-family: sans-serif;
font-style: normal;
font-weight: 700;
font-family: 'font_bold', sans-serif;
font-size: 12px;
line-height: 18px;
color: var(--c-grey-6);
@ -215,9 +212,7 @@ function toggleSelection(): void {
&__info-container {
grid-row: 3;
grid-column: 1;
font-family: sans-serif;
font-style: normal;
font-weight: 700;
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 24px;
color: #000;
@ -230,9 +225,7 @@ function toggleSelection(): void {
&__expire-container {
grid-row: 3;
grid-column: 2;
font-family: sans-serif;
font-style: normal;
font-weight: 700;
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 24px;
color: #000;
@ -252,9 +245,7 @@ function toggleSelection(): void {
}
&__default-text {
font-family: sans-serif;
font-style: normal;
font-weight: 700;
font-family: 'font_bold', sans-serif;
font-size: 12px;
line-height: 20px;
color: var(--c-blue-4);

View File

@ -78,7 +78,7 @@
/>
</div>
<div class="usage-charges-item-container__detailed-info-container__footer__buttons">
<UsageAndChargesItem2
<UsageAndChargesItem
v-for="usageAndCharges in projectUsageAndCharges"
:key="usageAndCharges.projectId"
:item="usageAndCharges"
@ -102,7 +102,7 @@ import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { useNotify, useRouter, useStore } from '@/utils/hooks';
import UsageAndChargesItem2 from '@/components/account/billing/estimatedCostsAndCredits/UsageAndChargesItem2.vue';
import UsageAndChargesItem from '@/components/account/billing/billingTabs/UsageAndChargesItem.vue';
import VButton from '@/components/common/VButton.vue';
import EstimatedChargesIcon from '@/../static/images/account/billing/totalEstimatedChargesIcon.svg';
@ -149,7 +149,7 @@ const priceSummary = computed((): number => {
function routeToBillingHistory(): void {
analytics.eventTriggered(AnalyticsEvent.SEE_PAYMENTS_CLICKED);
router.push(RouteConfig.Account.with(RouteConfig.Billing).with(RouteConfig.BillingHistory2).path);
router.push(RouteConfig.Account.with(RouteConfig.Billing).with(RouteConfig.BillingHistory).path);
}
function routeToPaymentMethods(): void {
@ -194,7 +194,7 @@ onMounted(async () => {
<style scoped lang="scss">
.total-cost {
font-family: sans-serif;
font-family: 'font_regular', sans-serif;
margin: 20px 0;
&__header-container {
@ -205,10 +205,9 @@ onMounted(async () => {
&__date {
display: flex;
justify-content: space-between;
align-items: bottom;
align-items: flex-end;
color: var(--c-grey-6);
font-weight: 700;
font-family: sans-serif;
font-family: 'font_bold', sans-serif;
border-radius: 5px;
height: 15px;
width: auto;
@ -255,7 +254,7 @@ onMounted(async () => {
}
&__link-text {
font-weight: medium;
font-weight: 500;
text-decoration: underline;
margin-top: 10px;
cursor: pointer;
@ -318,7 +317,7 @@ onMounted(async () => {
}
.cost-by-project {
font-family: sans-serif;
font-family: 'font_regular', sans-serif;
&__title {
padding-bottom: 10px;

View File

@ -161,9 +161,7 @@
:on-page-click-callback="paginationController"
>
<template #head>
<SortingHeader2
@sortFunction="sortFunction"
/>
<SortingHeader @sortFunction="sortFunction" />
</template>
<template #body>
<token-transaction-item
@ -198,7 +196,7 @@ import VButton from '@/components/common/VButton.vue';
import VLoader from '@/components/common/VLoader.vue';
import CreditCardContainer from '@/components/account/billing/billingTabs/CreditCardContainer.vue';
import StripeCardInput from '@/components/account/billing/paymentMethods/StripeCardInput.vue';
import SortingHeader2 from '@/components/account/billing/depositAndBillingHistory/SortingHeader2.vue';
import SortingHeader from '@/components/account/billing/billingTabs/SortingHeader.vue';
import AddTokenCard from '@/components/account/billing/paymentMethods/AddTokenCard.vue';
import AddTokenCardNative from '@/components/account/billing/paymentMethods/AddTokenCardNative.vue';
import TokenTransactionItem from '@/components/account/billing/paymentMethods/TokenTransactionItem.vue';
@ -242,7 +240,7 @@ const {
VisaIcon,
VButton,
TokenTransactionItem,
SortingHeader2,
SortingHeader,
CloseCrossIcon,
CreditCardImage,
StripeCardInput,
@ -534,7 +532,7 @@ $align: center;
.edit-card-text {
color: var(--c-blue-3);
font-family: sans-serif;
font-family: 'font_regular', sans-serif;
}
.red-trash {
@ -580,9 +578,7 @@ $align: center;
background: var(--c-blue-3);
box-shadow: 0 0 1px rgb(9 28 69 / 80%);
border-radius: 8px;
font-family: sans-serif;
font-style: normal;
font-weight: 700;
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 24px;
align-items: $align;
@ -609,9 +605,7 @@ $align: center;
border: 1px solid var(--c-grey-3);
box-shadow: 0 0 3px rgb(0 0 0 / 8%);
border-radius: 8px;
font-family: sans-serif;
font-style: normal;
font-weight: 700;
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 24px;
display: $flex;
@ -673,9 +667,7 @@ $align: center;
&__header {
grid-column: 1;
grid-row: 2;
font-family: sans-serif;
font-style: normal;
font-weight: 800;
font-family: 'font_bold', sans-serif;
font-size: 24px;
line-height: 31px;
text-align: $align;
@ -687,9 +679,7 @@ $align: center;
&__header-subtext {
grid-column: 1;
grid-row: 3;
font-family: sans-serif;
font-style: normal;
font-weight: 400;
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 20px;
text-align: $align;
@ -698,9 +688,7 @@ $align: center;
&__header-subtext-default {
margin-left: 94px;
font-family: sans-serif;
font-style: normal;
font-weight: 400;
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 20px;
color: var(--c-grey-6);
@ -784,9 +772,7 @@ $align: center;
&__create-header {
grid-row: 1;
grid-column: 1;
font-family: sans-serif;
font-style: normal;
font-weight: 700;
font-family: 'font_bold', sans-serif;
font-size: 18px;
line-height: 27px;
}
@ -794,16 +780,14 @@ $align: center;
&__create-subheader {
grid-row: 2;
grid-column: 1;
font-family: sans-serif;
font-style: normal;
font-weight: 400;
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 20px;
color: var(--c-grey-6);
}
&__title {
font-family: sans-serif;
font-family: 'font_regular', sans-serif;
font-size: 24px;
margin: 20px 0;
@ -880,7 +864,7 @@ $align: center;
.pagination {
display: flex;
justify-content: space-between;
font-family: sans-serif;
font-family: 'font_regular', sans-serif;
padding: 15px 0;
color: #6b7280;
@ -1018,5 +1002,4 @@ $align: center;
nav ul {
@include horizontal-list;
}
</style>

View File

@ -1,247 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="coupon-area">
<div class="coupon-area__top-container">
<h1 class="coupon-area__top-container__title">Coupon</h1>
<VButton
v-if="couponCodeBillingUIEnabled"
class="coupon-area__top-container__add-button"
:on-press="onCreateClick"
label="Add Coupon Code"
/>
</div>
<VLoader v-if="isCouponFetching" />
<div v-else-if="coupon" class="coupon-area__container">
<CouponIcon class="coupon-area__container__coupon-icon" />
<div class="coupon-area__container__text-container">
<div class="coupon-area__container__text-container__row">
<p class="coupon-area__container__text-container__row__name">{{ coupon.name }}</p>
<p class="coupon-area__container__text-container__row__promo">{{ coupon.promoCode }}</p>
</div>
<div class="coupon-area__container__text-container__row">
<p class="coupon-area__container__text-container__row__description">{{ coupon.getDescription() }}</p>
</div>
<div class="coupon-area__container__text-container__row">
<p class="coupon-area__container__text-container__row__expiration">
Active from <b>{{ startDate }}</b><template v-if="endDate"> to <b>{{ endDate }}</b></template>
</p>
</div>
</div>
</div>
<div v-else-if="couponCodeBillingUIEnabled" class="coupon-area__container blue">
<CouponAddIcon class="coupon-area__container__coupon-icon" />
<div class="coupon-area__container__text-container">
<div class="coupon-area__container__text-container__row">
<p class="coupon-area__container__text-container__row__add-title">Add a Coupon to Get Started</p>
</div>
<div class="coupon-area__container__text-container__row">
<p class="coupon-area__container__text-container__row__add-subtitle">Your coupon will show up here.</p>
</div>
</div>
</div>
<div v-else class="coupon-area__container">
<CouponIcon class="coupon-area__container__coupon-icon" />
<div class="coupon-area__container__text-container">
<p class="coupon-area__container__text-container__missing">No coupon is applied to your account.</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { Coupon, CouponDuration } from '@/types/payments';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { useNotify, useStore } from '@/utils/hooks';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import VButton from '@/components/common/VButton.vue';
import VLoader from '@/components/common/VLoader.vue';
import CouponAddIcon from '@/../static/images/account/billing/couponAdd.svg';
import CouponIcon from '@/../static/images/account/billing/couponLarge.svg';
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const store = useStore();
const notify = useNotify();
const isCouponFetching = ref<boolean>(true);
/**
* Opens Add Coupon modal.
*/
function onCreateClick(): void {
analytics.eventTriggered(AnalyticsEvent.APPLY_NEW_COUPON_CLICKED);
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.addCoupon);
}
/**
* Returns the coupon applied to the user's account.
*/
const coupon = computed((): Coupon | null => {
return store.state.paymentsModule.coupon;
});
/**
* Indicates if coupon code ui is enabled on the billing page.
*/
const couponCodeBillingUIEnabled = computed((): boolean => {
return store.state.appStateModule.couponCodeBillingUIEnabled;
});
/**
* Returns the start date of the coupon.
*/
const startDate = computed((): string => {
return coupon?.value?.addedAt.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }) || '';
});
/**
* Returns the expiration date of the coupon.
*/
const endDate = computed((): string => {
if (!coupon.value) {
return '';
}
let date: Date;
if (coupon.value.duration === CouponDuration.Once) {
// Last day of billing period is last day of the month
date = new Date(coupon.value.addedAt.getFullYear(), coupon.value.addedAt.getMonth() + 1, 0);
} else if (coupon.value.duration === CouponDuration.Repeating && coupon.value.expiresAt) {
date = coupon.value.expiresAt;
} else {
return '';
}
return date.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
});
/**
* Returns the expiration date of the coupon.
*/
const expiration = computed((): string => {
if (!coupon.value) {
return '';
}
if (coupon.value.expiresAt) {
return 'Expires ' + coupon.value.expiresAt.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
} else {
switch (coupon.value.duration) {
case CouponDuration.Once:
return 'Expires after first use';
case CouponDuration.Forever:
return 'Never expires';
default:
return 'Unknown expiration';
}
}
});
/**
* Lifecycle hook after initial render.
* Fetches coupon.
*/
onMounted(async () => {
try {
await store.dispatch(PAYMENTS_ACTIONS.GET_COUPON);
isCouponFetching.value = false;
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_COUPON_AREA);
}
});
</script>
<style scoped lang="scss">
.coupon-area {
padding: 40px;
margin-bottom: 32px;
font-family: 'font_regular', sans-serif;
font-size: 16px;
background-color: #fff;
border-radius: 8px;
&__top-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;
color: #384b65;
}
&__add-button {
padding: 13px 16px;
}
}
&__container {
display: flex;
flex-direction: row;
align-items: center;
padding: 30px 40px;
border-radius: 6px;
background-color: #f5f6fa;
font-size: 16px;
color: #61666b;
&.blue {
background-color: #e9f3ff;
}
&__coupon-icon {
min-width: 64px;
}
&__text-container {
margin-left: 40px;
&__row {
display: flex;
flex-direction: row;
justify-content: flex-start;
margin-top: 10px;
&:first-child {
margin-top: 0;
}
&__promo {
margin-left: 12px;
color: #adadad;
}
&__description {
font-family: 'font_medium', sans-serif;
font-size: 22px;
}
&__add-title {
font-family: 'font_bold', sans-serif;
font-size: 22px;
color: #2683ff;
}
&__add-subtitle {
font-size: 14px;
color: #717e92;
}
}
}
}
}
</style>

View File

@ -1,177 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="history-area">
<div class="history-area__back-area" @click.stop="onBackToBillingClick">
<BackImage />
<p class="history-area__back-area__title">Back to Billing</p>
</div>
<h1 v-if="isBillingHistory" class="history-area__title">Billing History</h1>
<h1 v-else class="history-area__title">Balance History</h1>
<VLoader v-if="isDataFetching" height="100px" width="100px" class="history-loader" />
<template v-else>
<div v-if="historyItems.length > 0" class="history-area__content">
<SortingHeader />
<PaymentsItem
v-for="item in historyItems"
:key="item.id"
:billing-item="item"
/>
</div>
<h2 v-else class="history-area__empty-state">No Items Yet</h2>
</template>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue';
import { RouteConfig } from '@/router';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PaymentsHistoryItem, PaymentsHistoryItemType } from '@/types/payments';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useNotify, useRouter, useStore } from '@/utils/hooks';
import PaymentsItem from '@/components/account/billing/depositAndBillingHistory/PaymentsItem.vue';
import SortingHeader from '@/components/account/billing/depositAndBillingHistory/SortingHeader.vue';
import VLoader from '@/components/common/VLoader.vue';
import BackImage from '@/../static/images/account/billing/back.svg';
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const store = useStore();
const notify = useNotify();
const nativeRouter = useRouter();
const router = reactive(nativeRouter);
const isDataFetching = ref<boolean>(true);
/**
* Indicates if current route is billing history page.
*/
const isBillingHistory = computed((): boolean => {
return router.currentRoute.name === RouteConfig.BillingHistory.name;
});
/**
* Returns list of history items depending on route name.
*/
const historyItems = computed((): PaymentsHistoryItem[] => {
if (isBillingHistory.value) {
return store.state.paymentsModule.paymentsHistory.filter((item: PaymentsHistoryItem) => {
return item.type === PaymentsHistoryItemType.Invoice || item.type === PaymentsHistoryItemType.Charge;
});
}
return store.state.paymentsModule.paymentsHistory.filter((item: PaymentsHistoryItem) => {
return item.type === PaymentsHistoryItemType.Transaction || item.type === PaymentsHistoryItemType.DepositBonus;
});
});
/**
* Replaces location to root billing route.
*/
function onBackToBillingClick(): void {
analytics.pageVisit(RouteConfig.Billing.path);
router.push(RouteConfig.Billing.path);
}
/**
* Lifecycle hook after initial render.
* Fetches payments history.
*/
onMounted(async () => {
try {
await store.dispatch(PAYMENTS_ACTIONS.GET_PAYMENTS_HISTORY);
isDataFetching.value = false;
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_PAYMENTS_HISTORY);
}
});
</script>
<style scoped lang="scss">
p,
h1 {
margin: 0;
}
.history-area {
margin-top: 27px;
padding: 0 0 80px;
background-color: #f5f6fa;
font-family: 'font_regular', sans-serif;
&__back-area {
display: flex;
align-items: center;
cursor: pointer;
width: 184px;
margin-bottom: 32px;
&__title {
font-family: 'font_medium', sans-serif;
font-weight: 500;
font-size: 16px;
line-height: 21px;
color: #768394;
white-space: nowrap;
margin-left: 15px;
}
&:hover {
.history-area__back-area__title {
color: #2683ff;
}
.back-button-svg-path {
fill: #2683ff;
}
}
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 22px;
line-height: 27px;
color: #384b65;
margin-bottom: 20px;
}
&__content {
background-color: #fff;
padding: 30px 40px 0;
border-radius: 8px;
}
&__empty-state {
font-size: 40px;
line-height: 46px;
text-align: center;
margin-top: 200px;
}
}
.history-loader {
margin-top: 50px;
}
::-webkit-scrollbar,
::-webkit-scrollbar-track,
::-webkit-scrollbar-thumb {
width: 0;
}
@media (max-height: 1000px) and (max-width: 1230px) {
.history-area {
overflow-y: scroll;
height: 65vh;
}
}
</style>

View File

@ -1,107 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="countdown-container">
<div v-if="isExpired">{{ expireDate }}</div>
<div v-else class="row">
<p>Expires in </p>
<p class="digit margin">{{ expirationTimer.hours | leadingZero }}</p>
<p>:</p>
<p class="digit">{{ expirationTimer.minutes | leadingZero }}</p>
<p>:</p>
<p class="digit">{{ expirationTimer.seconds | leadingZero }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeMount, ref } from 'vue';
import { PaymentsHistoryItem, PaymentsHistoryItemStatus, PaymentsHistoryItemType } from '@/types/payments';
const props = withDefaults(defineProps<{
billingItem?: PaymentsHistoryItem,
}>(), {
billingItem: () => new PaymentsHistoryItem(),
});
const { end, start, type, status } = props.billingItem;
const nowInSeconds = ref<number>(Math.trunc(new Date().getTime() / 1000));
const expirationTimeInSeconds = ref<number>(0);
const intervalID = ref<ReturnType<typeof setInterval>>();
/**
* indicates if billing item is expired.
*/
const isExpired = ref<boolean>(false);
/**
* String representation of creation date.
*/
const expireDate = computed((): string => {
return start.toLocaleString('default', { month: 'long', day: '2-digit', year: 'numeric' });
});
/**
* Seconds count for expiration timer.
*/
const expirationTimer = computed((): { seconds: number, minutes: number, hours: number } => {
return {
seconds: (expirationTimeInSeconds.value - nowInSeconds.value) % 60,
minutes: Math.trunc((expirationTimeInSeconds.value - nowInSeconds.value) / 60) % 60,
hours: Math.trunc((expirationTimeInSeconds.value - nowInSeconds.value) / 3600) % 24,
};
});
/**
* Indicates if transaction status is completed, paid or cancelled.
*/
const isTransactionCompleted = computed((): boolean => {
return status !== PaymentsHistoryItemStatus.Pending;
});
/**
* Starts expiration timer if item is not expired.
*/
function ready(): void {
intervalID.value = setInterval(() => {
if ((expirationTimeInSeconds.value - nowInSeconds.value) < 0 || isTransactionCompleted.value) {
isExpired.value = true;
intervalID.value && clearInterval(intervalID.value);
return;
}
nowInSeconds.value = Math.trunc(new Date().getTime() / 1000);
}, 1000);
}
onBeforeMount(() => {
expirationTimeInSeconds.value = Math.trunc(new Date(end).getTime() / 1000);
isExpired.value = (expirationTimeInSeconds.value - nowInSeconds.value) < 0;
if (type === PaymentsHistoryItemType.Transaction) {
isExpired.value = isTransactionCompleted.value;
}
ready();
});
</script>
<style scoped lang="scss">
.digit {
font-family: 'font_bold', sans-serif;
}
.margin {
margin-left: 5px;
}
.row {
display: flex;
align-items: center;
justify-content: flex-start;
}
</style>

View File

@ -1,102 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="container">
<PaymentsHistoryItemDate
class="container__item date"
:billing-item="billingItem"
/>
<p class="container__item description">{{ billingItem.description }}</p>
<p class="container__item status">{{ billingItem.formattedStatus }}</p>
<p class="container__item amount">
<b>
{{ billingItem.quantity.currency }}
<span v-if="billingItem.type === 1">
{{ billingItem.quantity.received.toFixed(2) }}
</span>
<span v-else>
{{ billingItem.quantity.total.toFixed(2) }}
</span>
</b>
<span v-if="billingItem.type === 1">
of <b>{{ billingItem.quantity.total.toFixed(2) }}</b>
</span>
</p>
<p class="container__item download">
<a v-if="billingItem.link" class="download-link" target="_blank" :href="billingItem.link">{{ billingItem.label }}</a>
</p>
</div>
</template>
<script setup lang="ts">
import { PaymentsHistoryItem } from '@/types/payments';
import PaymentsHistoryItemDate from '@/components/account/billing/depositAndBillingHistory/PaymentsHistoryItemDate.vue';
const props = withDefaults(defineProps<{
billingItem?: PaymentsHistoryItem;
}>(), {
billingItem: () => new PaymentsHistoryItem(),
});
</script>
<style scoped lang="scss">
.download-link {
color: #2683ff;
font-family: 'font_bold', sans-serif;
&:hover {
color: #0059d0;
}
}
.container {
display: flex;
align-items: center;
width: 100%;
border-top: 1px solid #c7cdd2;
&__item {
min-width: 20%;
font-family: 'font_medium', sans-serif;
font-size: 16px;
text-align: left;
color: #768394;
margin: 30px 0;
}
}
.date {
font-family: 'font_bold', sans-serif;
margin: 0;
}
.description {
min-width: 31%;
}
.status {
min-width: 17%;
}
.amount {
min-width: 22%;
margin: 0;
}
.download {
margin: 0;
text-align: right;
width: 10%;
min-width: 10%;
}
.row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 175px;
}
</style>

View File

@ -1,229 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="period-selection" @click.stop="toggleDropdown">
<div class="period-selection__current-choice">
<div class="period-selection__current-choice__label-area">
<DatePickerIcon />
<span class="period-selection__current-choice__label-area__label">{{ currentOption }}</span>
</div>
<ExpandIcon v-if="!isDropdownShown" />
<HideIcon v-else />
</div>
<div v-show="isDropdownShown" v-click-outside="closeDropdown" class="period-selection__dropdown">
<div
v-for="(option, index) in periodOptions"
:key="index"
class="period-selection__dropdown__item"
@click.prevent.stop="select(option)"
>
<SelectedIcon v-if="isOptionSelected(option)" class="selected-image" />
<span class="period-selection__dropdown__item__label">{{ option }}</span>
</div>
<div class="period-selection__dropdown__link-container" @click="redirect">
<span class="period-selection__dropdown__link-container__link">Billing History</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { RouteConfig } from '@/router';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useNotify, useRouter, useStore } from '@/utils/hooks';
import { APP_STATE_DROPDOWNS } from '@/utils/constants/appStatePopUps';
import DatePickerIcon from '@/../static/images/account/billing/datePicker.svg';
import SelectedIcon from '@/../static/images/account/billing/selected.svg';
import ExpandIcon from '@/../static/images/common/BlueExpand.svg';
import HideIcon from '@/../static/images/common/BlueHide.svg';
const periodOptions: string[] = [
'Current Billing Period',
'Previous Billing Period',
];
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const store = useStore();
const router = useRouter();
const notify = useNotify();
const currentOption = ref<string>(periodOptions[0]);
/**
* Indicates if periods dropdown is shown.
*/
const isDropdownShown = computed((): boolean => {
return store.state.appStateModule.viewsState.activeDropdown === APP_STATE_DROPDOWNS.PERIODS;
});
/**
* Returns start date of billing period from store.
*/
const startDate = computed((): Date => {
return store.state.paymentsModule.startDate;
});
/**
* Returns end date of billing period from store.
*/
const endDate = computed((): Date => {
return store.state.paymentsModule.endDate;
});
/**
* Indicates if option is selected.
* @param option - option string.
*/
function isOptionSelected(option: string): boolean {
return option === currentOption.value;
}
/**
* Closes dropdown.
*/
function closeDropdown(): void {
if (!isDropdownShown.value) return;
store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
}
/**
* Toggles dropdown visibility.
*/
function toggleDropdown(): void {
store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACTIVE_DROPDOWN, APP_STATE_DROPDOWNS.PERIODS);
}
/**
* Holds logic to redirect user to billing history page.
*/
function redirect(): void {
analytics.pageVisit(RouteConfig.Account.with(RouteConfig.BillingHistory).path);
router.push(RouteConfig.Account.with(RouteConfig.BillingHistory).path);
}
/**
* Sets billing state to previous billing period.
*/
async function onPreviousPeriodClick(): Promise<void> {
try {
await store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_PREVIOUS_ROLLUP);
} catch (error) {
await notify.error(`Unable to fetch project charges. ${error.message}`, AnalyticsErrorEventSource.BILLING_PERIODS_SELECTION);
}
}
/**
* Sets billing state to current billing period.
*/
async function onCurrentPeriodClick(): Promise<void> {
try {
await store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
} catch (error) {
await notify.error(`Unable to fetch project charges. ${error.message}`, AnalyticsErrorEventSource.BILLING_PERIODS_SELECTION);
}
}
/**
* Holds logic for select option click.
* @param option - option string.
*/
async function select(option: string): Promise<void> {
if (option === periodOptions[0]) {
await onCurrentPeriodClick();
}
if (option === periodOptions[1]) {
await onPreviousPeriodClick();
}
currentOption.value = option;
closeDropdown();
}
</script>
<style scoped lang="scss">
.period-selection {
padding: 15px;
width: 260px;
background-color: #fff;
position: relative;
font-family: 'font_regular', sans-serif;
border-radius: 6px;
cursor: pointer;
&__current-choice {
display: flex;
align-items: center;
justify-content: space-between;
&__label-area {
display: flex;
align-items: center;
&__label {
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 16px;
margin-left: 15px;
}
}
}
&__dropdown {
z-index: 120;
position: absolute;
left: 0;
top: 55px;
background-color: #fff;
border-radius: 6px;
border: 1px solid #c5cbdb;
box-shadow: 0 8px 34px rgb(161 173 185 / 41%);
width: 290px;
&__item {
padding: 15px;
&__label {
font-size: 14px;
line-height: 19px;
color: #494949;
}
&:hover {
background-color: #f5f5f7;
}
}
&__link-container {
width: calc(100% - 30px);
height: 50px;
padding: 0 15px;
display: flex;
align-items: center;
&:hover {
background-color: #f5f5f7;
}
&__link {
font-size: 14px;
line-height: 19px;
color: #7e8b9c;
}
}
}
}
.selected-image {
margin-right: 10px;
}
</style>

View File

@ -1,97 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div v-if="depositHistoryItems.length > 0" class="deposit-area">
<div class="deposit-area__header">
<h1 class="deposit-area__header__title">Short Balance History</h1>
<div class="deposit-area__header__button" @click.stop="onViewAllClick">View All</div>
</div>
<SortingHeader />
<PaymentsItem
v-for="item in depositHistoryItems"
:key="item.id"
:billing-item="item"
/>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { RouteConfig } from '@/router';
import { PaymentsHistoryItem, PaymentsHistoryItemType } from '@/types/payments';
import { AnalyticsHttpApi } from '@/api/analytics';
import { useRouter, useStore } from '@/utils/hooks';
import SortingHeader from '@/components/account/billing/depositAndBillingHistory/SortingHeader.vue';
import PaymentsItem from '@/components/account/billing/depositAndBillingHistory/PaymentsItem.vue';
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const store = useStore();
const router = useRouter();
/**
* Returns first 3 of deposit history items.
*/
const depositHistoryItems = computed((): PaymentsHistoryItem[] => {
return store.state.paymentsModule.paymentsHistory.filter((item: PaymentsHistoryItem) => {
return item.type === PaymentsHistoryItemType.Transaction || item.type === PaymentsHistoryItemType.DepositBonus;
}).slice(0, 3);
});
/**
* Changes location to deposit history route.
*/
function onViewAllClick(): void {
analytics.pageVisit(RouteConfig.Account.with(RouteConfig.DepositHistory).path);
router.push(RouteConfig.Account.with(RouteConfig.DepositHistory).path);
}
</script>
<style scoped lang="scss">
h1,
span {
margin: 0;
color: #354049;
}
.deposit-area {
padding: 40px 40px 10px;
background-color: #fff;
border-radius: 8px;
font-family: 'font_regular', sans-serif;
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40px;
font-family: 'font_bold', sans-serif;
&__title {
font-size: 28px;
line-height: 42px;
}
&__button {
display: flex;
width: 120px;
height: 48px;
border: 1px solid #afb7c1;
border-radius: 8px;
align-items: center;
justify-content: center;
font-size: 16px;
color: #354049;
cursor: pointer;
&:hover {
background-color: #2683ff;
color: #fff;
}
}
}
}
</style>

View File

@ -1,64 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="sort-header-container">
<div class="sort-header-container__item date">
<p class="sort-header-container__item__name">DATE</p>
</div>
<div class="sort-header-container__item description">
<p class="sort-header-container__item__name">DESCRIPTION</p>
</div>
<div class="sort-header-container__item status">
<p class="sort-header-container__item__name">STATUS</p>
</div>
<div class="sort-header-container__item amount">
<p class="sort-header-container__item__name">AMOUNT</p>
</div>
<div class="sort-header-container__item download">
<p class="sort-header-container__item__name">DOWNLOAD</p>
</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
.sort-header-container {
display: flex;
width: 100%;
padding-bottom: 32px;
&__item {
text-align: left;
min-width: 20%;
&__name {
font-family: 'font_medium', sans-serif;
font-size: 14px;
line-height: 19px;
color: #adadad;
margin: 0;
}
}
}
.description {
min-width: 31%;
}
.status {
min-width: 17%;
}
.amount {
min-width: 22%;
margin: 0;
}
.download {
text-align: right;
margin: 0;
min-width: 10%;
}
</style>

View File

@ -1,156 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="current-month-area">
<VLoader v-if="isDataFetching" class="consts-loader" />
<template v-else>
<h1 class="current-month-area__costs">{{ priceSummary | centsToDollars }}</h1>
<span class="current-month-area__title">Estimated Charges for {{ chosenPeriod }}</span>
<p class="current-month-area__info">
If you still have Storage and Bandwidth remaining in your free tier, you wont be charged. This information
is to help you estimate what charges would have been had you graduated to the paid tier.
</p>
<div class="current-month-area__content">
<p class="current-month-area__content__title">DETAILS</p>
<UsageAndChargesItem
v-for="usageAndCharges in projectUsageAndCharges"
:key="usageAndCharges.projectId"
:item="usageAndCharges"
class="item"
/>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { ProjectUsageAndCharges } from '@/types/payments';
import { MONTHS_NAMES } from '@/utils/constants/date';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useNotify, useStore } from '@/utils/hooks';
import VLoader from '@/components/common/VLoader.vue';
import UsageAndChargesItem from '@/components/account/billing/estimatedCostsAndCredits/UsageAndChargesItem.vue';
const store = useStore();
const notify = useNotify();
const isDataFetching = ref<boolean>(true);
/**
* projectUsageAndCharges is an array of all stored ProjectUsageAndCharges.
*/
const projectUsageAndCharges = computed((): ProjectUsageAndCharges[] => {
return store.state.paymentsModule.usageAndCharges;
});
/**
* priceSummary returns price summary of usages for all the projects.
*/
const priceSummary = computed((): number => {
return store.state.paymentsModule.priceSummary;
});
/**
* chosenPeriod returns billing period chosen by user.
*/
const chosenPeriod = computed((): string => {
const dateFromStore = store.state.paymentsModule.startDate;
return `${MONTHS_NAMES[dateFromStore.getUTCMonth()]} ${dateFromStore.getUTCFullYear()}`;
});
/**
* Lifecycle hook after initial render.
* Fetches projects and usage rollup.
*/
onMounted(async () => {
try {
await store.dispatch(PROJECTS_ACTIONS.FETCH);
} catch (error) {
isDataFetching.value = false;
notify.error(error.message, AnalyticsErrorEventSource.BILLING_ESTIMATED_COSTS_AND_CREDITS);
return;
}
try {
await store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
await store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_PRICE_MODEL);
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.BILLING_ESTIMATED_COSTS_AND_CREDITS);
}
isDataFetching.value = false;
});
</script>
<style scoped lang="scss">
h1,
h2,
p,
span {
margin: 0;
color: #354049;
}
.current-month-area {
margin-bottom: 32px;
padding: 40px 40px 0;
background-color: #fff;
border-radius: 8px;
font-family: 'font_regular', sans-serif;
&__costs {
font-size: 36px;
line-height: 53px;
color: #384b65;
font-family: 'font_medium', sans-serif;
}
&__title {
font-size: 16px;
line-height: 24px;
color: #909090;
}
&__info {
font-size: 14px;
line-height: 20px;
color: #909090;
margin: 15px 0 0;
}
&__content {
margin-top: 35px;
&__title {
font-size: 16px;
line-height: 23px;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #919191;
margin-bottom: 25px;
}
&__usage-charges {
margin: 18px 0 0;
background-color: #f5f6fa;
border-radius: 12px;
cursor: pointer;
}
}
}
.item {
border-top: 1px solid #c7cdd2;
}
.consts-loader {
padding-bottom: 40px;
}
</style>

View File

@ -1,291 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="usage-charges-item-container">
<div class="usage-charges-item-container__summary" @click="toggleDetailedInfo">
<div class="usage-charges-item-container__summary__name-container">
<ChargesExpandIcon v-if="isDetailedInfoShown" />
<ChargesHideIcon v-else />
<span>{{ projectName }}</span>
</div>
<span class="usage-charges-item-container__summary__amount">
Estimated Total {{ item.summary() | centsToDollars }}
</span>
</div>
<div v-if="isDetailedInfoShown" class="usage-charges-item-container__detailed-info-container">
<div class="usage-charges-item-container__detailed-info-container__info-header">
<span class="resource-header">RESOURCE</span>
<span class="period-header">PERIOD</span>
<span class="usage-header">USAGE</span>
<span class="cost-header">COST</span>
</div>
<div class="usage-charges-item-container__detailed-info-container__content-area">
<div class="usage-charges-item-container__detailed-info-container__content-area__resource-container">
<p>Storage ({{ storagePrice }} per Gigabyte-Month)</p>
<p>Egress ({{ egressPrice }} per GB)</p>
<p>Segments ({{ segmentPrice }} per Segment-Month)</p>
</div>
<div class="usage-charges-item-container__detailed-info-container__content-area__period-container">
<p>{{ period }}</p>
<p>{{ period }}</p>
<p>{{ period }}</p>
</div>
<div class="usage-charges-item-container__detailed-info-container__content-area__usage-container">
<p>{{ storageFormatted }} Gigabyte-month</p>
<p>{{ egressAmountAndDimension }}</p>
<p>{{ segmentCountFormatted }} Segment-month</p>
</div>
<div class="usage-charges-item-container__detailed-info-container__content-area__cost-container">
<p class="price">{{ item.storagePrice | centsToDollars }}</p>
<p class="price">{{ item.egressPrice | centsToDollars }}</p>
<p class="price">{{ item.segmentPrice | centsToDollars }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { ProjectUsageAndCharges, ProjectUsagePriceModel } from '@/types/payments';
import { Project } from '@/types/projects';
import { Size } from '@/utils/bytesSize';
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
import { decimalShift, formatPrice, CENTS_MB_TO_DOLLARS_GB_SHIFT } from '@/utils/strings';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { useStore } from '@/utils/hooks';
import ChargesHideIcon from '@/../static/images/account/billing/chargesHide.svg';
import ChargesExpandIcon from '@/../static/images/account/billing/chargesExpand.svg';
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* HOURS_IN_MONTH constant shows amount of hours in 30-day month.
*/
const HOURS_IN_MONTH = 720;
const props = withDefaults(defineProps<{
/**
* item represents usage and charges of current project by period.
*/
item?: ProjectUsageAndCharges;
}>(), {
item: () => new ProjectUsageAndCharges(),
});
const store = useStore();
/**
* isDetailedInfoShown indicates if area with detailed information about project charges is expanded.
*/
const isDetailedInfoShown = ref<boolean>(false);
/**
* projectName returns project name.
*/
const projectName = computed((): string => {
const projects: Project[] = store.state.projectsModule.projects;
const project: Project = projects.find(project => project.id === props.item.projectId);
return project?.name || '';
});
/**
* Returns string of date range.
*/
const period = computed((): string => {
const since = `${SHORT_MONTHS_NAMES[props.item.since.getUTCMonth()]} ${props.item.since.getUTCDate()}`;
const before = `${SHORT_MONTHS_NAMES[props.item.before.getUTCMonth()]} ${props.item.before.getUTCDate()}`;
return `${since} - ${before}`;
});
/**
* Returns project usage price model from store.
*/
const priceModel = computed((): ProjectUsagePriceModel => {
return store.state.paymentsModule.usagePriceModel;
});
/**
* Returns formatted egress depending on amount of bytes.
*/
const egressFormatted = computed((): Size => {
return new Size(props.item.egress, 2);
});
/**
* Returns formatted storage used in GB x month dimension.
*/
const storageFormatted = computed((): string => {
const bytesInGB = 1000000000;
return (props.item.storage / HOURS_IN_MONTH / bytesInGB).toFixed(2);
});
/**
* Returns formatted segment count in segment x month dimension.
*/
const segmentCountFormatted = computed((): string => {
return (props.item.segmentCount / HOURS_IN_MONTH).toFixed(2);
});
/**
* Returns storage price per GB.
*/
const storagePrice = computed((): string => {
return formatPrice(decimalShift(priceModel.value.storageMBMonthCents, CENTS_MB_TO_DOLLARS_GB_SHIFT));
});
/**
* Returns egress price per GB.
*/
const egressPrice = computed((): string => {
return formatPrice(decimalShift(priceModel.value.egressMBCents, CENTS_MB_TO_DOLLARS_GB_SHIFT));
});
/**
* Returns segment price.
*/
const segmentPrice = computed((): string => {
return formatPrice(decimalShift(priceModel.value.segmentMonthCents, 2));
});
/**
* Returns string of egress amount and dimension.
*/
const egressAmountAndDimension = computed((): string => {
return `${egressFormatted.value.formattedBytes} ${egressFormatted.value.label}`;
});
/**
* toggleDetailedInfo expands an area with detailed information about project charges.
*/
function toggleDetailedInfo(): void {
analytics.eventTriggered(AnalyticsEvent.USAGE_DETAILED_INFO_CLICKED);
isDetailedInfoShown.value = !isDetailedInfoShown.value;
}
</script>
<style scoped lang="scss">
p {
margin: 0;
}
.usage-charges-item-container {
font-size: 16px;
line-height: 21px;
padding: 30px 0;
font-family: 'font_regular', sans-serif;
&__summary {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
&__name-container {
display: flex;
align-items: center;
&__expand-image,
&__hide-image {
width: 14px;
height: 14px;
margin-right: 12px;
}
&__expand-image {
height: 8px;
}
}
&__amount {
font-size: 16px;
line-height: 21px;
text-align: right;
color: #354049;
}
}
&__detailed-info-container {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
padding: 16px 0 0 26px;
&__info-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
line-height: 19px;
color: #adadad;
height: 25px;
width: 100%;
}
&__content-area {
width: 100%;
padding: 10px 0 0;
display: flex;
align-items: center;
justify-content: space-between;
&__resource-container,
&__period-container,
&__cost-container,
&__usage-container {
width: 20%;
font-size: 14px;
line-height: 19px;
color: #354049;
:nth-child(1),
:nth-child(2) {
margin-bottom: 3px;
}
}
&__resource-container {
width: 40%;
}
}
&__link-container {
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 25px;
&__link {
font-size: 13px;
line-height: 19px;
color: #2683ff;
cursor: pointer;
}
}
}
}
.resource-header {
width: 40%;
}
.cost-header,
.period-header,
.usage-header {
width: 20%;
}
.cost-header,
.price {
text-align: right;
}
</style>

View File

@ -1,138 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="add-card-area">
<p class="add-card-area__label">Add Credit or Debit Card</p>
<StripeCardInput
ref="stripeCardInput"
class="add-card-area__stripe"
:on-stripe-response-callback="addCard"
/>
<div class="add-card-area__submit-area" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { USER_ACTIONS } from '@/store/modules/users';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import StripeCardInput from '@/components/account/billing/paymentMethods/StripeCardInput.vue';
const {
ADD_CREDIT_CARD,
GET_CREDIT_CARDS,
} = PAYMENTS_ACTIONS;
interface StripeForm {
onSubmit(): Promise<void>;
}
// @vue/component
@Component({
components: {
StripeCardInput,
},
})
export default class AddCardForm extends Vue {
public $refs!: {
stripeCardInput: StripeCardInput & StripeForm;
};
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Adds card after Stripe confirmation.
*
* @param token from Stripe
*/
public async addCard(token: string): Promise<void> {
this.$emit('toggleIsLoading');
try {
await this.$store.dispatch(ADD_CREDIT_CARD, token);
// We fetch User one more time to update their Paid Tier status.
await this.$store.dispatch(USER_ACTIONS.GET);
await this.$store.dispatch(USER_ACTIONS.GET_FROZEN_STATUS);
} catch (error) {
await this.$notify.error(error.message, AnalyticsErrorEventSource.BILLING_ADD_STRIPE_CC_FORM);
this.$emit('toggleIsLoading');
return;
}
await this.$notify.success('Card successfully added. There can be a delay before changes related to changing your limits will take effect.');
try {
await this.$store.dispatch(GET_CREDIT_CARDS);
} catch (error) {
await this.$notify.error(error.message, AnalyticsErrorEventSource.BILLING_ADD_STRIPE_CC_FORM);
this.$emit('toggleIsLoading');
}
this.$emit('toggleIsLoading');
this.$emit('toggleIsLoaded');
setTimeout(() => {
this.$emit('cancel');
this.$emit('toggleIsLoaded');
setTimeout(() => {
if (!this.userHasOwnProject) {
this.analytics.pageVisit(RouteConfig.CreateProject.path);
this.$router.push(RouteConfig.CreateProject.path);
}
}, 500);
}, 2000);
}
/**
* Provides card information to Stripe.
*/
public async onConfirmAddStripe(): Promise<void> {
await this.$refs.stripeCardInput.onSubmit();
}
/**
* Indicates if user has own project.
*/
private get userHasOwnProject(): boolean {
return this.$store.getters.projectsCount > 0;
}
}
</script>
<style scoped lang="scss">
.add-card-area {
margin-top: 44px;
display: flex;
max-height: 52px;
justify-content: space-between;
align-items: center;
font-family: 'font_regular', sans-serif;
&__label {
font-family: 'font_medium', sans-serif;
font-size: 21px;
}
&__stripe {
width: 60%;
min-width: 400px;
}
&__submit-area {
display: flex;
align-items: center;
min-width: 135px;
}
}
</style>

View File

@ -1,68 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="add-storj-area">
<p class="add-storj-area__support-info">To deposit STORJ token and request higher limits, please contact <a target="_blank" rel="noopener noreferrer" href="https://supportdcs.storj.io/hc/en-us/requests/new?ticket_form_id=360000683212">Support</a></p>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
// @vue/component
@Component
export default class AddStorjForm extends Vue {}
</script>
<style scoped lang="scss">
p {
margin: 0;
}
.add-storj-area {
margin: 20px 0;
font-family: 'font_regular', sans-serif;
display: flex;
max-height: 52px;
justify-content: space-between;
align-items: center;
&__selection-container {
display: flex;
align-items: center;
&__label {
margin-right: 30px;
max-width: 215px;
}
&__form {
width: 60%;
}
}
&__submit-area {
display: flex;
align-items: center;
min-width: 135px;
}
&__support-info {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #000;
a {
color: var(--c-blue-3);
}
}
}
.loading-image {
width: 18px;
height: 18px;
margin-right: 5px;
}
</style>

View File

@ -1,321 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="token">
<div class="token__large-icon-container">
<div class="token__large-icon">
<StorjLarge />
</div>
</div>
<div v-if="!showAddFunds" class="token__base">
<div class="token__base__small-icon">
<StorjSmall />
</div>
<div class="token__base__confirmation-container">
<p class="token__base__confirmation-container__label">STORJ Token Deposit</p>
<span :class="`token__base__confirmation-container__circle-icon ${billingItem.formattedStatus}`">
&#9679;
</span>
<span class="token__base__confirmation-container__text">
<span>
{{ billingItem.formattedStatus }}
</span>
</span>
</div>
<div class="token__base__balance-container">
<p class="token__base__balance-container__label">Total Balance</p>
<span class="token__base__balance-container__text">
USD ${{ billingItem.quantity.received.toFixed(2) }}
</span>
</div>
<VButton
label="See transactions"
width="120px"
height="30px"
:is-transparent="true"
font-size="13px"
class="token__base__transaction-button"
:on-press="toggleTransactionsTable"
/>
<VButton
label="Add funds"
font-size="13px"
width="80px"
height="30px"
class="token__base__funds-button"
:on-press="toggleShowAddFunds"
/>
</div>
<div v-else class="token__add-funds">
<h3 class="token__add-funds__title">
STORJ Token
</h3>
<p class="token__add-funds__support-info">To deposit STORJ token and request higher limits, please contact <a target="_blank" rel="noopener noreferrer" href="https://supportdcs.storj.io/hc/en-us/requests/new?ticket_form_id=360000683212">Support</a></p>
<VButton
label="Back"
width="100px"
height="30px"
:is-transparent="true"
font-size="13px"
class="token__base__transaction-button"
:on-press="toggleShowAddFunds"
/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { PaymentsHistoryItem } from '@/types/payments';
import VButton from '@/components/common/VButton.vue';
import StorjSmall from '@/../static/images/billing/storj-icon-small.svg';
import StorjLarge from '@/../static/images/billing/storj-icon-large.svg';
// @vue/component
@Component({
components: {
StorjSmall,
StorjLarge,
VButton,
},
})
export default class BalanceTokenCard extends Vue {
@Prop({ default: () => new PaymentsHistoryItem() })
private readonly billingItem: PaymentsHistoryItem;
private showAddFunds = false;
public toggleShowAddFunds(): void {
this.showAddFunds = !this.showAddFunds;
}
public toggleTransactionsTable(): void {
this.$emit('showTransactions');
}
}
</script>
<style scoped lang="scss">
.Confirmed {
color: var(--c-green-5);
}
.Rejected {
color: #ac1a00;
}
.Pending {
color: var(--c-yellow-4);
}
.token {
border-radius: 10px;
width: 348px;
height: 203px;
box-sizing: border-box;
padding: 24px;
box-shadow: 0 0 20px rgb(0 0 0 / 4%);
background: #fff;
position: relative;
font-family: 'font_regular', sans-serif;
&__large-icon-container {
position: absolute;
top: 0;
right: 0;
height: 120px;
width: 120px;
z-index: 1;
border-radius: 10px;
overflow: hidden;
}
&__large-icon {
position: absolute;
top: -25px;
right: -24px;
}
&__base {
display: grid;
grid-template-columns: 1.5fr 1fr;
grid-template-rows: 4fr 1fr 1fr;
overflow: hidden;
font-family: sans-serif;
z-index: 5;
position: relative;
height: 100%;
width: 100%;
&__small-icon {
grid-column: 1;
grid-row: 1;
height: 30px;
width: 40px;
background-color: var(--c-blue-1);
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
}
&__confirmation-container {
grid-column: 1;
grid-row: 2;
display: grid;
grid-template-columns: 1fr 6fr;
grid-template-rows: 1fr 1fr;
&__label {
font-size: 12px;
font-weight: 700;
color: var(--c-grey-6);
grid-column: 1/ span 2;
grid-row: 1;
margin: auto 0 0;
}
&__circle-icon {
grid-column: 1;
grid-row: 2;
margin: auto;
}
&__text {
font-size: 16px;
font-weight: 700;
grid-column: 2;
grid-row: 2;
margin: auto 0;
}
}
&__balance-container {
grid-column: 2;
grid-row: 2;
display: grid;
grid-template-rows: 1fr 1fr;
&__label {
font-size: 12px;
font-weight: 700;
color: var(--c-grey-6);
grid-row: 1;
margin: auto 0 0;
}
&__text {
font-size: 16px;
font-weight: 700;
grid-row: 2;
margin: auto 0;
}
}
&__transaction-button {
grid-column: 1;
grid-row: 4;
}
&__funds-button {
grid-column: 2;
grid-row: 4;
}
}
&__confirmation-container {
grid-column: 1;
grid-row: 2;
z-index: 3;
display: grid;
grid-template-columns: 1fr 6fr;
grid-template-rows: 1fr 1fr;
&__label {
font-size: 12px;
font-weight: 700;
color: var(--c-grey-6);
grid-column: 1/ span 2;
grid-row: 1;
margin: auto 0 0;
}
&__circle-icon {
grid-column: 1;
grid-row: 2;
margin: auto;
}
&__text {
font-size: 16px;
font-weight: 700;
grid-column: 2;
grid-row: 2;
margin: auto 0;
}
}
&__balance-container {
grid-column: 2;
grid-row: 2;
z-index: 3;
display: grid;
grid-template-rows: 1fr 1fr;
&__label {
font-size: 12px;
font-weight: 700;
color: var(--c-grey-6);
grid-row: 1;
margin: auto 0 0;
}
&__text {
font-size: 16px;
font-weight: 700;
grid-row: 2;
margin: auto 0;
}
}
&__transaction-button {
grid-column: 1;
grid-row: 4;
z-index: 3;
}
&__funds-button {
grid-column: 2;
grid-row: 4;
z-index: 3;
}
&__add-funds {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
width: 100%;
&__title {
font-family: 'font_bold', sans-serif;
}
&__support-info {
font-size: 14px;
line-height: 20px;
color: #000;
z-index: 1;
a {
color: var(--c-blue-3);
text-decoration: underline !important;
}
}
}
}
</style>

View File

@ -1,214 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="payment-methods-container__card-container">
<div class="payment-methods-container__card-container__info-area">
<div class="payment-methods-container__card-container__info-area__card-logo">
<component :is="cardIcon" />
</div>
<div class="payment-methods-container__card-container__info-area__info-container">
<img src="@/../static/images/payments/cardStars.png" alt="Hidden card digits stars image">
<h1 class="bold">{{ creditCard.last4 }}</h1>
</div>
<div class="payment-methods-container__card-container__info-area__expire-container">
<h2 class="medium">Expires</h2>
<h1 class="bold">{{ creditCard.expMonth }}/{{ creditCard.expYear }}</h1>
</div>
</div>
<div class="payment-methods-container__card-container__button-area">
<div v-if="creditCard.isDefault" class="payment-methods-container__card-container__default-button">
<p class="payment-methods-container__card-container__default-button__label">Default</p>
</div>
<div v-else class="payment-methods-container__card-container__dots-container">
<div @click.stop="toggleSelection">
<svg width="20" height="4" viewBox="0 0 20 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="4" height="4" rx="2" fill="#354049" />
<rect x="8" width="4" height="4" rx="2" fill="#354049" />
<rect x="16" width="4" height="4" rx="2" fill="#354049" />
</svg>
</div>
<CardDialog
v-if="creditCard.isSelected"
:card-id="creditCard.id"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue, { VueConstructor } from 'vue';
import { Component, Prop } from 'vue-property-decorator';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { CreditCard } from '@/types/payments';
import CardDialog from '@/components/account/billing/paymentMethods/CardDialog.vue';
import AmericanExpressIcon from '@/../static/images/payments/cardIcons/americanexpress.svg';
import DefaultIcon from '@/../static/images/payments/cardIcons/default.svg';
import DinersIcon from '@/../static/images/payments/cardIcons/dinersclub.svg';
import DiscoverIcon from '@/../static/images/payments/cardIcons/discover.svg';
import JCBIcon from '@/../static/images/payments/cardIcons/jcb.svg';
import MastercardIcon from '@/../static/images/payments/cardIcons/mastercard.svg';
import UnionPayIcon from '@/../static/images/payments/cardIcons/unionpay.svg';
import VisaIcon from '@/../static/images/payments/cardIcons/visa.svg';
const {
TOGGLE_CARD_SELECTION,
} = PAYMENTS_ACTIONS;
// @vue/component
@Component({
components: {
CardDialog,
JCBIcon,
DinersIcon,
MastercardIcon,
AmericanExpressIcon,
DiscoverIcon,
UnionPayIcon,
VisaIcon,
DefaultIcon,
},
})
export default class CardComponent extends Vue {
@Prop({ default: () => new CreditCard() })
private readonly creditCard: CreditCard;
// TODO: move to CreditCard
/**
* Returns card logo depends of brand.
*/
public get cardIcon(): VueConstructor<Vue> {
switch (this.creditCard.brand) {
case 'jcb':
return JCBIcon;
case 'diners':
return DinersIcon;
case 'mastercard':
return MastercardIcon;
case 'amex':
return AmericanExpressIcon;
case 'discover':
return DiscoverIcon;
case 'unionpay':
return UnionPayIcon;
case 'visa':
return VisaIcon;
default:
return DefaultIcon;
}
}
/**
* Toggle card selection dialog.
*/
public toggleSelection(): void {
this.$store.dispatch(TOGGLE_CARD_SELECTION, this.creditCard.id);
}
}
</script>
<style scoped lang="scss">
.bold {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 21px;
color: #61666b;
margin-block-start: 0.5em;
margin-block-end: 0.5em;
}
.medium {
font-family: 'font_regular', sans-serif;
font-size: 16px;
line-height: 21px;
color: #61666b;
margin-right: 5px;
}
.payment-methods-container__card-container {
width: calc(100% - 40px);
margin-top: 12px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px;
background-color: #f5f6fa;
border-radius: 6px;
&__info-area {
width: 75%;
display: flex;
align-items: center;
justify-content: flex-start;
&__card-logo {
display: flex;
align-items: center;
justify-content: center;
height: 70px;
width: 85px;
}
&__info-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: auto;
min-width: 165px;
margin-left: 15px;
}
&__expire-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: auto;
margin-left: 30px;
}
}
&__button-area {
display: flex;
justify-content: center;
align-items: center;
}
&__default-button {
width: 100px;
height: 34px;
border-radius: 6px;
background-color: white;
display: flex;
justify-content: center;
align-items: center;
&__label {
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 23px;
color: #000;
}
}
&__dots-container {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
margin-left: 10px;
cursor: pointer;
position: relative;
}
}
.discover-svg-path {
max-width: 80px;
}
</style>

View File

@ -1,98 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div v-click-outside="closeCardsDialog" class="dialog">
<p class="label dialog__make-default" @click="onMakeDefaultClick">Make Default</p>
<p class="label dialog__delete" @click="onRemoveClick">Delete</p>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
const {
CLEAR_CARDS_SELECTION,
MAKE_CARD_DEFAULT,
REMOVE_CARD,
} = PAYMENTS_ACTIONS;
// @vue/component
@Component
export default class CardDialog extends Vue {
@Prop({ default: '' })
private readonly cardId: string;
/**
* Closes card selection dialog.
*/
public closeCardsDialog(): void {
this.$store.dispatch(CLEAR_CARDS_SELECTION);
}
/**
* Selects card as default.
*/
public async onMakeDefaultClick(): Promise<void> {
try {
await this.$store.dispatch(MAKE_CARD_DEFAULT, this.cardId);
} catch (error) {
await this.$notify.error(error.message, AnalyticsErrorEventSource.BILLING_CC_DIALOG);
}
}
/**
* Removes card from list.
*/
public async onRemoveClick(): Promise<void> {
try {
await this.$store.dispatch(REMOVE_CARD, this.cardId);
await this.$notify.success('Credit card removed');
} catch (error) {
await this.$notify.error(error.message, AnalyticsErrorEventSource.BILLING_CC_DIALOG);
}
}
}
</script>
<style scoped lang="scss">
.dialog {
position: absolute;
top: 22px;
right: -30px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
z-index: 100;
background-image: url('../../../../../static/images/payments/Dialog.png');
background-size: contain;
width: 167px;
height: 122px;
cursor: initial;
&__make-default {
color: #61666b;
}
&__delete {
color: #eb5757;
}
}
.label {
font-family: 'font_medium', sans-serif;
font-size: 16px;
margin: 0;
height: 35%;
text-align: center;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
</style>

View File

@ -1,432 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="payment-methods-area">
<div class="payment-methods-area__functional-area" :class="functionalAreaClassName">
<div class="payment-methods-area__functional-area__top-container">
<h1 class="payment-methods-area__functional-area__title" aria-roledescription="title">Payment Method</h1>
<div class="payment-methods-area__functional-area__button-area">
<div v-if="!areAddButtonsClicked" class="payment-methods-area__functional-area__button-area__default-buttons">
<VButton
class="add-storj-button"
label="Add STORJ"
width="123px"
height="46px"
:is-blue-white="true"
:on-press="onAddSTORJ"
/>
</div>
<div v-else class="payment-methods-area__functional-area__button-area__cancel" @click="onCancel">
<p class="payment-methods-area__functional-area__button-area__cancel__text">Cancel</p>
</div>
</div>
</div>
<PaymentsBonus
v-if="isDefaultBonusBannerShown && !isAddCardClicked"
:any-credit-cards="false"
class="payment-methods-area__functional-area__bonus"
/>
<PaymentsBonus
v-else-if="!isDefaultBonusBannerShown && !isAddCardClicked"
:any-credit-cards="true"
class="payment-methods-area__functional-area__bonus"
/>
<AddStorjForm
v-if="isAddingStorjState"
:is-loading="isLoading"
@toggleIsLoading="toggleIsLoading"
@cancel="onCancel"
/>
<AddCardForm
v-if="isAddingCardState"
ref="addCard"
@toggleIsLoading="toggleIsLoading"
@toggleIsLoaded="toggleIsLoaded"
@cancel="onCancel"
/>
<div
v-if="!isAddStorjClicked"
class="add-card-button"
:class="{ 'button-moved': isAddCardClicked }"
@click="onAddCard"
>
<img
v-if="isLoading"
class="payment-loading-image"
src="@/../static/images/account/billing/loading.gif"
alt="loading gif"
>
<SuccessImage
v-if="isLoaded"
class="payment-loaded-image"
/>
<span>{{ addingCCButtonLabel }}</span>
</div>
</div>
<div v-if="isAddingCardState && noCreditCards" class="payment-methods-area__security-info-container">
<LockImage />
<span class="payment-methods-area__security-info-container__info">
Your card is secured by Stripe through TLS and AES-256 encryption. Your information is secure.
</span>
</div>
<VLoader v-if="areCardsFetching" class="pm-loader" />
<div v-if="!noCreditCards && !areCardsFetching" class="payment-methods-area__existing-cards-container">
<CardComponent
v-for="card in creditCards"
:key="card.id"
:credit-card="card"
/>
</div>
<div v-if="isLoading || isLoaded" class="payment-methods-area__blur" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { CreditCard } from '@/types/payments';
import { PaymentMethodsBlockState } from '@/utils/constants/billingEnums';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import AddCardForm from '@/components/account/billing/paymentMethods/AddCardForm.vue';
import AddStorjForm from '@/components/account/billing/paymentMethods/AddStorjForm.vue';
import CardComponent from '@/components/account/billing/paymentMethods/CardComponent.vue';
import PaymentsBonus from '@/components/account/billing/paymentMethods/PaymentsBonus.vue';
import VButton from '@/components/common/VButton.vue';
import VLoader from '@/components/common/VLoader.vue';
import LockImage from '@/../static/images/account/billing/lock.svg';
import SuccessImage from '@/../static/images/account/billing/success.svg';
interface AddCardConfirm {
onConfirmAddStripe(): Promise<void>;
}
// @vue/component
@Component({
components: {
AddStorjForm,
AddCardForm,
VButton,
CardComponent,
PaymentsBonus,
LockImage,
SuccessImage,
VLoader,
},
})
export default class PaymentMethods extends Vue {
private areaState: number = PaymentMethodsBlockState.DEFAULT;
public isLoading = false;
public isLoaded = false;
public isAddCardClicked = false;
public isAddStorjClicked = false;
public areCardsFetching = true;
/**
* Lifecycle hook after initial render where credit cards are fetched.
*/
public async mounted(): Promise<void> {
try {
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_CREDIT_CARDS);
this.areCardsFetching = false;
} catch (error) {
await this.$notify.error(error.message, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS);
}
}
public $refs!: {
addCard: AddCardForm & AddCardConfirm;
};
/**
* Returns list of credit cards from store.
*/
public get creditCards(): CreditCard[] {
return this.$store.state.paymentsModule.creditCards;
}
/**
* Sets class with specific styles for functional area.
*/
public get functionalAreaClassName(): string {
switch (true) {
case this.isAddCardClicked:
return 'reduced';
case this.isAddStorjClicked:
return 'extended';
default:
return '';
}
}
/**
* Indicates if adding buttons were clicked.
*/
public get areAddButtonsClicked(): boolean {
return this.isAddCardClicked || this.isAddStorjClicked;
}
/**
* Sets button label depending on loading state.
*/
public get addingCCButtonLabel(): string {
switch (true) {
case this.isLoading:
return 'Adding';
case this.isLoaded:
return 'Added!';
default:
return 'Add Card';
}
}
/**
* Indicates if adding tokens in progress.
*/
public get isAddingStorjState(): boolean {
return this.areaState === PaymentMethodsBlockState.ADDING_STORJ;
}
/**
* Indicates if adding card in progress.
*/
public get isAddingCardState(): boolean {
return this.areaState === PaymentMethodsBlockState.ADDING_CARD;
}
/**
* Indicates if free credits or percentage bonus banner is shown.
*/
public get isDefaultBonusBannerShown(): boolean {
return this.isDefaultState && !this.$store.getters.canUserCreateFirstProject;
}
/**
* Indicates if any of credit cards is attached to account.
*/
public get noCreditCards(): boolean {
return this.$store.state.paymentsModule.creditCards.length === 0;
}
/**
* Changes area state to adding tokens state.
*/
public onAddSTORJ(): void {
this.isAddStorjClicked = true;
setTimeout(() => {
this.areaState = PaymentMethodsBlockState.ADDING_STORJ;
}, 500);
}
/**
* Changes area state to adding card state and proceeds adding card process.
*/
public async onAddCard(): Promise<void> {
if (this.isAddCardClicked) {
await this.onConfirmAddCard();
return;
}
this.isAddCardClicked = true;
setTimeout(() => {
this.areaState = PaymentMethodsBlockState.ADDING_CARD;
}, 500);
}
/**
* Changes area state and token deposit value to default.
*/
public onCancel(): void {
if (this.isLoading || this.isDefaultState) return;
this.isAddCardClicked = false;
this.isAddStorjClicked = false;
this.areaState = PaymentMethodsBlockState.DEFAULT;
}
/**
* Toggles loading state.
*/
public toggleIsLoading(): void {
this.isLoading = !this.isLoading;
}
/**
* Toggles loaded state.
*/
public toggleIsLoaded(): void {
this.isLoaded = !this.isLoaded;
}
/**
* Indicates if no adding card nor tokens in progress.
*/
private get isDefaultState(): boolean {
return this.areaState === PaymentMethodsBlockState.DEFAULT;
}
/**
* Provides card information to Stripe.
*/
private async onConfirmAddCard(): Promise<void> {
await this.$refs.addCard.onConfirmAddStripe();
}
}
</script>
<style scoped lang="scss">
.add-card-button {
display: flex;
justify-content: center;
align-items: center;
width: 125px;
height: 50px;
position: absolute;
top: 0;
right: 0;
cursor: pointer;
border-radius: 6px;
background-color: #2683ff;
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 23px;
color: #fff;
user-select: none;
transition: top 0.5s ease-in-out;
&:hover {
background-color: #0059d0;
}
}
.button-moved {
top: 83px;
}
.payment-methods-area {
position: relative;
padding: 40px;
margin-bottom: 32px;
background-color: #fff;
border-radius: 8px;
font-family: 'font_regular', sans-serif;
&__functional-area {
position: relative;
height: 192px;
transition: all 0.3s ease-in-out;
&__top-container {
display: flex;
align-items: center;
justify-content: space-between;
}
&__bonus {
margin-top: 20px;
}
&__title {
margin: 0;
font-family: 'font_bold', sans-serif;
font-size: 28px;
line-height: 42px;
color: #384b65;
}
&__button-area {
display: flex;
align-items: center;
max-height: 48px;
position: relative;
top: 1px;
&__default-buttons {
display: flex;
justify-content: flex-start;
width: 269px;
}
&__cancel {
&__text {
font-family: 'font_medium', sans-serif;
font-size: 16px;
text-decoration: underline;
color: #354049;
opacity: 0.7;
cursor: pointer;
user-select: none;
}
}
}
}
&__security-info-container {
position: absolute;
left: 0;
top: 215px;
width: 100%;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background-color: #cef0e3;
border-radius: 0 0 8px 8px;
&__info {
margin-left: 5px;
font-size: 15px;
line-height: 18px;
color: #34bf89;
}
}
&__existing-cards-container {
position: relative;
margin-top: 45px;
width: 100%;
}
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
border-radius: 8px;
background-color: rgb(229 229 229 / 20%);
z-index: 100;
}
}
.payment-loading-image {
width: 18px;
height: 18px;
margin-right: 5px;
}
.payment-loaded-image {
margin-right: 5px;
}
.extended {
height: 200px; // TODO: should be based on whether storj payment is allowed or not
// see https://github.com/storj/storj-private/issues/43
// height: 300px;
}
.reduced {
height: 170px;
}
.pm-loader {
margin-top: 60px;
}
</style>

View File

@ -1,95 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="payments-bonus-container">
<LogoIcon v-if="anyCreditCards" class="banner-logo-svg" />
<CardIcon v-else class="banner-gift-svg" />
<div v-if="anyCreditCards" class="payments-bonus-container__text-container">
<p class="payments-bonus-container__text-container__main-text">
Get free credits for paying in STORJ Token
</p>
<p class="payments-bonus-container__text-container__additional-text">
Deposit STORJ Token to your account and receive a 10% bonus, or $10 for every $100.
</p>
</div>
<div v-else class="payments-bonus-container__text-container">
<p class="payments-bonus-container__text-container__main-text">
Add a Payment Method to Get Started
</p>
<p class="payments-bonus-container__text-container__additional-text">
Your first 150GB are free. You will not be charged until you use more than 150GB.
</p>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import CardIcon from '@/../static/images/account/billing/card.svg';
import LogoIcon from '@/../static/images/logo.svg';
// @vue/component
@Component({
components: {
LogoIcon,
CardIcon,
},
})
export default class PaymentsBonus extends Vue {
/**
* Indicates if any credit card is attached to account.
*/
@Prop({ default: false })
public readonly anyCreditCards: boolean;
}
</script>
<style scoped lang="scss">
.payments-bonus-container {
display: flex;
justify-content: flex-start;
align-items: center;
width: calc(100% - 72px);
padding: 30px 36px;
background-color: #e9f3ff;
border-radius: 6px;
&__image {
width: 77px;
height: 77px;
}
&__text-container {
margin-left: 35px;
&__main-text {
font-family: 'font_bold', sans-serif;
font-size: 22px;
line-height: 30px;
margin: 0;
color: #2683ff;
}
&__additional-text {
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 20px;
margin: 12px 0 0;
color: #717e92;
}
}
}
.banner-logo-svg {
min-width: 54px;
width: 207px;
height: 37px;
}
.banner-gift-svg {
min-height: 64px;
min-width: 50px;
}
</style>

View File

@ -1,487 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div :class="`${selectionStyle}__form-container`">
<div v-if="!isCustomAmount" :class="`${selectionStyle}__selected-container`">
<div :class="`${selectionStyle}__selected-container__label-container`" @click="open">
<p :class="`${selectionStyle}__selected-container__label-container__label`">{{ current.label }}</p>
<div :class="`${selectionStyle}__selected-container__label-container__svg ${isSelectionShown ?'down': 'up'}`">
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.372773 0.338888C0.869804 -0.112963 1.67565 -0.112963 2.17268 0.338888L7 4.72741L11.8273 0.338888C12.3243 -0.112963 13.1302 -0.112963 13.6272 0.338888C14.1243 0.790739 14.1243 1.52333 13.6272 1.97519L7 8L0.372773 1.97519C-0.124258 1.52333 -0.124258 0.790739 0.372773 0.338888Z" fill="#000000" />
</svg>
</div>
</div>
</div>
<label v-if="isCustomAmount" :class="`${selectionStyle}__label`">
<input
v-model="customAmount"
v-number
:class="`${selectionStyle}__custom-input`"
placeholder="Enter Amount in USD"
@input="onCustomAmountChange"
>
<p v-if="customAmount" :class="`${selectionStyle}__label__sign`">$</p>
<div :class="`${selectionStyle}__input-svg`" @click.stop="closeCustomAmountSelection">
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.372773 0.338888C0.869804 -0.112963 1.67565 -0.112963 2.17268 0.338888L7 4.72741L11.8273 0.338888C12.3243 -0.112963 13.1302 -0.112963 13.6272 0.338888C14.1243 0.790739 14.1243 1.52333 13.6272 1.97519L7 8L0.372773 1.97519C-0.124258 1.52333 -0.124258 0.790739 0.372773 0.338888Z" fill="#000000" />
</svg>
</div>
</label>
<div
v-if="isSelectionShown"
v-click-outside="close"
:class="`${selectionStyle}__options-container`"
>
<div
v-for="option in paymentOptions"
:key="option.label"
:class="`${selectionStyle}__options-container__item`"
@click.prevent.stop="select(option)"
>
<div v-if="isOptionSelected(option)" :class="`${selectionStyle}__options-container__item__svg`">
<svg width="15" height="13" viewBox="0 0 15 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.0928 3.02746C14.6603 2.4239 14.631 1.4746 14.0275 0.907152C13.4239 0.339699 12.4746 0.368972 11.9072 0.972536L14.0928 3.02746ZM4.53846 11L3.44613 12.028C3.72968 12.3293 4.12509 12.5001 4.53884 12.5C4.95258 12.4999 5.34791 12.3289 5.63131 12.0275L4.53846 11ZM3.09234 7.27469C2.52458 6.67141 1.57527 6.64261 0.971991 7.21036C0.36871 7.77812 0.339911 8.72743 0.907664 9.33071L3.09234 7.27469ZM11.9072 0.972536L3.44561 9.97254L5.63131 12.0275L14.0928 3.02746L11.9072 0.972536ZM5.6308 9.97199L3.09234 7.27469L0.907664 9.33071L3.44613 12.028L5.6308 9.97199Z" fill="#000000" />
</svg>
</div>
<p :class="`${selectionStyle}__options-container__item__label`">{{ option.label }}</p>
</div>
<div :class="`${selectionStyle}__options-container__custom-container`" @click.stop.prevent="openCustomAmountSelection">
<div v-if="isCustomAmount" :class="`${selectionStyle}__options-container__item__svg`">
<svg width="15" height="13" viewBox="0 0 15 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.0928 3.02746C14.6603 2.4239 14.631 1.4746 14.0275 0.907152C13.4239 0.339699 12.4746 0.368972 11.9072 0.972536L14.0928 3.02746ZM4.53846 11L3.44613 12.028C3.72968 12.3293 4.12509 12.5001 4.53884 12.5C4.95258 12.4999 5.34791 12.3289 5.63131 12.0275L4.53846 11ZM3.09234 7.27469C2.52458 6.67141 1.57527 6.64261 0.971991 7.21036C0.36871 7.77812 0.339911 8.72743 0.907664 9.33071L3.09234 7.27469ZM11.9072 0.972536L3.44561 9.97254L5.63131 12.0275L14.0928 3.02746L11.9072 0.972536ZM5.6308 9.97199L3.09234 7.27469L0.907664 9.33071L3.44613 12.028L5.6308 9.97199Z" fill="#000000" />
</svg>
</div>
Custom Amount
</div>
</div>
<div v-if="isSelectionShown" :class="`${selectionStyle}__payment-selection-blur`" />
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { PaymentAmountOption } from '@/types/payments';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { APP_STATE_DROPDOWNS } from '@/utils/constants/appStatePopUps';
// @vue/component
@Component
export default class TokenDepositSelection3 extends Vue {
@Prop({ default: () => [] })
public readonly paymentOptions: PaymentAmountOption[];
@Prop({ default: 'old' })
public readonly selectionStyle: string;
/**
* current selected payment option from default ones.
*/
public current: PaymentAmountOption;
public customAmount = '';
/**
* Indicates if custom amount selection state is active.
*/
public isCustomAmount = false;
/**
* Lifecycle hook before initial render.
* Sets initial deposit amount.
*/
public beforeMount(): void {
this.current = this.paymentOptions[0];
}
/**
* Indicates if concrete payment option is currently selected.
*/
public isOptionSelected(option: PaymentAmountOption): boolean {
return (option.value === this.current.value) && !this.isCustomAmount;
}
/**
* isSelectionShown flag that indicate is token amount selection shown.
*/
public get isSelectionShown(): boolean {
return this.$store.state.appStateModule.viewsState.activeDropdown === APP_STATE_DROPDOWNS.PAYMENT_SELECTION;
}
/**
* opens token amount selection.
*/
public open(): void {
setTimeout(() => this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACTIVE_DROPDOWN, APP_STATE_DROPDOWNS.PAYMENT_SELECTION), 0);
}
/**
* closes token amount selection.
*/
public close(): void {
if (!this.isSelectionShown) return;
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
}
/**
* onCustomAmountChange input event handle that emits value to parent component.
*/
public onCustomAmountChange(): void {
this.$emit('onChangeTokenValue', parseInt(this.customAmount, 10));
}
/**
* Sets view state to custom amount selection.
*/
public openCustomAmountSelection(): void {
this.isCustomAmount = true;
this.close();
this.$emit('onChangeTokenValue', 0);
}
/**
* Sets view state to default.
*/
public closeCustomAmountSelection(): void {
this.open();
this.$emit('onChangeTokenValue', this.current.value);
}
/**
* select standard value from list and emits it value to parent component.
*/
public select(option: PaymentAmountOption): void {
this.isCustomAmount = false;
this.current = option;
this.$emit('onChangeTokenValue', option.value);
this.close();
}
}
</script>
<style scoped lang="scss">
.up {
transform: rotate(-90deg);
transform-origin: center;
}
.down {
transform: rotate(0deg);
transform-origin: center;
}
.old {
&__custom-input {
width: 200px;
height: 48px;
border: 1px solid #afb7c1;
border-radius: 8px;
background-color: transparent;
padding: 0 36px 0 25px;
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 19px;
color: #354049;
appearance: textfield;
}
&__custom-input::-webkit-inner-spin-button,
&__custom-input::-webkit-outer-spin-button {
appearance: none;
margin: 0;
}
&__form-container {
position: relative;
}
&__label {
position: relative;
height: 21px;
&__sign {
position: absolute;
top: 50%;
left: 15px;
transform: translate(0, -50%);
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
color: #354049;
margin: 0;
}
}
&__input-svg {
position: absolute;
top: 49%;
right: 20px;
transform: translate(0, -50%);
cursor: pointer;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
}
&__selected-container {
position: relative;
width: 256px;
height: 48px;
border: 1px solid #afb7c1;
border-radius: 8px;
background-color: transparent;
display: flex;
align-items: center;
&__label-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 25px;
width: calc(100% - 40px);
height: 100%;
&__label {
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 28px;
color: #354049;
margin: 0;
}
&__svg {
cursor: pointer;
min-height: 25px;
}
}
}
&__options-container {
width: 256px;
position: absolute;
height: auto;
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 48px;
color: #354049;
background-color: white;
z-index: 102;
border-radius: 12px;
top: 50px;
box-shadow: 0 4px 4px rgb(0 0 0 / 25%);
&__custom-container {
display: flex;
align-items: center;
justify-content: flex-start;
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
padding: 0 30px;
cursor: pointer;
&:hover {
background-color: #f2f2f6;
}
}
&__item {
display: flex;
align-items: center;
padding: 0 30px;
cursor: pointer;
&__svg {
cursor: pointer;
margin-right: 10px;
}
&__label {
margin: 0;
}
&:first-of-type {
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
&:hover {
background-color: #f2f2f6;
}
&.selected {
background-color: red;
}
}
}
&__payment-selection-blur {
width: 258px;
height: 50px;
position: absolute;
top: 0;
left: 0;
}
}
.new {
&__custom-input {
width: 168px;
height: 35px;
border: 1px solid #afb7c1;
border-radius: 8px;
background-color: #fff;
padding: 0 34px 0 25px;
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 19px;
color: #354049;
appearance: textfield;
}
&__custom-input::placeholder {
font-size: 14px;
}
&__custom-input::-webkit-inner-spin-button,
&__custom-input::-webkit-outer-spin-button {
appearance: none;
margin: 0;
}
&__form-container {
position: relative;
}
&__label {
position: relative;
height: 21px;
&__sign {
position: absolute;
top: 50%;
left: 15px;
transform: translate(0, -50%);
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
color: #354049;
margin: 0;
}
}
&__input-svg {
position: absolute;
top: 49%;
right: 10px;
transform: translate(0, -50%);
cursor: pointer;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
}
&__selected-container {
position: relative;
width: 100%;
height: 35px;
border: 1px solid #afb7c1;
border-radius: 8px;
background-color: #fff;
display: flex;
align-items: center;
&__label-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px 0 25px;
width: calc(100% - 40px);
height: 100%;
&__label {
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 28px;
color: #354049;
margin: 0;
}
&__svg {
cursor: pointer;
min-height: 25px;
transition-duration: 200ms;
}
}
}
&__options-container {
width: 100%;
position: absolute;
height: auto;
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 48px;
color: #354049;
background-color: white;
z-index: 102;
border-radius: 12px;
top: 37px;
box-shadow: 0 4px 4px rgb(0 0 0 / 25%);
&__custom-container {
display: flex;
align-items: center;
justify-content: flex-start;
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
padding: 0 30px;
cursor: pointer;
&:hover {
background-color: #f2f2f6;
}
}
&__item {
display: flex;
align-items: center;
padding: 0 30px;
cursor: pointer;
&__svg {
cursor: pointer;
margin-right: 10px;
}
&__label {
margin: 0;
}
&:first-of-type {
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
&:hover {
background-color: #f2f2f6;
}
&.selected {
background-color: red;
}
}
}
&__payment-selection-blur {
width: 258px;
height: 50px;
position: absolute;
top: 0;
left: 0;
}
}
</style>

View File

@ -124,7 +124,7 @@ function displayDivider(idx: number): boolean {
align-items: center;
&__text {
font-family: sans-serif;
font-family: 'font_regular', sans-serif;
font-size: 14px;
color: #232a34;
cursor: pointer;

View File

@ -62,7 +62,7 @@ import MailIcon from '@/../static/images/register/mail.svg';
const props = withDefaults(defineProps<{
email: string;
showManualActivationMsg: boolean;
showManualActivationMsg?: boolean;
}>(), {
email: '',
showManualActivationMsg: true,

View File

@ -61,7 +61,6 @@ import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { MetaUtils } from '@/utils/meta';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { APP_STATE_DROPDOWNS } from '@/utils/constants/appStatePopUps';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
@ -89,14 +88,6 @@ const dropdownYPos = ref<number>(0);
const dropdownXPos = ref<number>(0);
const accountArea = ref<HTMLDivElement>();
/**
* Indicates if tabs options are hidden.
*/
const isNewBillingScreen = computed((): boolean => {
const isNewBillingScreen = MetaUtils.getMetaContent('new-billing-screen');
return isNewBillingScreen === 'true';
});
/**
* Returns bottom and left position of dropdown.
*/
@ -133,14 +124,8 @@ function navigateToBilling(): void {
if (router.currentRoute.path.includes(RouteConfig.Billing.path)) return;
if (isNewBillingScreen.value) {
router.push(RouteConfig.Account.with(RouteConfig.Billing).with(RouteConfig.BillingOverview).path);
analytics.pageVisit(RouteConfig.Account.with(RouteConfig.Billing).with(RouteConfig.BillingOverview).path);
return;
}
router.push(RouteConfig.Account.with(RouteConfig.Billing).path);
analytics.pageVisit(RouteConfig.Account.with(RouteConfig.Billing).path);
router.push(RouteConfig.Account.with(RouteConfig.Billing).with(RouteConfig.BillingOverview).path);
analytics.pageVisit(RouteConfig.Account.with(RouteConfig.Billing).with(RouteConfig.BillingOverview).path);
}
/**

View File

@ -82,7 +82,6 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { MetaUtils } from '@/utils/meta';
import { AnalyticsHttpApi } from '@/api/analytics';
import { RouteConfig } from '@/router';
import { NavigationLink } from '@/types/navigation';
@ -273,7 +272,7 @@ export default class NavigationArea extends Vue {
* Sends new path click event to segment.
*/
public trackClickEvent(path: string): void {
if (this.isNewBillingScreen && path === '/account/billing') {
if (path === '/account/billing') {
this.routeToOverview();
} else {
this.analytics.pageVisit(path);
@ -286,15 +285,6 @@ export default class NavigationArea extends Vue {
public routeToOverview(): void {
this.$router.push(RouteConfig.Account.with(RouteConfig.Billing).with(RouteConfig.BillingOverview).path);
}
/**
* Indicates if tabs options are hidden.
*/
public get isNewBillingScreen(): boolean {
const isNewBillingScreen = MetaUtils.getMetaContent('new-billing-screen');
return isNewBillingScreen === 'true';
}
}
</script>

View File

@ -81,19 +81,10 @@ export default class QuickStartLinks extends Vue {
this.analytics.eventTriggered(AnalyticsEvent.CREATE_AN_ACCESS_GRANT_CLICKED);
this.closeDropdowns();
if (this.isNewAccessGrantFlow) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.NewCreateAccessModal).path);
this.$router.push({
name: RouteConfig.NewCreateAccessModal.name,
params: { accessType: AccessType.AccessGrant },
}).catch(() => {return;});
return;
}
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).name,
params: { accessType: 'access' },
name: RouteConfig.CreateAccessModal.name,
params: { accessType: AccessType.AccessGrant },
}).catch(() => {return;});
}
@ -104,19 +95,10 @@ export default class QuickStartLinks extends Vue {
this.analytics.eventTriggered(AnalyticsEvent.CREATE_S3_CREDENTIALS_CLICKED);
this.closeDropdowns();
if (this.isNewAccessGrantFlow) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.NewCreateAccessModal).path);
this.$router.push({
name: RouteConfig.NewCreateAccessModal.name,
params: { accessType: AccessType.S3 },
}).catch(() => {return;});
return;
}
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).name,
params: { accessType: 's3' },
name: RouteConfig.CreateAccessModal.name,
params: { accessType: AccessType.S3 },
}).catch(() => {return;});
}
@ -161,13 +143,6 @@ export default class QuickStartLinks extends Vue {
this.closeDropdowns();
}
/**
* Indicates if new access grant flow should be used.
*/
private get isNewAccessGrantFlow(): boolean {
return this.$store.state.appStateModule.isNewAccessGrantFlow;
}
}
</script>

View File

@ -21,7 +21,6 @@ import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import { MetaUtils } from '@/utils/meta';
import { NavigationLink } from '@/types/navigation';
import InfoIcon from '@/../static/images/notifications/info.svg';
@ -46,18 +45,10 @@ export default class BillingNotification extends Vue {
return RouteConfig.Account;
}
/**
* Indicates if tabs options are hidden.
*/
public get isNewBillingScreen(): boolean {
const isNewBillingScreen = MetaUtils.getMetaContent('new-billing-screen');
return isNewBillingScreen === 'true';
}
public get billingPath(): string {
const billing = this.baseAccountRoute.with(RouteConfig.Billing);
return this.isNewBillingScreen ? billing.with(RouteConfig.BillingOverview).path : billing.path;
return billing.with(RouteConfig.BillingOverview).path;
}
/**

View File

@ -49,10 +49,10 @@ import { AnalyticsHttpApi } from '@/api/analytics';
import CLIFlowContainer from '@/components/onboardingTour/steps/common/CLIFlowContainer.vue';
import PermissionsSelect from '@/components/onboardingTour/steps/cliFlow/PermissionsSelect.vue';
import BucketNameBullet from '@/components/accessGrants/permissions/BucketNameBullet.vue';
import BucketsSelection from '@/components/accessGrants/permissions/BucketsSelection.vue';
import BucketNameBullet from '@/components/onboardingTour/steps/cliFlow/permissions/BucketNameBullet.vue';
import BucketsSelection from '@/components/onboardingTour/steps/cliFlow/permissions/BucketsSelection.vue';
import VLoader from '@/components/common/VLoader.vue';
import DurationSelection from '@/components/accessGrants/permissions/DurationSelection.vue';
import DurationSelection from '@/components/onboardingTour/steps/cliFlow/permissions/DurationSelection.vue';
import Icon from '@/../static/images/onboardingTour/accessGrant.svg';

View File

@ -26,7 +26,7 @@ import { Component, Vue, Prop } from 'vue-property-decorator';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { APP_STATE_DROPDOWNS } from '@/utils/constants/appStatePopUps';
import BucketsDropdown from '@/components/accessGrants/permissions/BucketsDropdown.vue';
import BucketsDropdown from '@/components/onboardingTour/steps/cliFlow/permissions/BucketsDropdown.vue';
import ExpandIcon from '@/../static/images/common/BlackArrowExpand.svg';

View File

@ -28,7 +28,7 @@ import { Component, Vue, Prop } from 'vue-property-decorator';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { APP_STATE_DROPDOWNS } from '@/utils/constants/appStatePopUps';
import DurationPicker from '@/components/accessGrants/permissions/DurationPicker.vue';
import DurationPicker from '@/components/onboardingTour/steps/cliFlow/permissions/DurationPicker.vue';
import ExpandIcon from '@/../static/images/common/BlackArrowExpand.svg';

View File

@ -10,16 +10,13 @@ import AllDashboardArea from '@/views/all-dashboard/AllDashboardArea.vue';
import MyProjects from '@/views/all-dashboard/components/MyProjects.vue';
import AccessGrants from '@/components/accessGrants/AccessGrants.vue';
import CreateAccessModal from '@/components/accessGrants/CreateAccessModal.vue';
import CreateAccessGrantFlow from '@/components/accessGrants/newCreateFlow/CreateAccessGrantFlow.vue';
import CreateAccessGrantFlow from '@/components/accessGrants/createFlow/CreateAccessGrantFlow.vue';
import AccountArea from '@/components/account/AccountArea.vue';
import AccountBilling from '@/components/account/billing/BillingArea.vue';
import BillingOverview from '@/components/account/billing/billingTabs/Overview.vue';
import BillingPaymentMethods from '@/components/account/billing/billingTabs/PaymentMethods.vue';
import BillingHistory2 from '@/components/account/billing/billingTabs/BillingHistory.vue';
import BillingHistory from '@/components/account/billing/billingTabs/BillingHistory.vue';
import BillingCoupons from '@/components/account/billing/billingTabs/Coupons.vue';
import DetailedHistory from '@/components/account/billing/depositAndBillingHistory/DetailedHistory.vue';
import CreditsHistory from '@/components/account/billing/coupons/CouponArea.vue';
import SettingsArea from '@/components/account/SettingsArea.vue';
import Page404 from '@/components/errors/Page404.vue';
import BucketsView from '@/components/objects/BucketsView.vue';
@ -89,27 +86,20 @@ export abstract class RouteConfig {
public static Settings2 = new NavigationLink('settings', 'Settings 2');
public static Billing = new NavigationLink('billing', 'Billing');
public static Billing2 = new NavigationLink('billing', 'Account Billing');
public static BillingHistory = new NavigationLink('billing-history', 'Billing History');
public static BillingHistory4 = new NavigationLink('billing-history', 'Billing History 4');
public static BillingOverview = new NavigationLink('overview', 'Overview');
// this duplicates the path of BillingOverview so that they can be used interchangeably in BillingArea.vue
public static BillingOverview2 = new NavigationLink('overview', 'Billing Overview');
public static BillingPaymentMethods = new NavigationLink('payment-methods', 'Payment Methods');
// this duplicates the path of BillingPaymentMethods so that they can be used interchangeably in BillingArea.vue
public static BillingPaymentMethods2 = new NavigationLink('payment-methods', 'Payment Methods 2');
public static BillingHistory = new NavigationLink('billing-history', 'Billing History');
// this duplicates the path of BillingHistory so that they can be used interchangeably in BillingArea.vue
public static BillingHistory2 = new NavigationLink('billing-history2', 'Billing History 2');
// this duplicates the path of BillingHistory2 so that they can be used interchangeably in BillingArea.vue
public static BillingHistory3 = new NavigationLink('billing-history2', 'Billing History 3');
public static BillingCoupons = new NavigationLink('coupons', 'Coupons');
public static BillingCoupons2 = new NavigationLink('coupons', 'Billing Coupons');
public static DepositHistory = new NavigationLink('deposit-history', 'Deposit History');
public static DepositHistory2 = new NavigationLink('deposit-history', 'Deposit History 2');
public static CreditsHistory = new NavigationLink('credits-history', 'Credits History');
public static CreditsHistory2 = new NavigationLink('credits-history', 'Credits History 2');
// access grant child paths
public static CreateAccessModal = new NavigationLink('create-access-modal', 'Create Access Modal');
public static NewCreateAccessModal = new NavigationLink('new-create-access-modal', 'New Create Access Modal');
// onboarding tour child paths
public static PricingPlanStep = new NavigationLink('pricing', 'Pricing Plan');
@ -149,13 +139,10 @@ export const notProjectRelatedRoutes = [
RouteConfig.ResetPassword.name,
RouteConfig.Authorize.name,
RouteConfig.Billing.name,
RouteConfig.BillingHistory.name,
RouteConfig.BillingOverview.name,
RouteConfig.BillingPaymentMethods.name,
RouteConfig.BillingHistory2.name,
RouteConfig.BillingHistory.name,
RouteConfig.BillingCoupons.name,
RouteConfig.DepositHistory.name,
RouteConfig.CreditsHistory.name,
RouteConfig.Settings.name,
];
@ -235,9 +222,9 @@ export const router = new Router({
component: BillingPaymentMethods,
},
{
path: RouteConfig.BillingHistory2.path,
name: RouteConfig.BillingHistory2.name,
component: BillingHistory2,
path: RouteConfig.BillingHistory.path,
name: RouteConfig.BillingHistory.name,
component: BillingHistory,
},
{
path: RouteConfig.BillingCoupons.path,
@ -246,21 +233,6 @@ export const router = new Router({
},
],
},
{
path: RouteConfig.BillingHistory.path,
name: RouteConfig.BillingHistory.name,
component: DetailedHistory,
},
{
path: RouteConfig.DepositHistory.path,
name: RouteConfig.DepositHistory.name,
component: DetailedHistory,
},
{
path: RouteConfig.CreditsHistory.path,
name: RouteConfig.CreditsHistory.name,
component: CreditsHistory,
},
],
},
{
@ -370,11 +342,6 @@ export const router = new Router({
{
path: RouteConfig.CreateAccessModal.path,
name: RouteConfig.CreateAccessModal.name,
component: CreateAccessModal,
},
{
path: RouteConfig.NewCreateAccessModal.path,
name: RouteConfig.NewCreateAccessModal.name,
component: CreateAccessGrantFlow,
},
],
@ -455,9 +422,9 @@ export const router = new Router({
component: BillingPaymentMethods,
},
{
path: RouteConfig.BillingHistory3.path,
name: RouteConfig.BillingHistory3.name,
component: BillingHistory2,
path: RouteConfig.BillingHistory2.path,
name: RouteConfig.BillingHistory2.name,
component: BillingHistory,
},
{
path: RouteConfig.BillingCoupons2.path,
@ -466,21 +433,6 @@ export const router = new Router({
},
],
},
{
path: RouteConfig.BillingHistory4.path,
name: RouteConfig.BillingHistory4.name,
component: DetailedHistory,
},
{
path: RouteConfig.DepositHistory2.path,
name: RouteConfig.DepositHistory2.name,
component: DetailedHistory,
},
{
path: RouteConfig.CreditsHistory2.path,
name: RouteConfig.CreditsHistory2.name,
component: CreditsHistory,
},
],
},
],

View File

@ -41,7 +41,6 @@ export class AppState {
public couponCodeBillingUIEnabled = false,
public couponCodeSignupUIEnabled = false,
public isAllProjectsDashboard = MetaUtils.getMetaContent('all-projects-dashboard') === 'true',
public isNewAccessGrantFlow = false,
public config: FrontendConfig = new FrontendConfig(),
) {}
}
@ -90,9 +89,6 @@ export function makeAppStateModule(configApi: FrontendConfigApi): StoreModule<Ap
[APP_STATE_MUTATIONS.SET_SATELLITE_STATUS](state: AppState, isBetaSatellite: boolean): void {
state.isBetaSatellite = isBetaSatellite;
},
[APP_STATE_MUTATIONS.SET_ACCESS_GRANT_FLOW_STATUS](state: AppState, isNewAccessGrantFlow: boolean): void {
state.isNewAccessGrantFlow = isNewAccessGrantFlow;
},
[APP_STATE_MUTATIONS.SET_ONB_AG_NAME_STEP_BACK_ROUTE](state: AppState, backRoute: string): void {
state.viewsState.onbAGStepBackRoute = backRoute;
},

View File

@ -20,7 +20,6 @@ export const APP_STATE_MUTATIONS = {
SET_SATELLITE_STATUS: 'SET_SATELLITE_STATUS',
SET_COUPON_CODE_BILLING_UI_STATUS: 'SET_COUPON_CODE_BILLING_UI_STATUS',
SET_COUPON_CODE_SIGNUP_UI_STATUS: 'SET_COUPON_CODE_SIGNUP_UI_STATUS',
SET_ACCESS_GRANT_FLOW_STATUS: 'SET_ACCESS_GRANT_FLOW_STATUS',
SET_ONB_AG_NAME_STEP_BACK_ROUTE: 'SET_ONB_AG_NAME_STEP_BACK_ROUTE',
SET_ONB_API_KEY_STEP_BACK_ROUTE: 'SET_ONB_API_KEY_STEP_BACK_ROUTE',
SET_ONB_API_KEY: 'SET_ONB_API_KEY',

View File

@ -114,7 +114,6 @@ import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { AuthHttpApi } from '@/api/auth';
import { MetaUtils } from '@/utils/meta';
import { useABTestingStore } from '@/store/modules/abTestingStore';
import VButton from '@/components/common/VButton.vue';
@ -159,13 +158,6 @@ const user = computed((): User => {
return store.getters.user;
});
/**
* Indicates if new billing screen should be used.
*/
const isNewBillingScreen = computed((): boolean => {
return MetaUtils.getMetaContent('new-billing-screen') === 'true';
});
/**
* Toggles account dropdown visibility.
*/
@ -205,7 +197,7 @@ function navigateToBilling(): void {
return;
}
const routeConf = isNewBillingScreen ? billing.with(RouteConfig.BillingOverview2).path : billing.path;
const routeConf = billing.with(RouteConfig.BillingOverview2).path;
router.push(routeConf);
analytics.pageVisit(routeConf);
}

View File

@ -68,7 +68,6 @@ import {
AnalyticsErrorEventSource,
AnalyticsEvent,
} from '@/utils/constants/analyticsEventNames';
import { MetaUtils } from '@/utils/meta';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AuthHttpApi } from '@/api/auth';
import { APP_STATE_DROPDOWNS } from '@/utils/constants/appStatePopUps';
@ -99,13 +98,6 @@ const isDropdownOpen = computed((): boolean => {
return store.state.appStateModule.viewsState.activeDropdown === APP_STATE_DROPDOWNS.ALL_DASH_ACCOUNT;
});
/**
* Indicates if new billing screen should be used.
*/
const isNewBillingScreen = computed((): boolean => {
return MetaUtils.getMetaContent('new-billing-screen') === 'true';
});
/**
* Returns satellite name from store.
*/
@ -132,7 +124,7 @@ function navigateToBilling(): void {
return;
}
const routeConf = isNewBillingScreen ? billing.with(RouteConfig.BillingOverview2).path : billing.path;
const routeConf = billing.with(RouteConfig.BillingOverview2).path;
router.push(routeConf);
analytics.pageVisit(routeConf);
}

View File

@ -1,4 +0,0 @@
<svg width="8" height="14" viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="back-button-svg-path" fill-rule="evenodd" clip-rule="evenodd" d="M7.66111 0.372773C8.11296 0.869804 8.11296 1.67565 7.66111 2.17268L3.27259 7L7.66111 11.8273C8.11296 12.3243 8.11296 13.1302 7.66111 13.6272C7.20926 14.1243 6.47666 14.1243 6.02481 13.6272L-3.0598e-07 7L6.02481 0.372773C6.47667 -0.124258 7.20926 -0.124258 7.66111 0.372773Z" fill="#768394"/>
</svg>

Before

Width:  |  Height:  |  Size: 476 B

View File

@ -1,6 +0,0 @@
<svg width="64" height="50" viewBox="0 0 61 47" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.8378 12.7027C36.8378 19.0167 41.9563 24.1351 48.2703 24.1351V41.9189C48.2703 44.0236 46.5641 45.7297 44.4594 45.7297H5.08107C2.97642 45.7297 1.27026 44.0236 1.27026 41.9189V16.5135C1.27026 14.4089 2.97642 12.7027 5.08107 12.7027H36.8378Z" stroke="#2683FF" stroke-width="2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.2703 24.1352V27.946H1.27026V20.3243L39.7496 20.3251C41.8429 22.6636 44.8847 24.1352 48.2703 24.1352Z" fill="#2683FF"/>
<path d="M48.2703 24.1351C54.5842 24.1351 59.7027 19.0167 59.7027 12.7027C59.7027 6.38875 54.5842 1.27028 48.2703 1.27028C41.9563 1.27028 36.8378 6.38875 36.8378 12.7027C36.8378 19.0167 41.9563 24.1351 48.2703 24.1351Z" stroke="#2683FF" stroke-width="2"/>
<path d="M48.2703 5.71622V19.5737M41.3415 12.645H55.199H41.3415Z" stroke="#2683FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,3 +0,0 @@
<svg class="usage-charges-item-container__summary__name-container__expand-image" width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.372773 0.338888C0.869804 -0.112963 1.67565 -0.112963 2.17268 0.338888L7 4.72741L11.8273 0.338888C12.3243 -0.112963 13.1302 -0.112963 13.6272 0.338888C14.1243 0.790739 14.1243 1.52333 13.6272 1.97519L7 8L0.372773 1.97519C-0.124258 1.52333 -0.124258 0.790739 0.372773 0.338888Z" fill="#2683FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 526 B

View File

@ -1,3 +0,0 @@
<svg class="usage-charges-item-container__summary__name-container__hide-image" width="8" height="14" viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.328889 13.6272C-0.10963 13.1302 -0.10963 12.3243 0.328889 11.8273L4.58792 7L0.328889 2.17268C-0.10963 1.67565 -0.10963 0.869804 0.328889 0.372774C0.767408 -0.124258 1.47839 -0.124258 1.91691 0.372774L7.76396 7L1.91691 13.6272C1.47839 14.1243 0.767409 14.1243 0.328889 13.6272Z" fill="#2683FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 525 B

View File

@ -1,5 +0,0 @@
<svg width="64" height="46.125" fill="none" version="1.1" viewBox="0 0 64 46.125" xmlns="http://www.w3.org/2000/svg">
<path d="m1.625 12c-0.89746231 0-1.625 0.727537-1.625 1.625v9.355469c1.9597256 1.311781 3.25 3.546706 3.25 6.082031s-1.2902744 4.770169-3.25 6.082031v9.355469c0 0.897487 0.72753769 1.625 1.625 1.625h48.75c0.897487 0 1.625-0.727513 1.625-1.625v-8.884766c-2.407762-1.196569-4.0625-3.681603-4.0625-6.552734 0-1.560052 0.491789-3.004181 1.324219-4.191406a12 12 0 0 1-6.199219-2.882813v20.886719h-34.9375v-27.625h31.115234a12 12 0 0 1-0.240234-2.25 12 12 0 0 1 0.07227-1zm9.75 6.5v21.125h28.4375v-21.125z" fill="#2683ff" stroke-width=".8125"/>
<circle cx="51" cy="13" r="12" fill="none" stroke="#2683ff" stroke-width="2"/>
<path d="m44 13h14m-7-7v14" fill="none" stroke="#2683ff" stroke-linecap="round" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 845 B

View File

@ -1,3 +0,0 @@
<svg width="64" height="42" fill="none" version="1.1" viewBox="0 0 64 42" xmlns="http://www.w3.org/2000/svg">
<path d="m2 0c-1.104569 0-2 0.89543-2 2v11.5156c2.41197 1.6145 4 4.364 4 7.4844s-1.58803 5.8699-4 7.4845v11.5155c0 1.1046 0.895431 2 2 2h60c1.1046 0 2-0.8954 2-2v-10.9355c-2.9634-1.4727-5-4.5308-5-8.0645s2.0366-6.5918 5-8.0645v-10.9355c0-1.10457-0.8954-2-2-2zm8 4.00001h43v33.99999h-43zm4 4h35v25.99999h-35z" clip-rule="evenodd" fill="#949daa" fill-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 484 B

View File

@ -1,3 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="date-picker-svg-path" d="M16.3213 2.28026H14.8009V1.50058C14.8009 1.10058 14.4806 0.760742 14.0611 0.760742C13.6611 0.760742 13.3213 1.08106 13.3213 1.50058V2.28026H6.66106V1.50058C6.66106 1.10058 6.34074 0.760742 5.92122 0.760742C5.5009 0.760742 5.2009 1.10058 5.2009 1.50058V2.28026H3.68058C1.92042 2.28026 0.500977 3.70058 0.500977 5.45986V16.0599C0.500977 17.82 1.9213 19.2395 3.68058 19.2395H16.3204C18.0805 19.2395 19.5 17.8191 19.5 16.0599V5.45986C19.5008 3.70048 18.0804 2.28026 16.321 2.28026H16.3213ZM3.68066 3.74042H5.20098V4.5201C5.20098 4.9201 5.5213 5.25994 5.94082 5.25994C6.36114 5.25994 6.68066 4.93962 6.68066 4.5201V3.74042H13.3603V4.5201C13.3603 4.9201 13.6806 5.25994 14.1001 5.25994C14.5001 5.25994 14.8399 4.93962 14.8399 4.5201V3.74042H16.3603C17.3001 3.74042 18.0806 4.50058 18.0806 5.46074V7.06074H1.96098V5.46074C1.96098 4.5209 2.74066 3.74042 3.68052 3.74042H3.68066ZM9.62126 14.2006H10.4009C11.0213 14.2006 11.5213 14.7006 11.5213 15.3209C11.5213 15.9413 11.0213 16.4413 10.4009 16.4413H9.62126C9.00094 16.4413 8.50094 15.9413 8.50094 15.3209C8.50094 14.7006 9.00094 14.2006 9.62126 14.2006ZM8.50094 10.8404C8.50094 10.2201 9.00094 9.7201 9.62126 9.7201L10.4009 9.72088C11.0213 9.72088 11.5213 10.2209 11.5213 10.8412C11.5213 11.4615 11.0213 11.9615 10.4009 11.9615H9.62126C9.00094 11.9607 8.50094 11.4607 8.50094 10.8404V10.8404ZM14.8407 14.2006H15.6204C16.2407 14.2006 16.7407 14.7006 16.7407 15.3209C16.7407 15.9413 16.2407 16.4413 15.6204 16.4413H14.8407C14.2204 16.4413 13.7204 15.9413 13.7204 15.3209C13.7212 14.7006 14.2212 14.2006 14.8407 14.2006ZM13.7212 10.8404C13.7212 10.2201 14.2212 9.7201 14.8415 9.7201H15.6212C16.2415 9.7201 16.7415 10.2201 16.7415 10.8404C16.7415 11.4607 16.2415 11.9607 15.6212 11.9607H14.8415C14.2212 11.9607 13.7212 11.4607 13.7212 10.8404ZM6.2806 10.8404C6.2806 11.4607 5.7806 11.9607 5.16028 11.9607H4.3806C3.76028 11.9607 3.26028 11.4607 3.26028 10.8404C3.26028 10.2201 3.76028 9.7201 4.3806 9.7201H5.16028C5.7806 9.72088 6.2806 10.2209 6.2806 10.8404ZM4.3806 14.2006H5.16028C5.7806 14.2006 6.2806 14.7006 6.2806 15.3209C6.2806 15.9413 5.7806 16.4413 5.16028 16.4413H4.3806C3.76028 16.4413 3.26028 15.9413 3.26028 15.3209C3.26106 14.7006 3.76106 14.2006 4.3806 14.2006Z" fill="#2683FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,3 +0,0 @@
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.372773 0.338888C0.869804 -0.112963 1.67565 -0.112963 2.17268 0.338888L7 4.72741L11.8273 0.338888C12.3243 -0.112963 13.1302 -0.112963 13.6272 0.338888C14.1243 0.790739 14.1243 1.52333 13.6272 1.97519L7 8L0.372773 1.97519C-0.124258 1.52333 -0.124258 0.790739 0.372773 0.338888Z" fill="#768394"/>
</svg>

Before

Width:  |  Height:  |  Size: 451 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="Layer_1" x="0px" y="0px" width="14px" height="8px" viewBox="0 0 14 8" enable-background="new 0 0 14 8" xml:space="preserve">
<image id="image0" width="14" height="8" x="0" y="0" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAICAMAAAD+zz7+AAAABGdBTUEAALGPC/xhBQAAACBjSFJN AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAXVBMVEX////+//+0u8X8/f2n sLt2g5Srs779/v74+fqbpLGep7P6+vv19veUnqvv8PKMl6WPmafx8vTm6eyDj566wcn+/v6xuMKG kqHp6+6HkqF3g5TBx86krLh/i5vO0tgN414OAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAAYAAAAGAA 8GtCzwAAAAd0SU1FB+QGCQ4gEt/qMf4AAABSSURBVAjXPYxZDoAgEEMHtC4I7oqy3f+YMiH4vvqS tkSMkFLQT9MCXV9tGJFRUzFtclaA0Wzzwk3urxvRfgBn/rluwD70wvky8g6WQkz1MsXwAXmBAxlp 0FISAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTA2LTA5VDE0OjMyOjE4KzAwOjAwc98QGgAAACV0 RVh0ZGF0ZTptb2RpZnkAMjAyMC0wNi0wOVQxNDozMjoxOCswMDowMAKCqKYAAAAASUVORK5CYII="/>
</svg>

Before

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,4 +0,0 @@
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 7.3335V9.8335C7.63365 9.83414 7.27773 9.95548 6.98727 10.1787C6.69681 10.402 6.488 10.7147 6.39313 11.0686C6.29827 11.4224 6.32263 11.7977 6.46245 12.1363C6.60228 12.4749 6.84978 12.758 7.16667 12.9418V14.0002C7.16667 14.2212 7.25446 14.4331 7.41074 14.5894C7.56702 14.7457 7.77899 14.8335 8 14.8335V17.3335H2.16667C1.72464 17.3335 1.30072 17.1579 0.988155 16.8453C0.675595 16.5328 0.5 16.1089 0.5 15.6668V9.00016C0.5 8.0835 1.25 7.3335 2.16667 7.3335H8Z" fill="#34BF89"/>
<path d="M8.00016 14.8334C8.22118 14.8334 8.43314 14.7456 8.58942 14.5893C8.7457 14.4331 8.8335 14.2211 8.8335 14.0001V12.9417C9.15039 12.7579 9.39788 12.4748 9.53771 12.1362C9.67753 11.7976 9.7019 11.4223 9.60703 11.0685C9.51217 10.7146 9.30336 10.4019 9.0129 10.1787C8.72244 9.9554 8.36651 9.83406 8.00016 9.83341V7.33341H10.5002V4.83341C10.5002 4.17037 10.2368 3.53449 9.76793 3.06565C9.29909 2.59681 8.6632 2.33341 8.00016 2.33341C7.33712 2.33341 6.70124 2.59681 6.2324 3.06565C5.76355 3.53449 5.50016 4.17037 5.50016 4.83341V7.33341H3.8335V4.83341C3.8335 3.72835 4.27248 2.66854 5.05388 1.88714C5.83529 1.10573 6.89509 0.666748 8.00016 0.666748C9.10523 0.666748 10.165 1.10573 10.9464 1.88714C11.7278 2.66854 12.1668 3.72835 12.1668 4.83341V7.33341H13.8335C14.2755 7.33341 14.6994 7.50901 15.012 7.82157C15.3246 8.13413 15.5002 8.55805 15.5002 9.00008V15.6667C15.5002 16.1088 15.3246 16.5327 15.012 16.8453C14.6994 17.1578 14.2755 17.3334 13.8335 17.3334H8.00016V14.8334Z" fill="#34BF89"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,3 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.0275 30.9607C36.5353 30.0514 36.04 29.1404 35.5463 28.2264C34.6275 26.531 33.7103 24.8357 32.7931 23.1404C31.6916 21.1091 30.5931 19.0748 29.4931 17.0436C28.4307 15.0826 27.3713 13.1248 26.3088 11.1656C25.5275 9.72492 24.7494 8.28276 23.9681 6.84076C23.7572 6.45014 23.5463 6.05952 23.3353 5.672C23.1166 5.26576 22.8853 4.87512 22.5541 4.54388C21.3979 3.3798 19.4791 3.15636 18.0885 4.03608C17.4916 4.41421 17.0604 4.95016 16.7291 5.5642C16.2213 6.50172 15.7135 7.4392 15.2057 8.37984C14.2807 10.0908 13.3541 11.8017 12.4291 13.5126C11.3151 15.5548 10.2104 17.6018 9.10102 19.6486C8.05102 21.5893 6.99946 23.5268 5.9479 25.469C5.17914 26.8909 4.40882 28.3097 3.63854 29.7314C3.43541 30.1064 3.2323 30.4814 3.02918 30.8564C2.7448 31.3846 2.52138 31.9189 2.45106 32.5283C2.25262 34.2502 3.45886 35.9471 5.12606 36.369C5.5667 36.4815 6.00106 36.4861 6.44638 36.4861H33.9464H33.9901C34.8964 36.4674 35.7558 36.1268 36.4198 35.5096C37.0604 34.9158 37.4354 34.1033 37.5417 33.2439C37.6432 32.4346 37.4089 31.6689 37.0276 30.9611L37.0275 30.9607ZM18.4367 13.9527C18.4367 13.0777 19.1523 12.4293 19.9992 12.3902C20.8429 12.3512 21.5617 13.1371 21.5617 13.9527V24.9559C21.5617 25.8309 20.846 26.4794 19.9992 26.5184C19.1554 26.5575 18.4367 25.7715 18.4367 24.9559V13.9527ZM19.9992 31.8403C19.1211 31.8403 18.4085 31.1294 18.4085 30.2497C18.4085 29.3716 19.1195 28.659 19.9992 28.659C20.8773 28.659 21.5898 29.37 21.5898 30.2497C21.5898 31.1278 20.8773 31.8403 19.9992 31.8403Z" fill="#F4D638"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,5 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EB5757"/>
<path d="M20.5 22.75C21.7676 22.75 22.8047 21.645 22.8047 20.2944V10.4556C22.8047 10.3328 22.797 10.2019 22.7816 10.0791C22.6126 8.90857 21.6523 8 20.5 8C19.2324 8 18.1953 9.10502 18.1953 10.4556V20.2862C18.1953 21.645 19.2324 22.75 20.5 22.75Z" fill="#F5F5F9"/>
<path d="M20.5 25.1465C18.7146 25.1465 17.2734 26.5877 17.2734 28.373C17.2734 30.1584 18.7146 31.5996 20.5 31.5996C22.2853 31.5996 23.7265 30.1584 23.7265 28.373C23.7337 26.5877 22.2925 25.1465 20.5 25.1465Z" fill="#F5F5F9"/>
</svg>

Before

Width:  |  Height:  |  Size: 657 B

View File

@ -1,4 +0,0 @@
<svg width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.495399 5.75514C-0.165133 5.12956 -0.165133 4.1153 0.495399 3.48973C1.15593 2.86415 2.22687 2.86415 2.8874 3.48973L6.87406 7.26541C7.53459 7.89098 7.53459 8.90524 6.87406 9.53082C6.21353 10.1564 5.14259 10.1564 4.48206 9.53082L0.495399 5.75514Z" fill="#2683FF"/>
<path d="M6.87406 9.53082C6.21353 10.1564 5.14259 10.1564 4.48206 9.53082C3.82153 8.90524 3.82153 7.89098 4.48206 7.26541L11.6581 0.469182C12.3186 -0.156394 13.3895 -0.156394 14.0501 0.469182C14.7106 1.09476 14.7106 2.10902 14.0501 2.73459L6.87406 9.53082Z" fill="#2683FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 660 B

View File

@ -1,5 +0,0 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="11" cy="11" r="10.5" stroke="white"/>
<path d="M5.17423 11.8324C4.63467 11.2929 4.63467 10.4181 5.17423 9.8785C5.7138 9.33894 6.5886 9.33894 7.12817 9.8785L10.3847 13.1351C10.9243 13.6746 10.9243 14.5494 10.3847 15.089C9.84516 15.6286 8.97036 15.6286 8.43079 15.089L5.17423 11.8324Z" fill="white"/>
<path d="M10.3847 15.089C9.84516 15.6286 8.97036 15.6286 8.43079 15.089C7.89123 14.5494 7.89123 13.6746 8.43079 13.1351L14.2926 7.27325C14.8322 6.73369 15.707 6.73369 16.2465 7.27325C16.7861 7.81282 16.7861 8.68762 16.2465 9.22719L10.3847 15.089Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 689 B

View File

@ -1,3 +0,0 @@
<svg width="16" height="15" viewBox="0 0 16 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6305 0.799805C11.9832 0.799805 12.2707 1.07873 12.2846 1.42802L12.2851 1.45435L12.2852 1.95023C12.7142 2.03323 13.0132 2.15631 13.3134 2.31689C13.9147 2.63842 14.3865 3.11025 14.708 3.71147L14.7356 3.76376L14.7761 3.84369C15.0537 4.40549 15.2008 5.02927 15.2008 6.55354V10.1856C15.2008 11.8301 15.0296 12.4265 14.708 13.0277C14.3865 13.6289 13.9147 14.1007 13.3134 14.4223L13.2611 14.4499L13.1812 14.4904C12.6194 14.7679 11.9956 14.915 10.4714 14.915H5.5302C3.88567 14.915 3.28933 14.7438 2.68812 14.4223C2.08691 14.1007 1.61507 13.6289 1.29354 13.0277L1.26514 12.9738L1.22467 12.8939C0.951951 12.3413 0.805404 11.728 0.800781 10.2565V6.55354C0.800781 4.90902 0.97201 4.31268 1.29354 3.71147C1.61507 3.11025 2.08691 2.63842 2.68812 2.31689L2.742 2.28848C3.03832 2.13461 3.34109 2.01732 3.77597 1.9391L3.77599 1.45435C3.77599 1.09285 4.06904 0.799805 4.43053 0.799805C4.78321 0.799805 5.07074 1.07873 5.08456 1.42802L5.08508 1.45435L5.085 1.82875L5.23003 1.82609C5.30409 1.82503 5.38049 1.82437 5.45935 1.82413H10.4714C10.6518 1.82413 10.8196 1.82619 10.9762 1.83028L10.976 1.45435C10.976 1.09285 11.269 0.799805 11.6305 0.799805ZM13.8916 7.06047H2.10975L2.10987 10.2524L2.11066 10.3771C2.11995 11.5359 2.20985 11.9652 2.44792 12.4103C2.64744 12.7834 2.93241 13.0684 3.30549 13.2679L3.35224 13.2924L3.39998 13.3162C3.81477 13.518 4.26495 13.5974 5.34118 13.6053L5.5302 13.6059H10.4714C11.7731 13.6059 12.2284 13.518 12.6961 13.2679C13.0692 13.0684 13.3541 12.7834 13.5536 12.4103L13.5781 12.3636L13.602 12.3158C13.8038 11.901 13.8831 11.4509 13.891 10.3746L13.8917 10.1856L13.8916 7.06047ZM10.4714 3.13322H5.46337L5.33868 3.134C5.24994 3.13471 5.16547 3.1359 5.08495 3.13759L5.08508 3.97903C5.08508 4.34052 4.79203 4.63357 4.43053 4.63357C4.07785 4.63357 3.79032 4.35464 3.77651 4.00535L3.77599 3.97903L3.77599 3.27922C3.60221 3.32828 3.45474 3.39144 3.30549 3.47126C2.93241 3.67078 2.64744 3.95575 2.44792 4.32883L2.42344 4.37559C2.24943 4.71564 2.15981 5.06428 2.12606 5.75134H13.8755C13.8402 5.03277 13.7438 4.68437 13.5536 4.32883C13.3541 3.95575 13.0692 3.67078 12.6961 3.47126L12.6493 3.44679C12.5333 3.38741 12.4163 3.33787 12.2851 3.29701L12.2851 3.97903C12.2851 4.34052 11.992 4.63357 11.6305 4.63357C11.2779 4.63357 10.9903 4.35464 10.9765 4.00535L10.976 3.97903L10.9762 3.13872C10.8772 3.1363 10.7722 3.13471 10.6604 3.13389L10.4714 3.13322Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,30 +0,0 @@
<svg width="240" height="187" viewBox="0 0 380 295" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M168 295C246.997 295 311 231.2 311 152.5C311 73.8 246.997 10 168 10C89.0028 10 25 73.8 25 152.5C25 231.2 89.0028 295 168 295Z" fill="#E8EAF2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3168 98C21.4071 98 20 96.5077 20 94.6174C20.9046 68.9496 31.8599 45.769 49.0467 28.7566C66.2335 11.7442 89.6518 0.900089 115.583 0.00470057C117.492 -0.094787 119 1.39753 119 3.28779V32.4377C119 34.2284 117.593 35.6213 115.784 35.7208C99.7025 36.5167 85.2294 43.3813 74.4751 53.927C63.8213 64.5722 56.8863 78.8984 56.0822 94.8164C55.9817 96.6072 54.5746 98 52.7655 98H23.3168Z" fill="#B0B6C9"/>
<path d="M117.5 30C124.404 30 130 25.0751 130 19C130 12.9249 124.404 8 117.5 8C110.596 8 105 12.9249 105 19C105 25.0751 110.596 30 117.5 30Z" fill="#8F96AD"/>
<path d="M112.5 97C116.09 97 119 94.3137 119 91C119 87.6863 116.09 85 112.5 85C108.91 85 106 87.6863 106 91C106 94.3137 108.91 97 112.5 97Z" fill="#B0B6C9"/>
<path d="M15.0005 282C23.226 282 30 274.575 30 265.5C30 256.425 23.226 249 15.0005 249C6.77499 249 0.00102409 256.425 0.00102409 265.5C-0.0957468 274.678 6.67822 282 15.0005 282Z" fill="#8F96AD"/>
<path d="M15.5 274C19.0286 274 22 270.9 22 267C22 263.2 19.1214 260 15.5 260C11.9714 260 9 263.1 9 267C9 270.9 11.8786 274 15.5 274Z" fill="white"/>
<path d="M282.587 111H307.413C309.906 111 312 108.955 312 106.5C312 104.045 309.906 102 307.413 102H282.587C280.094 102 278 104.045 278 106.5C278 108.955 280.094 111 282.587 111Z" fill="white"/>
<path d="M282.585 93H289.415C291.951 93 294 91.02 294 88.5C294 85.98 291.951 84 289.415 84H282.585C280.049 84 278 85.98 278 88.5C278 91.02 279.951 93 282.585 93Z" fill="#E8EAF2"/>
<path d="M252.872 92H260.128C262.823 92 265 90.4091 265 88.5C265 86.5909 262.823 85 260.128 85H252.872C250.177 85 248 86.5909 248 88.5C248 90.4091 250.177 92 252.872 92Z" fill="#363840"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M45 166C48.8182 166 52 162.818 52 159C52 155.182 48.8182 152 45 152C41.1818 152 38 155.182 38 159C38 162.818 41.1818 166 45 166Z" fill="#B0B6C9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M217 232C220.818 232 224 228.818 224 225C224 221.182 220.818 218 217 218C213.182 218 210 221.182 210 225C210 228.818 213.182 232 217 232Z" fill="#2683FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26 142C29.8182 142 33 139.045 33 135.5C33 131.955 29.8182 129 26 129C22.1818 129 19 131.955 19 135.5C19 139.045 22.1818 142 26 142Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M45 142C48.8182 142 52 139.045 52 135.5C52 131.955 48.8182 129 45 129C41.1818 129 38 131.955 38 135.5C38 139.045 41.1818 142 45 142Z" fill="#E8EAF2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M64 142C67.8182 142 71 139.045 71 135.5C71 131.955 67.8182 129 64 129C60.1818 129 57 131.955 57 135.5C57 139.045 60.1818 142 64 142Z" fill="white"/>
<path d="M107.014 129.651C107.014 129.651 152.017 118.395 199.527 125.169C212.857 127.061 224.785 134.831 232.001 146.186C245.031 166.606 263.374 203.062 259.465 241.112L239.018 246.093C239.018 246.093 224.885 200.97 209.049 182.643C209.049 182.643 190.205 225.275 191.208 248.683C191.208 249.38 191.308 249.977 191.308 250.575C193.513 273.485 101 254.858 101 254.858L107.014 129.651Z" fill="#F5F6FA"/>
<path d="M143 89.7894L145.01 121.569C145.211 124.568 147.12 127.066 149.833 127.865C156.063 129.664 167.821 131.863 179.276 127.266C181.387 126.466 182.492 123.968 181.789 121.669L166.514 73L143 89.7894Z" fill="#8F96AD"/>
<path d="M189 61.014C189 61.014 186.474 85.2772 181.219 95.8484C175.964 106.42 174.448 114.272 161.412 109.641C148.376 105.01 141.707 93.5328 142.01 80.2434C142.01 80.2434 142.414 59.7052 147.972 54.3692C153.631 49.0333 189 61.014 189 61.014Z" fill="#B0B6C9"/>
<path d="M150.596 75.686L152.115 76.4754C152.115 76.4754 153.128 60.6872 159.814 61.4766C166.5 62.266 190.609 69.8641 199.625 64.9303C208.235 60.1938 191.521 44.2082 180.074 40.4585C163.866 35.0313 150.798 35.5247 144.822 45.2936C144.416 45.8857 143.606 45.8857 143.201 45.2936C142.492 44.0108 128.209 53.9772 132.97 65.917C133.172 66.5091 138.946 83.4815 140.567 83.9748C140.972 84.0735 141.479 83.8762 141.681 83.4815L146.24 74.4032C146.442 73.9098 147.05 73.7125 147.557 74.0085L150.596 75.686Z" fill="#0F002D"/>
<path d="M149.877 78.0283C149.877 78.0283 154.31 62.6808 145.56 63.0051C136.81 63.3293 139.844 79.7576 144.744 83L149.877 78.0283Z" fill="#B0B6C9"/>
<path d="M106.635 221.07C104.63 206.983 119.272 186.154 125.289 178.305C126.994 176.092 127.996 173.274 127.996 170.457C128.197 150.433 119.773 137.553 106.335 129C106.335 129 57.5953 185.953 70.0308 229.724C71.3345 234.453 73.4406 238.478 76.048 242C78.0538 225.397 97.1082 221.875 106.635 221.07Z" fill="#F5F6FA"/>
<path d="M107.966 215L106 214.798C107.655 200.851 120.172 183.67 125.448 177L127 178.112C121.828 184.681 109.621 201.559 107.966 215Z" fill="#0F002D"/>
<path d="M107.128 221.954C106.926 221.337 106.825 220.617 106.725 220C97.054 220.823 78.0147 224.423 76 241.29C97.8599 270.808 158 260.111 158 260.111V248.592C158.101 248.695 111.862 239.953 107.128 221.954Z" fill="#B0B6C9"/>
<path d="M152 257C152 257 160.863 236.189 176.575 243.593C192.187 250.997 190.978 255.799 190.978 255.799L152 257Z" fill="#B0B6C9"/>
<path d="M271.213 238H136.787C134.194 238 132 235.787 132 233.172V139.828C132 137.213 134.194 135 136.787 135H271.213C273.806 135 276 137.213 276 139.828V233.172C276 235.787 273.906 238 271.213 238Z" fill="#363840"/>
<path d="M217.252 258H195.744C193.109 258 191 256 191 253.5V190.5C191 188 193.109 186 195.744 186H217.252C219.888 186 221.996 188 221.996 190.5V253.5C222.102 255.9 219.888 258 217.252 258Z" fill="#363840"/>
<path d="M246.189 254H150.811C149.305 254 148 255.444 148 257.111V258.889C148 260.556 149.305 262 150.811 262H246.189C247.695 262 249 260.556 249 258.889V257.111C249 255.444 247.795 254 246.189 254Z" fill="#363840"/>
<path d="M350.452 224.555C349.952 224.555 349.553 224.555 349.154 224.654C348.355 224.754 347.557 224.256 347.257 223.56C337.873 206.543 319.705 195 298.742 195C279.775 195 263.004 204.454 253.121 218.883C252.622 219.579 251.724 219.878 250.925 219.778C248.429 219.281 245.834 218.982 243.239 218.982C223.772 219.082 208 234.605 208 253.91C208 253.91 208 253.91 208 254.01C208 255.104 208.898 256 210.096 256H377.904C379.002 256 380 255.104 380 254.01V253.91C379.8 237.591 366.623 224.555 350.452 224.555Z" fill="#B0B6C9"/>
<path d="M206 195C210.418 195 214 191.194 214 186.5C214 181.806 210.418 178 206 178C201.582 178 198 181.806 198 186.5C198 191.194 201.582 195 206 195Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,3 +0,0 @@
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.73373 5.74583C9.37871 6.08472 8.80311 6.08472 8.44808 5.74583L5 2.45445L1.55192 5.74583C1.19689 6.08472 0.621288 6.08472 0.266267 5.74583C-0.0887565 5.40694 -0.0887565 4.8575 0.266267 4.51861L5 -4.37114e-07L9.73373 4.51861C10.0888 4.8575 10.0888 5.40695 9.73373 5.74583Z" fill="#354049"/>
</svg>

Before

Width:  |  Height:  |  Size: 446 B

View File

@ -1,3 +0,0 @@
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.372773 0.338888C0.869804 -0.112963 1.67565 -0.112963 2.17268 0.338888L7 4.72741L11.8273 0.338888C12.3243 -0.112963 13.1302 -0.112963 13.6272 0.338888C14.1243 0.790739 14.1243 1.52333 13.6272 1.97519L7 8L0.372773 1.97519C-0.124258 1.52333 -0.124258 0.790739 0.372773 0.338888Z" fill="#2683FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 451 B

View File

@ -1,3 +0,0 @@
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.6272 7.66111C13.1302 8.11296 12.3243 8.11296 11.8273 7.66111L7 3.27259L2.17268 7.66111C1.67565 8.11296 0.869804 8.11296 0.372774 7.66111C-0.124258 7.20926 -0.124258 6.47666 0.372774 6.02481L7 -6.11959e-07L13.6272 6.02481C14.1243 6.47667 14.1243 7.20926 13.6272 7.66111Z" fill="#2683FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 446 B

View File

@ -1,4 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="18" height="17.8349" rx="8.91743" fill="#A9B5C1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.00391 13.2114C9.55619 13.2114 10.0039 12.7637 10.0039 12.2114L10.0039 8.92702C10.0039 8.37474 9.55619 7.92702 9.00391 7.92702C8.45162 7.92702 8.00391 8.37474 8.00391 8.92702L8.00391 12.2114C8.00391 12.7637 8.45162 13.2114 9.00391 13.2114ZM9 6.93616C9.68402 6.93616 10.25 6.38165 10.25 5.69763C10.25 5.01361 9.68402 4.4591 9 4.4591C8.31598 4.4591 7.75 5.01361 7.75 5.69763C7.75 6.38165 8.31598 6.93616 9 6.93616Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 655 B

View File

@ -1,4 +0,0 @@
<svg width="13" height="14" viewBox="0 0 13 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="0.954102" width="13" height="12.8807" rx="6.44037" fill="#A9B5C1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.50288 10.4956C6.90175 10.4956 7.2251 10.1723 7.2251 9.77338L7.2251 7.40131C7.2251 7.00244 6.90175 6.67909 6.50288 6.67909C6.104 6.67909 5.78065 7.00244 5.78065 7.40131L5.78065 9.77338C5.78065 10.1723 6.104 10.4956 6.50288 10.4956ZM6.50005 5.96355C6.99407 5.96355 7.40283 5.56308 7.40283 5.06906C7.40283 4.57504 6.99407 4.17456 6.50005 4.17456C6.00604 4.17456 5.59728 4.57504 5.59728 5.06906C5.59728 5.56308 6.00604 5.96355 6.50005 5.96355Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 696 B

View File

@ -1,5 +0,0 @@
<svg width="24" height="22" viewBox="0 0 24 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="11" cy="11" r="11" fill="#00D459"/>
<path d="M5.17423 11.8324C4.63467 11.2929 4.63467 10.4181 5.17423 9.8785C5.7138 9.33894 6.5886 9.33894 7.12817 9.8785L10.3847 13.1351C10.9243 13.6746 10.9243 14.5494 10.3847 15.089C9.84516 15.6286 8.97036 15.6286 8.43079 15.089L5.17423 11.8324Z" fill="white"/>
<path d="M10.3847 15.089C9.84516 15.6286 8.97036 15.6286 8.43079 15.089C7.89123 14.5494 7.89123 13.6746 8.43079 13.1351L14.2926 7.27325C14.8322 6.73369 15.707 6.73369 16.2465 7.27325C16.7861 7.81282 16.7861 8.68762 16.2465 9.22719L10.3847 15.089Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 675 B

View File

@ -1,44 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import { createLocalVue, mount } from '@vue/test-utils';
import { RouteConfig } from '@/router';
import HistoryDropdown from '@/components/account/billing/HistoryDropdown.vue';
const localVue = createLocalVue();
describe('HistoryDropdown', (): void => {
it('renders correctly if credit history', (): void => {
const creditsHistory: string = RouteConfig.Account.with(RouteConfig.CreditsHistory).path;
const wrapper = mount(HistoryDropdown, {
localVue,
propsData: {
label: 'Credits History',
route: creditsHistory,
},
directives: {
clickOutside: {},
},
});
expect(wrapper).toMatchSnapshot();
});
it('renders correctly if balance history', (): void => {
const balanceHistory: string = RouteConfig.Account.with(RouteConfig.DepositHistory).path;
const wrapper = mount(HistoryDropdown, {
localVue,
propsData: {
label: 'Balance History',
route: balanceHistory,
},
directives: {
clickOutside: {},
},
});
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -1,13 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`HistoryDropdown renders correctly if balance history 1`] = `
<div class="history-dropdown">
<div class="history-dropdown__link-container"><span class="history-dropdown__link-container__link">Balance History</span></div>
</div>
`;
exports[`HistoryDropdown renders correctly if credit history 1`] = `
<div class="history-dropdown">
<div class="history-dropdown__link-container"><span class="history-dropdown__link-container__link">Credits History</span></div>
</div>
`;

View File

@ -1,32 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
import { FrontendConfigApiMock } from '../../../mock/api/config';
import { NotificatorPlugin } from '@/utils/plugins/notificator';
import { makeNotificationsModule } from '@/store/modules/notifications';
import { makeAppStateModule } from '@/store/modules/appState';
import AddCardForm from '@/components/account/billing/paymentMethods/AddCardForm.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const appStateModule = makeAppStateModule(new FrontendConfigApiMock());
const notificationsModule = makeNotificationsModule();
const store = new Vuex.Store({ modules: { appStateModule, notificationsModule } });
localVue.use(new NotificatorPlugin(store));
describe('AddCardForm', () => {
it('renders correctly', () => {
const wrapper = mount(AddCardForm, {
localVue,
});
expect(wrapper).toMatchSnapshot();
});
});

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