Skip to content

Commit

Permalink
🪟 🎨 Trial end banner (#14913)
Browse files Browse the repository at this point in the history
* alert banner

* cleanup

* cleanup

* review cleanup

* Minor cleanup

Co-authored-by: Tim Roes <tim@airbyte.io>
  • Loading branch information
teallarson and timroes authored Jul 28, 2022
1 parent 291e91e commit 3a013a4
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 44 deletions.
24 changes: 24 additions & 0 deletions airbyte-webapp/src/components/base/Banner/AlertBanner.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@use "../../../scss/colors";

.alertBannerContainer {
height: 30px;
width: 100%;
text-align: center;
position: fixed;
z-index: 3;
font-size: 12px;
line-height: 30px;
color: colors.$black;

& a {
color: colors.$black;
}
}

.beige {
background-color: colors.$beige-100;
}

.red {
background-color: colors.$red;
}
47 changes: 12 additions & 35 deletions airbyte-webapp/src/components/base/Banner/AlertBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,18 @@
import classnames from "classnames";
import React from "react";
import { FormattedMessage } from "react-intl";
import styled from "styled-components";

import { Link } from "components/Link";

import { CloudRoutes } from "packages/cloud/cloudRoutes";
import { CreditStatus } from "packages/cloud/lib/domain/cloudWorkspaces/types";

const Container = styled.div<{ errorType?: string }>`
height: 30px;
width: 100%;
background: ${({ errorType, theme }) => (errorType === "credits" ? theme.redColor : theme.warningColor)};
color: ${({ theme }) => theme.blackColor};
text-align: center;
position: fixed;
z-index: 3;
font-size: 12px;
line-height: 30px;
`;
const CreditsLink = styled(Link)`
color: ${({ theme }) => theme.blackColor};
`;
import styles from "./AlertBanner.module.scss";

interface AlertBannerProps {
alertType: string;
id: CreditStatus | string;
color?: "default" | "warning";
message: React.ReactNode;
}

export const AlertBanner: React.FC<AlertBannerProps> = ({ alertType: errorType, id }) => (
<Container errorType={errorType}>
{errorType === "credits" ? (
<FormattedMessage
id={id}
values={{ lnk: (content: React.ReactNode) => <CreditsLink to={CloudRoutes.Credits}>{content}</CreditsLink> }}
/>
) : (
<FormattedMessage id={id} />
)}
</Container>
);
export const AlertBanner: React.FC<AlertBannerProps> = ({ color, message }) => {
const bannerStyle = classnames(styles.alertBannerContainer, {
[styles.beige]: color === "default" || !color,
[styles.red]: color === "warning",
});

return <div className={bannerStyle}>{message}</div>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface CloudWorkspace {
remainingCredits: number;
creditStatus?: CreditStatus;
lastCreditPurchaseIncrementTimestamp?: number | null;
trialExpiryTimestamp?: number | null;
}

export interface CreditConsumptionByConnector {
Expand Down
4 changes: 3 additions & 1 deletion airbyte-webapp/src/packages/cloud/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
"modals.addUser.button.cancel": "Cancel",
"modals.addUser.button.submit": "Send invitation",
"workspaces.viewAllWorkspaces": "View all workspaces",
"settings.accessManagement.roleViewers": "<b>Viewers</b> are in read-only and cannot edit or\u00a0add connections.",
"settings.accessManagement.roleViewers": "<b>Viewers</b> are in read-only and cannot edit or add connections.",
"settings.accessManagement.roleEditors": "<b>Editors</b> can edit connections",
"settings.accessManagement.roleAdmin": "<b>Admin</b> can also manage users",

Expand Down Expand Up @@ -130,6 +130,8 @@
"password.validation": "Your password is too weak",
"password.invalid": "Invalid password",

"trial.alertMessage": "You are using a trial of Airbyte. Your trial ends in <b>{value, plural, one {# day} other {# days}}</b>.",

"verifyEmail.notification": "You successfully verified your email. Thank you.",

"webapp.cannotReachServer": "Cannot reach server."
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from "react";
import { Outlet } from "react-router-dom";
import React, { useMemo } from "react";
import { useIntl } from "react-intl";
import { Link, Outlet } from "react-router-dom";
import styled from "styled-components";

import { LoadingPage } from "components";
import { AlertBanner } from "components/base/Banner/AlertBanner";

import { CloudRoutes } from "packages/cloud/cloudRoutes";
import { CreditStatus } from "packages/cloud/lib/domain/cloudWorkspaces/types";
import { useGetCloudWorkspace } from "packages/cloud/services/workspaces/WorkspacesService";
import SideBar from "packages/cloud/views/layout/SideBar";
Expand Down Expand Up @@ -36,25 +38,51 @@ const DataBlock = styled.div<{ hasBanner?: boolean }>`
`;

const MainView: React.FC = (props) => {
const { formatMessage } = useIntl();
const workspace = useCurrentWorkspace();
const cloudWorkspace = useGetCloudWorkspace(workspace.workspaceId);
const showBanner =
const showCreditsBanner =
cloudWorkspace.creditStatus &&
[
CreditStatus.NEGATIVE_BEYOND_GRACE_PERIOD,
CreditStatus.NEGATIVE_MAX_THRESHOLD,
CreditStatus.NEGATIVE_WITHIN_GRACE_PERIOD,
].includes(cloudWorkspace.creditStatus);
].includes(cloudWorkspace.creditStatus) &&
!cloudWorkspace.trialExpiryTimestamp;

const alertToShow = showCreditsBanner ? "credits" : cloudWorkspace.trialExpiryTimestamp ? "trial" : undefined;

const alertMessage = useMemo(() => {
if (alertToShow === "credits") {
return formatMessage(
{ id: `credits.creditsProblem.${cloudWorkspace.creditStatus}` },
{
values: {
lnk: (content: React.ReactNode) => <Link to={CloudRoutes.Credits}>{content}</Link>,
},
}
);
} else if (alertToShow === "trial") {
const { trialExpiryTimestamp } = cloudWorkspace;

//calculate difference between timestamp (in epoch seconds) and now (in epoch seconds)
const trialRemainingSeconds = trialExpiryTimestamp ? trialExpiryTimestamp - Date.now() / 1000 : 0;

//calculate days (rounding up if decimal)
const trialRemainingDays = Math.ceil(trialRemainingSeconds / (24 * 60 * 60));

return formatMessage({ id: "trial.alertMessage" }, { value: trialRemainingDays });
}
return null;
}, [alertToShow, cloudWorkspace, formatMessage]);

return (
<MainContainer>
<InsufficientPermissionsErrorBoundary errorComponent={<StartOverErrorView />}>
<SideBar />
<Content>
{cloudWorkspace.creditStatus && showBanner && (
<AlertBanner alertType="credits" id={`credits.creditsProblem.${cloudWorkspace.creditStatus}`} />
)}
<DataBlock hasBanner={showBanner}>
{alertToShow && <AlertBanner message={alertMessage} />}
<DataBlock hasBanner={!!alertToShow}>
<ResourceNotFoundErrorBoundary errorComponent={<StartOverErrorView />}>
<React.Suspense fallback={<LoadingPage />}>{props.children ?? <Outlet />}</React.Suspense>
</ResourceNotFoundErrorBoundary>
Expand Down

0 comments on commit 3a013a4

Please sign in to comment.