Skip to content

Commit

Permalink
web/satellite: display correct prices in account upgrade modal
Browse files Browse the repository at this point in the history
The account upgrade modal has been updated to display prices according
to the the user's price model. Previously, the modal displayed only the
default prices which were incorrect for users with price overrides.

Resolves storj/storj-private#187

Change-Id: I58206cc8ea7e7742a37f759a84dbb24ca40dd8eb
  • Loading branch information
jewharton committed Mar 17, 2023
1 parent 6942da3 commit e0c3f66
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
</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>
<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>
Expand Down Expand Up @@ -53,7 +53,7 @@ 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 } from '@/utils/strings';
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';
Expand All @@ -68,12 +68,6 @@ const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
*/
const HOURS_IN_MONTH = 720;
/**
* CENTS_MB_TO_DOLLARS_GB_SHIFT constant represents how many places to the left
* a decimal point must be shifted to convert from cents/MB to dollars/GB.
*/
const CENTS_MB_TO_DOLLARS_GB_SHIFT = -1;
const props = withDefaults(defineProps<{
/**
* item represents usage and charges of current project by period.
Expand Down Expand Up @@ -144,21 +138,21 @@ const segmentCountFormatted = computed((): string => {
* Returns storage price per GB.
*/
const storagePrice = computed((): string => {
return decimalShift(priceModel.value.storageMBMonthCents, CENTS_MB_TO_DOLLARS_GB_SHIFT);
return formatPrice(decimalShift(priceModel.value.storageMBMonthCents, CENTS_MB_TO_DOLLARS_GB_SHIFT));
});
/**
* Returns egress price per GB.
*/
const egressPrice = computed((): string => {
return decimalShift(priceModel.value.egressMBCents, CENTS_MB_TO_DOLLARS_GB_SHIFT);
return formatPrice(decimalShift(priceModel.value.egressMBCents, CENTS_MB_TO_DOLLARS_GB_SHIFT));
});
/**
* Returns segment price.
*/
const segmentPrice = computed((): string => {
return decimalShift(priceModel.value.segmentMonthCents, 2);
return formatPrice(decimalShift(priceModel.value.segmentMonthCents, 2));
});
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
</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 <span class="price-per-month">(${{ storagePrice }} per Gigabyte-Month)</span></p>
<p>Egress <span class="price-per-month">(${{ egressPrice }} per GB)</span></p>
<p>Segments <span class="price-per-month">(${{ segmentPrice }} per Segment-Month)</span></p>
<p>Storage <span class="price-per-month">({{ storagePrice }} per Gigabyte-Month)</span></p>
<p>Egress <span class="price-per-month">({{ egressPrice }} per GB)</span></p>
<p>Segments <span class="price-per-month">({{ segmentPrice }} per Segment-Month)</span></p>
</div>
<div class="usage-charges-item-container__detailed-info-container__content-area__period-container">
<p>{{ period }}</p>
Expand Down Expand Up @@ -58,7 +58,7 @@ 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 } from '@/utils/strings';
import { decimalShift, formatPrice, CENTS_MB_TO_DOLLARS_GB_SHIFT } from '@/utils/strings';
import { useStore } from '@/utils/hooks';
import GreyChevron from '@/../static/images/common/greyChevron.svg';
Expand All @@ -68,12 +68,6 @@ import GreyChevron from '@/../static/images/common/greyChevron.svg';
*/
const HOURS_IN_MONTH = 720;
/**
* CENTS_MB_TO_DOLLARS_GB_SHIFT constant represents how many places to the left
* a decimal point must be shifted to convert from cents/MB to dollars/GB.
*/
const CENTS_MB_TO_DOLLARS_GB_SHIFT = -1;
const props = withDefaults(defineProps<{
/**
* item represents usage and charges of current project by period.
Expand Down Expand Up @@ -144,21 +138,21 @@ const segmentCountFormatted = computed((): string => {
* Returns storage price per GB.
*/
const storagePrice = computed((): string => {
return decimalShift(priceModel.value.storageMBMonthCents, CENTS_MB_TO_DOLLARS_GB_SHIFT);
return formatPrice(decimalShift(priceModel.value.storageMBMonthCents, CENTS_MB_TO_DOLLARS_GB_SHIFT));
});
/**
* Returns egress price per GB.
*/
const egressPrice = computed((): string => {
return decimalShift(priceModel.value.egressMBCents, CENTS_MB_TO_DOLLARS_GB_SHIFT);
return formatPrice(decimalShift(priceModel.value.egressMBCents, CENTS_MB_TO_DOLLARS_GB_SHIFT));
});
/**
* Returns segment price.
*/
const segmentPrice = computed((): string => {
return decimalShift(priceModel.value.segmentMonthCents, 2);
return formatPrice(decimalShift(priceModel.value.segmentMonthCents, 2));
});
/**
Expand Down
58 changes: 54 additions & 4 deletions web/satellite/src/components/modals/AddPaymentMethodModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,16 @@
<p class="add-modal__bullets__left__item__label">100 request per second rate limit</p>
</div>
</div>
<div class="add-modal__bullets__right">
<VLoader v-if="isPriceFetching" class="add-modal__bullets__right-loader" width="90px" height="90px" />
<div v-else class="add-modal__bullets__right">
<h2 class="add-modal__bullets__right__title">Storage price:</h2>
<div class="add-modal__bullets__right__item">
<p class="add-modal__bullets__right__item__price">$4</p>
<p class="add-modal__bullets__right__item__price">{{ storagePrice }}</p>
<p class="add-modal__bullets__right__item__label">TB / month</p>
</div>
<h2 class="add-modal__bullets__right__title">Bandwidth price:</h2>
<div class="add-modal__bullets__right__item">
<p class="add-modal__bullets__right__item__price">$7</p>
<p class="add-modal__bullets__right__item__price">{{ bandwidthPrice }}</p>
<p class="add-modal__bullets__right__item__label">TB</p>
</div>
</div>
Expand Down Expand Up @@ -125,7 +126,7 @@
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import { computed, onMounted, ref } from 'vue';
import { useNotify, useRoute, useStore } from '@/utils/hooks';
import { RouteConfig } from '@/router';
Expand All @@ -137,6 +138,8 @@ import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import { ProjectUsagePriceModel } from '@/types/payments';
import { decimalShift, formatPrice, CENTS_MB_TO_DOLLARS_TB_SHIFT } from '@/utils/strings';
import VModal from '@/components/common/VModal.vue';
import VLoader from '@/components/common/VLoader.vue';
Expand All @@ -160,9 +163,23 @@ const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const isAddModal = ref<boolean>(true);
const isAddCard = ref<boolean>(true);
const isLoading = ref<boolean>(false);
const isPriceFetching = ref<boolean>(true);
const stripeCardInput = ref<StripeCardInput & StripeForm | null>(null);
/**
* Lifecycle hook after initial render.
* Fetches project usage price model.
*/
onMounted(async () => {
try {
await store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_PRICE_MODEL);
isPriceFetching.value = false;
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
}
});
/**
* Provides card information to Stripe.
*/
Expand Down Expand Up @@ -234,6 +251,29 @@ function setIsAddCard(): void {
const limitsIncreaseRequestURL = computed((): string => {
return MetaUtils.getMetaContent('project-limits-increase-request-url');
});
/**
* Returns project usage price model from store.
*/
const priceModel = computed((): ProjectUsagePriceModel => {
return store.state.paymentsModule.usagePriceModel;
});
/**
* Returns the storage price formatted as dollars per terabyte.
*/
const storagePrice = computed((): string => {
const storage = priceModel.value.storageMBMonthCents.toString();
return formatPrice(decimalShift(storage, CENTS_MB_TO_DOLLARS_TB_SHIFT));
});
/**
* Returns the bandwidth (egress) price formatted as dollars per terabyte.
*/
const bandwidthPrice = computed((): string => {
const egress = priceModel.value.egressMBCents.toString();
return formatPrice(decimalShift(egress, CENTS_MB_TO_DOLLARS_TB_SHIFT));
});
</script>

<style scoped lang="scss">
Expand Down Expand Up @@ -478,6 +518,16 @@ const limitsIncreaseRequestURL = computed((): string => {
}
}
&__right-loader {
width: 50%;
align-items: center;
@media screen and (max-width: 570px) {
width: 100%;
margin-top: 16px;
}
}
&__right {
padding-left: 50px;
Expand Down
39 changes: 39 additions & 0 deletions web/satellite/src/utils/strings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

/**
* CENTS_MB_TO_DOLLARS_GB_SHIFT constant represents how many places to the left
* a decimal point must be shifted to convert from cents/MB to dollars/GB.
*/
export const CENTS_MB_TO_DOLLARS_GB_SHIFT = -1;

/**
* CENTS_MB_TO_DOLLARS_TB_SHIFT constant represents how many places to the left
* a decimal point must be shifted to convert from cents/MB to dollars/TB.
*/
export const CENTS_MB_TO_DOLLARS_TB_SHIFT = -4;

/**
* decimalShift shifts the decimal point of a number represented by the given string.
* @param decimal - the string representation of the number
Expand Down Expand Up @@ -39,3 +51,30 @@ export function decimalShift(decimal: string, places: number): string {
}
return sign + (int || '0') + (frac ? '.' + frac : '');
}

/**
* formatPrice formats the decimal string as a price.
* @param decimal - the decimal string to format
*/
export function formatPrice(decimal: string) {
let sign = '';
if (decimal[0] === '-') {
sign = '-';
decimal = decimal.substring(1);
}

const parts = decimal.split('.');
const int = parts[0]?.replace(/^0+/, '');
let frac = '';
if (parts.length > 1) {
frac = parts[1].replace(/0+$/, '');
if (frac) {
frac = frac.padEnd(2, '0');
}
}
if (!int && !frac) {
return '$0';
}

return sign + '$' + (int || '0') + (frac ? '.' + frac : '');
}
41 changes: 37 additions & 4 deletions web/satellite/tests/unit/utils/strings.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

import { decimalShift } from '@/utils/strings';
import { decimalShift, formatPrice } from '@/utils/strings';

describe('decimalShift', (): void => {
it('shifts integers correctly', function() {
it('handles empty strings', (): void => {
expect(decimalShift('', 0)).toBe('0');
expect(decimalShift('', 2)).toBe('0');
expect(decimalShift('', -2)).toBe('0');
});

it('shifts integers correctly', (): void => {
['', '-'].forEach(sign => {
const decimal = sign+'123';
expect(decimalShift(decimal, 0)).toBe(sign+'123');
Expand All @@ -14,7 +20,7 @@ describe('decimalShift', (): void => {
});
});

it('shifts decimals correctly', function() {
it('shifts decimals correctly', (): void => {
['', '-'].forEach(sign => {
const decimal = sign+'1.23';
expect(decimalShift(decimal, 0)).toBe(sign+'1.23');
Expand All @@ -24,11 +30,38 @@ describe('decimalShift', (): void => {
});
});

it('trims unnecessary characters', function() {
it('trims unnecessary characters', (): void => {
['', '-'].forEach(sign => {
expect(decimalShift(sign+'0.0012300', -2)).toBe(sign+'0.123');
expect(decimalShift(sign+'12300', 2)).toBe(sign+'123');
expect(decimalShift(sign+'000.000', 1)).toBe('0');
});
});
});

describe('formatPrice', (): void => {
it('handles empty strings', (): void => {
expect(formatPrice('')).toBe('$0');
});

it('formats correctly', (): void => {
['', '-'].forEach(sign => {
expect(formatPrice(sign+'123')).toBe(sign+'$123');
expect(formatPrice(sign+'1.002')).toBe(sign+'$1.002');
});
});

it('adds zeros when necessary', (): void => {
['', '-'].forEach(sign => {
expect(formatPrice(sign+'12.3')).toBe(sign+'$12.30');
expect(formatPrice(sign+'.123')).toBe(sign+'$0.123');
});
});

it('trims unnecessary characters', (): void => {
['', '-'].forEach(sign => {
expect(formatPrice(sign+'0.0')).toBe('$0');
expect(formatPrice(sign+'00123.00')).toBe(sign+'$123');
});
});
});

0 comments on commit e0c3f66

Please sign in to comment.