Skip to content

Commit 3a013a4

Browse files
teallarsonTim Roes
andauthored
🪟 🎨 Trial end banner (#14913)
* alert banner * cleanup * cleanup * review cleanup * Minor cleanup Co-authored-by: Tim Roes <[email protected]>
1 parent 291e91e commit 3a013a4

File tree

5 files changed

+76
-44
lines changed

5 files changed

+76
-44
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@use "../../../scss/colors";
2+
3+
.alertBannerContainer {
4+
height: 30px;
5+
width: 100%;
6+
text-align: center;
7+
position: fixed;
8+
z-index: 3;
9+
font-size: 12px;
10+
line-height: 30px;
11+
color: colors.$black;
12+
13+
& a {
14+
color: colors.$black;
15+
}
16+
}
17+
18+
.beige {
19+
background-color: colors.$beige-100;
20+
}
21+
22+
.red {
23+
background-color: colors.$red;
24+
}
Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,18 @@
1+
import classnames from "classnames";
12
import React from "react";
2-
import { FormattedMessage } from "react-intl";
3-
import styled from "styled-components";
43

5-
import { Link } from "components/Link";
6-
7-
import { CloudRoutes } from "packages/cloud/cloudRoutes";
8-
import { CreditStatus } from "packages/cloud/lib/domain/cloudWorkspaces/types";
9-
10-
const Container = styled.div<{ errorType?: string }>`
11-
height: 30px;
12-
width: 100%;
13-
background: ${({ errorType, theme }) => (errorType === "credits" ? theme.redColor : theme.warningColor)};
14-
color: ${({ theme }) => theme.blackColor};
15-
text-align: center;
16-
position: fixed;
17-
z-index: 3;
18-
font-size: 12px;
19-
line-height: 30px;
20-
`;
21-
const CreditsLink = styled(Link)`
22-
color: ${({ theme }) => theme.blackColor};
23-
`;
4+
import styles from "./AlertBanner.module.scss";
245

256
interface AlertBannerProps {
26-
alertType: string;
27-
id: CreditStatus | string;
7+
color?: "default" | "warning";
8+
message: React.ReactNode;
289
}
2910

30-
export const AlertBanner: React.FC<AlertBannerProps> = ({ alertType: errorType, id }) => (
31-
<Container errorType={errorType}>
32-
{errorType === "credits" ? (
33-
<FormattedMessage
34-
id={id}
35-
values={{ lnk: (content: React.ReactNode) => <CreditsLink to={CloudRoutes.Credits}>{content}</CreditsLink> }}
36-
/>
37-
) : (
38-
<FormattedMessage id={id} />
39-
)}
40-
</Container>
41-
);
11+
export const AlertBanner: React.FC<AlertBannerProps> = ({ color, message }) => {
12+
const bannerStyle = classnames(styles.alertBannerContainer, {
13+
[styles.beige]: color === "default" || !color,
14+
[styles.red]: color === "warning",
15+
});
16+
17+
return <div className={bannerStyle}>{message}</div>;
18+
};

airbyte-webapp/src/packages/cloud/lib/domain/cloudWorkspaces/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface CloudWorkspace {
1212
remainingCredits: number;
1313
creditStatus?: CreditStatus;
1414
lastCreditPurchaseIncrementTimestamp?: number | null;
15+
trialExpiryTimestamp?: number | null;
1516
}
1617

1718
export interface CreditConsumptionByConnector {

airbyte-webapp/src/packages/cloud/locales/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
"modals.addUser.button.cancel": "Cancel",
100100
"modals.addUser.button.submit": "Send invitation",
101101
"workspaces.viewAllWorkspaces": "View all workspaces",
102-
"settings.accessManagement.roleViewers": "<b>Viewers</b> are in read-only and cannot edit or\u00a0add connections.",
102+
"settings.accessManagement.roleViewers": "<b>Viewers</b> are in read-only and cannot edit or add connections.",
103103
"settings.accessManagement.roleEditors": "<b>Editors</b> can edit connections",
104104
"settings.accessManagement.roleAdmin": "<b>Admin</b> can also manage users",
105105

@@ -130,6 +130,8 @@
130130
"password.validation": "Your password is too weak",
131131
"password.invalid": "Invalid password",
132132

133+
"trial.alertMessage": "You are using a trial of Airbyte. Your trial ends in <b>{value, plural, one {# day} other {# days}}</b>.",
134+
133135
"verifyEmail.notification": "You successfully verified your email. Thank you.",
134136

135137
"webapp.cannotReachServer": "Cannot reach server."

airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.tsx

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import React from "react";
2-
import { Outlet } from "react-router-dom";
1+
import React, { useMemo } from "react";
2+
import { useIntl } from "react-intl";
3+
import { Link, Outlet } from "react-router-dom";
34
import styled from "styled-components";
45

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

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

3840
const MainView: React.FC = (props) => {
41+
const { formatMessage } = useIntl();
3942
const workspace = useCurrentWorkspace();
4043
const cloudWorkspace = useGetCloudWorkspace(workspace.workspaceId);
41-
const showBanner =
44+
const showCreditsBanner =
4245
cloudWorkspace.creditStatus &&
4346
[
4447
CreditStatus.NEGATIVE_BEYOND_GRACE_PERIOD,
4548
CreditStatus.NEGATIVE_MAX_THRESHOLD,
4649
CreditStatus.NEGATIVE_WITHIN_GRACE_PERIOD,
47-
].includes(cloudWorkspace.creditStatus);
50+
].includes(cloudWorkspace.creditStatus) &&
51+
!cloudWorkspace.trialExpiryTimestamp;
52+
53+
const alertToShow = showCreditsBanner ? "credits" : cloudWorkspace.trialExpiryTimestamp ? "trial" : undefined;
54+
55+
const alertMessage = useMemo(() => {
56+
if (alertToShow === "credits") {
57+
return formatMessage(
58+
{ id: `credits.creditsProblem.${cloudWorkspace.creditStatus}` },
59+
{
60+
values: {
61+
lnk: (content: React.ReactNode) => <Link to={CloudRoutes.Credits}>{content}</Link>,
62+
},
63+
}
64+
);
65+
} else if (alertToShow === "trial") {
66+
const { trialExpiryTimestamp } = cloudWorkspace;
67+
68+
//calculate difference between timestamp (in epoch seconds) and now (in epoch seconds)
69+
const trialRemainingSeconds = trialExpiryTimestamp ? trialExpiryTimestamp - Date.now() / 1000 : 0;
70+
71+
//calculate days (rounding up if decimal)
72+
const trialRemainingDays = Math.ceil(trialRemainingSeconds / (24 * 60 * 60));
73+
74+
return formatMessage({ id: "trial.alertMessage" }, { value: trialRemainingDays });
75+
}
76+
return null;
77+
}, [alertToShow, cloudWorkspace, formatMessage]);
4878

4979
return (
5080
<MainContainer>
5181
<InsufficientPermissionsErrorBoundary errorComponent={<StartOverErrorView />}>
5282
<SideBar />
5383
<Content>
54-
{cloudWorkspace.creditStatus && showBanner && (
55-
<AlertBanner alertType="credits" id={`credits.creditsProblem.${cloudWorkspace.creditStatus}`} />
56-
)}
57-
<DataBlock hasBanner={showBanner}>
84+
{alertToShow && <AlertBanner message={alertMessage} />}
85+
<DataBlock hasBanner={!!alertToShow}>
5886
<ResourceNotFoundErrorBoundary errorComponent={<StartOverErrorView />}>
5987
<React.Suspense fallback={<LoadingPage />}>{props.children ?? <Outlet />}</React.Suspense>
6088
</ResourceNotFoundErrorBoundary>

0 commit comments

Comments
 (0)