Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update copy/styling for usage limit banners and modals #4601

Merged
merged 24 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a9e5440
add basic transitions to button component
LMNTL Aug 24, 2023
78556db
update over limit banner copy + styling
LMNTL Aug 24, 2023
28e3c35
update over limit modal copy + styling
LMNTL Aug 24, 2023
f1f6a2e
fix typo in usage banners
LMNTL Aug 24, 2023
d87f650
fix typo
LMNTL Aug 24, 2023
533bbf9
update item labels in limit warnings
LMNTL Aug 29, 2023
7ca4d78
pull limit notifications into their own component for re-use
LMNTL Aug 29, 2023
dbbaef0
add limit notifications to custom project view
LMNTL Aug 29, 2023
7e63450
only show warning banner if limits aren't exceeded
LMNTL Aug 29, 2023
a5f6e75
add limit notifications to usage page
LMNTL Aug 29, 2023
975dd09
update over limit modal styling and content
LMNTL Aug 29, 2023
16765c2
add useWhen hook
LMNTL Aug 29, 2023
ddf250f
make sure usage page displays when stripe is not enabled
LMNTL Aug 29, 2023
24a81fc
remove old text in banner
LMNTL Aug 29, 2023
761d326
update limit banner copy on usage page
LMNTL Aug 29, 2023
0fc6ee2
Merge remote-tracking branch 'origin/beta' into usage-limits-update-copy
LMNTL Sep 5, 2023
5a4d6ba
add useWhenStripeIsEnabled hook
LMNTL Sep 5, 2023
ae25fe7
add small readme to hooks folder
LMNTL Sep 5, 2023
ea47471
update js/hooks/README.md
LMNTL Sep 5, 2023
8a5cbe6
update warning icon position and Upgrade Now button hover state
LMNTL Sep 7, 2023
b6ee7e7
get all usage containers aligned
LMNTL Sep 8, 2023
109bbc5
don't show warning if usage equals limit
LMNTL Sep 8, 2023
01eba60
use same styling for links in modal as in banners
LMNTL Sep 8, 2023
cdb53bd
show upgrade button for warning banner on usage page
LMNTL Sep 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 7 additions & 5 deletions jsapp/js/account/plan.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import {notify} from 'js/utils';
import {ACTIVE_STRIPE_STATUSES} from 'js/constants';
import type {FreeTierThresholds} from 'js/envStore';
import envStore from 'js/envStore';
import {when} from 'mobx';
import {ACCOUNT_ROUTES} from 'js/account/routes';
import useWhen from 'js/hooks/useWhen.hook';

interface PlanState {
subscribedProduct: null | BaseSubscription;
Expand Down Expand Up @@ -148,8 +148,9 @@ export default function Plan() {
}
}, [state.subscribedProduct]);

useEffect(() => {
when(() => envStore.isReady).then(() => {
useWhen(
() => envStore.isReady,
() => {
// If Stripe isn't loaded, just redirect to the account page
if (!envStore.data.stripe_public_key) {
navigate(ACCOUNT_ROUTES.ACCOUNT_SETTINGS);
Expand Down Expand Up @@ -182,8 +183,9 @@ export default function Plan() {
Promise.all(fetchPromises).then(() => {
setAreButtonsDisabled(false);
});
});
}, [searchParams, shouldRevalidate]);
},
[searchParams, shouldRevalidate]
);

// Re-fetch data from API and re-enable buttons if displaying from back/forward cache
useEffect(() => {
Expand Down
5 changes: 3 additions & 2 deletions jsapp/js/account/stripe.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,9 @@ export async function postCustomerPortal(organizationId: string) {
}

export async function getSubscriptionInterval() {
await when(() => envStore.isReady && subscriptionStore.isInitialised);
await when(() => envStore.isReady);
if (envStore.data.stripe_public_key) {
await when(() => subscriptionStore.isInitialised);
const subscriptionList: SubscriptionInfo[] =
subscriptionStore.subscriptionResponse;
const activeSubscription = subscriptionList.find((sub) =>
Expand All @@ -113,7 +114,7 @@ export async function getSubscriptionInterval() {
return activeSubscription.items[0].price.recurring?.interval;
}
}
return null;
return 'month';
}

export async function getAccountLimits() {
Expand Down
9 changes: 8 additions & 1 deletion jsapp/js/account/usage.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import envStore from 'js/envStore';
import {formatDate, truncateNumber} from 'js/utils';
import {getUsageForOrganization} from './usage.api';
import styles from './usage.module.scss';
import LimitNotifications from 'js/components/usageLimits/limitNotifications.component';

interface UsageState {
storage: number;
Expand Down Expand Up @@ -93,6 +94,12 @@ export default function Usage() {
if (envStore.data.stripe_public_key) {
limits = await getAccountLimits();
} else {
setUsage((prevState) => {
return {
...prevState,
isLimitsLoaded: true,
};
});
return;
}

Expand Down Expand Up @@ -179,7 +186,7 @@ export default function Usage() {
return (
<div className={styles.root}>
<h2>{t('Your account total use')}</h2>

<LimitNotifications usagePage />
<div className={styles.row}>
<div className={styles.box}>
<span>
Expand Down
1 change: 1 addition & 0 deletions jsapp/js/components/common/button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ $button-border-radius: sizes.$x6;
border-color: transparent;
border-radius: $button-border-radius;
background-color: transparent;
transition: background-color 0.2s, opacity 0.2s, color 0.2s;
}

.long-button-padding {
Expand Down
5 changes: 0 additions & 5 deletions jsapp/js/components/usageContainer.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@
margin-top: sizes.$x18;
align-items: center;
justify-content: space-between;

&.empty {
justify-content: start;
gap: 2em;
}
}

.usageRow {
Expand Down
59 changes: 23 additions & 36 deletions jsapp/js/components/usageContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,47 +26,34 @@ const UsageContainer = ({
if (limit !== 'unlimited' && limit) {
limitRatio = usage / limit;
}
const isOverLimit = limitRatio > 1;
const isOverLimit = limitRatio >= 1;
const isNearingLimit = !isOverLimit && limitRatio > USAGE_WARNING_RATIO;
return (
<div
className={classnames(styles.usage, {
[styles.empty]: !usage,
})}
>
<div className={styles.usage}>
<strong className={styles.description}>
{label || (period === 'month' ? t('Monthly') : t('Yearly'))}
</strong>
{!usage && (
<AriaText
uiText='-'
screenReaderText={t('none')}
classNames={classnames(styles.usageRow, styles.empty)}
/>
)}
{Boolean(usage) && (
<div
className={classnames(styles.usageRow, {
[styles.warning]: isNearingLimit,
[styles.overlimit]: isOverLimit,
})}
>
{isNearingLimit && <Icon name='warning' color='amber' size='m' />}
{isOverLimit && <Icon name='warning' color='red' size='m' />}
<strong>
{isStorage ? prettyBytes(usage) : usage.toLocaleString()}
</strong>
{limit !== 'unlimited' && limit && (
<>
{' '}
<AriaText uiText='/' screenReaderText={t('used out of')} />{' '}
<span>
{isStorage ? prettyBytes(limit) : limit.toLocaleString()}
</span>
</>
)}
</div>
)}
<div
className={classnames(styles.usageRow, {
[styles.warning]: isNearingLimit,
[styles.overlimit]: isOverLimit,
})}
>
{isNearingLimit && <Icon name='warning' color='amber' size='m' />}
{isOverLimit && <Icon name='warning' color='red' size='m' />}
<strong>
{isStorage ? prettyBytes(usage) : usage.toLocaleString()}
</strong>
{limit !== 'unlimited' && limit && (
<>
{' '}
<AriaText uiText='/' screenReaderText={t('used out of')} />{' '}
<span>
{isStorage ? prettyBytes(limit) : limit.toLocaleString()}
</span>
</>
)}
</div>
</div>
);
};
Expand Down
90 changes: 90 additions & 0 deletions jsapp/js/components/usageLimits/limitNotifications.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import LimitBanner from 'js/components/usageLimits/overLimitBanner.component';
import LimitModal from 'js/components/usageLimits/overLimitModal.component';
import React, {useState} from 'react';
import {Cookies} from 'react-cookie';
import {
getAllExceedingLimits,
getPlanInterval,
} from 'js/components/usageLimits/usageCalculations';
import useWhenStripeIsEnabled from 'js/hooks/useWhenStripeIsEnabled.hook';

const cookies = new Cookies();

interface LimitNotificationsProps {
useModal?: boolean;
usagePage?: boolean;
}

const LimitNotifications = ({
useModal = false,
usagePage = false,
}: LimitNotificationsProps) => {
const [showModal, setShowModal] = useState(false);
const [dismissed, setDismissed] = useState(!useModal);
const [stripeEnabled, setStripeEnabled] = useState(false);

const limits = getAllExceedingLimits();
const interval = getPlanInterval();

useWhenStripeIsEnabled(() => {
setStripeEnabled(true);
// only check cookies if we're displaying a modal
if (!useModal) {
return;
}
const limitsCookie = cookies.get('kpiOverLimitsCookie');
if (
limitsCookie === undefined &&
(limits.exceedList.includes('storage') ||
limits.exceedList.includes('submission'))
) {
setShowModal(true);
}
if (limitsCookie) {
setDismissed(true);
}
}, [limits]);

const modalDismissed = () => {
setDismissed(true);
const dateNow = new Date();
const expireDate = new Date(dateNow.setDate(dateNow.getDate() + 1));
cookies.set('kpiOverLimitsCookie', {
expires: expireDate,
});
};

if (!stripeEnabled) {
return null;
}

return (
<>
{dismissed && (
<LimitBanner
interval={interval}
limits={limits.exceedList}
usagePage={usagePage}
/>
)}
{!limits.exceedList.length && (
<LimitBanner
warning
interval={interval}
limits={limits.warningList}
usagePage={usagePage}
/>
)}
{useModal && (
<LimitModal
show={showModal}
limits={limits.exceedList}
interval={interval}
dismissed={modalDismissed}
/>
)}
</>
);
};

export default LimitNotifications;
58 changes: 39 additions & 19 deletions jsapp/js/components/usageLimits/overLimitBanner.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface OverLimitBannerProps {
warning?: boolean;
limits: string[];
interval: string;
usagePage: boolean;
}

const OverLimitBanner = (props: OverLimitBannerProps) => {
Expand All @@ -31,44 +32,63 @@ const OverLimitBanner = (props: OverLimitBannerProps) => {
/>
<div className={styles.bannerContent}>
{props.warning
? t('You are close to surpassing your')
: t('You have surpassed your')}{' '}
? t('You are approaching your')
: t('You have reached your')}{' '}
<strong>
{`${props.interval}ly`}{' '}
{props.interval === 'month' ? t('monthly') : t('yearly')}{' '}
{props.limits.map((item, i) => (
<span key={i}>
{i > 0 && ', '}
{i === props.limits.length - 1 && i > 0 && 'and '}
{i > 0 && props.limits.length > 2 && ', '}
{i === props.limits.length - 1 && i > 0 && t(' and ')}
{item}
</span>
))}{' '}
{t('limit')}
{props.limits.length > 1 && 's'}
</strong>
{'. '}
{t('Please')}{' '}
{props.warning && (
<>
{t(
'Please review you current usage and consider upgrading to a plan with larger capacity if necessary. You can'
)}{' '}
{!props.usagePage && (
<>
<a
href={`#${ACCOUNT_ROUTES.USAGE}`}
className={styles.bannerLink}
>
{t('review your usage')}
</a>{' '}
{t('and')}{' '}
</>
)}
<a href={`#${ACCOUNT_ROUTES.PLAN}`} className={styles.bannerLink}>
{t('upgrade in the plans section')}
</a>
{t('upgrade your plan')}
</a>{' '}
{props.usagePage ? t('as soon as possible') : t('if needed')}
</>
)}
{!props.warning && (
<>
{t(
'Please upgrade to a plan with a larger capacity to continue collecting data this ##PERIOD##. You can'
).replace(/##PERIOD##/g, props.interval)}{' '}
<a href={`#${ACCOUNT_ROUTES.USAGE}`} className={styles.bannerLink}>
{t('review your usage here')}
</a>
<a href={`#${ACCOUNT_ROUTES.PLAN}`} className={styles.bannerLink}>
{t('upgrade your plan')}
</a>{' '}
{t('to continue collecting data')}
{!props.usagePage && (
<>
{'. '}
<a
href={`#${ACCOUNT_ROUTES.USAGE}`}
className={styles.bannerLink}
>
{t('Review your usage in account settings')}
</a>
</>
)}
</>
)}
{'.'}
</div>
{props.warning && (
{props.warning && !props.usagePage && (
<Button
type={'frame'}
color={'dark-blue'}
Expand All @@ -80,9 +100,9 @@ const OverLimitBanner = (props: OverLimitBannerProps) => {
classNames={[styles.bannerBtn]}
/>
)}
{!props.warning && (
{(!props.warning || props.usagePage) && (
<Button
type={'full'}
type={'frame'}
color={'dark-red'}
endIcon='arrow-right'
size='s'
Expand Down
2 changes: 2 additions & 0 deletions jsapp/js/components/usageLimits/overLimitBanner.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
display: flex;
padding: 0 2%;
align-items: center;
justify-content: flex-start;
min-height: 75px;
flex-shrink: 0;
border-radius: 0.5em;
Expand All @@ -28,6 +29,7 @@
flex-shrink: 0;
min-width: 120px !important;
height: 32px;
margin-inline: auto 0;
}


Expand Down