diff --git a/pkg/ui/src/redux/alerts.spec.ts b/pkg/ui/src/redux/alerts.spec.ts index 6a4f2b07b6b1..325945b59289 100644 --- a/pkg/ui/src/redux/alerts.spec.ts +++ b/pkg/ui/src/redux/alerts.spec.ts @@ -22,9 +22,14 @@ import { AdminUIState, createAdminUIStore } from "./state"; import { AlertLevel, alertDataSync, - staggeredVersionWarningSelector, staggeredVersionDismissedSetting, - newVersionNotificationSelector, newVersionDismissedLocalSetting, - disconnectedAlertSelector, disconnectedDismissedLocalSetting, + staggeredVersionWarningSelector, + staggeredVersionDismissedSetting, + newVersionNotificationSelector, + newVersionDismissedLocalSetting, + disconnectedAlertSelector, + disconnectedDismissedLocalSetting, + emailSubscriptionAlertLocalSetting, + emailSubscriptionAlertSelector, } from "./alerts"; import { versionsSelector } from "src/redux/nodes"; import { @@ -350,6 +355,26 @@ describe("alerts", function() { }); }); }); + + describe("email signup for release notes alert", () => { + it("initialized with default 'false' (hidden) state", () => { + const settingState = emailSubscriptionAlertLocalSetting.selector(state()); + assert.isFalse(settingState); + }); + + it("dismissed by alert#dismiss", async () => { + // set alert to open state + dispatch(emailSubscriptionAlertLocalSetting.set(true)); + let openState = emailSubscriptionAlertLocalSetting.selector(state()); + assert.isTrue(openState); + + // dismiss alert + const alert = emailSubscriptionAlertSelector(state()); + await alert.dismiss(dispatch, state); + openState = emailSubscriptionAlertLocalSetting.selector(state()); + assert.isFalse(openState); + }); + }); }); describe("data sync listener", function() { diff --git a/pkg/ui/src/redux/alerts.ts b/pkg/ui/src/redux/alerts.ts index 3a19ef747d62..4086fc7c9c02 100644 --- a/pkg/ui/src/redux/alerts.ts +++ b/pkg/ui/src/redux/alerts.ts @@ -33,6 +33,7 @@ export enum AlertLevel { NOTIFICATION, WARNING, CRITICAL, + SUCCESS, } export interface AlertInfo { @@ -50,6 +51,9 @@ export interface Alert extends AlertInfo { // ThunkAction which will result in this alert being dismissed. This // function will be dispatched to the redux store when the alert is dismissed. dismiss: ThunkAction, AdminUIState, void>; + // Makes alert to be positioned in the top right corner of the screen instead of + // stretching to full width. + showAsAlert?: boolean; } const localSettingsSelector = (state: AdminUIState) => state.localSettings; @@ -240,6 +244,29 @@ export const disconnectedAlertSelector = createSelector( }, ); +export const emailSubscriptionAlertLocalSetting = new LocalSetting( + "email_subscription_alert", localSettingsSelector, false, +); + +export const emailSubscriptionAlertSelector = createSelector( + emailSubscriptionAlertLocalSetting.selector, + ( emailSubscriptionAlert): Alert => { + if (!emailSubscriptionAlert) { + return undefined; + } + return { + level: AlertLevel.SUCCESS, + title: "You successfully signed up for CockroachDB release notes", + text: "You will receive emails about CockroachDB releases and best practices. You can unsubscribe from these emails anytime.", + showAsAlert: true, + dismiss: (dispatch: Dispatch) => { + dispatch(emailSubscriptionAlertLocalSetting.set(false)); + return Promise.resolve(); + }, + }; + }, +); + /** * Selector which returns an array of all active alerts which should be * displayed in the alerts panel, which is embedded within the cluster overview @@ -261,6 +288,7 @@ export const panelAlertsSelector = createSelector( */ export const bannerAlertsSelector = createSelector( disconnectedAlertSelector, + emailSubscriptionAlertSelector, (...alerts: Alert[]): Alert[] => { return _.without(alerts, null, undefined); }, diff --git a/pkg/ui/src/views/app/containers/alertBanner/index.tsx b/pkg/ui/src/views/app/containers/alertBanner/index.tsx index ebf522aba764..25bbbc93160a 100644 --- a/pkg/ui/src/views/app/containers/alertBanner/index.tsx +++ b/pkg/ui/src/views/app/containers/alertBanner/index.tsx @@ -17,6 +17,7 @@ import "./alertbanner.styl"; import { AlertBox } from "src/views/shared/components/alertBox"; import { Alert, bannerAlertsSelector } from "src/redux/alerts"; import { AdminUIState } from "src/redux/state"; +import { AlertMessage } from "src/views/shared/components/alertMessage"; interface AlertBannerProps { /** @@ -45,9 +46,14 @@ class AlertBanner extends React.Component { // Display only the first visible component. const { dismiss, ...alertProps } = alerts[0]; const boundDismiss = bindActionCreators(() => dismiss, dispatch); - return
- -
; + // tslint:disable-next-line:variable-name + const AlertComponent = alertProps.showAsAlert ? AlertMessage : AlertBox; + + return ( +
+ +
+ ); } } diff --git a/pkg/ui/src/views/shared/components/alertMessage/alertMessage.styl b/pkg/ui/src/views/shared/components/alertMessage/alertMessage.styl new file mode 100644 index 000000000000..b83dc10dbe2f --- /dev/null +++ b/pkg/ui/src/views/shared/components/alertMessage/alertMessage.styl @@ -0,0 +1,46 @@ +// Copyright 2020 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +@require '~src/components/core/index.styl' + +.alert-massage + max-width 470px + font-size 12px + // it is !important because Ant sets margin to 0 + // when closing animation happens and Alert panel + // jumps to the left side. + margin 0 0 0 auto!important + padding-left 48px + border-radius 3px + box-shadow 0 0 10px 0 rgba(71, 88, 114, 0.47) + background-color #ffffff + border-color #ffffff + .alert-massage__icon + font-size 20px + left 16px + .ant-alert-close-icon + margin unset + right 0 + top 0 + padding-right 16px + .ant-alert-message + font-size 14px + color #242a35 + font-weight 600 + + &__close-text + color $colors--neutral-7 + margin $spacing-smaller 0 + font-size $font-size--large + line-height $spacing-smaller + font-weight $font-weight--extra-bold + +.ant-alert-success .alert-massage__icon + color #37a806 diff --git a/pkg/ui/src/views/shared/components/alertMessage/alertMessage.tsx b/pkg/ui/src/views/shared/components/alertMessage/alertMessage.tsx new file mode 100644 index 000000000000..4dc5ff00b641 --- /dev/null +++ b/pkg/ui/src/views/shared/components/alertMessage/alertMessage.tsx @@ -0,0 +1,89 @@ +// Copyright 2020 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import React from "react"; +import { Alert, Icon } from "antd"; +import { Link } from "react-router-dom"; + +import { AlertInfo, AlertLevel } from "src/redux/alerts"; +import "./alertMessage.styl"; + +interface AlertMessageProps extends AlertInfo { + dismiss(): void; +} + +type AlertType = "success" | "info" | "warning" | "error"; + +const mapAlertLevelToType = (alertLevel: AlertLevel): AlertType => { + switch (alertLevel) { + case AlertLevel.SUCCESS: + return "success"; + case AlertLevel.NOTIFICATION: + return "info"; + case AlertLevel.WARNING: + return "warning"; + case AlertLevel.CRITICAL: + return "error"; + default: + return "info"; + } +}; + +const getIconType = (alertLevel: AlertLevel): string => { + switch (alertLevel) { + case AlertLevel.SUCCESS: + return "check-circle"; + case AlertLevel.NOTIFICATION: + return "info-circle"; + case AlertLevel.WARNING: + return "warning"; + case AlertLevel.CRITICAL: + return "close-circle"; + default: + return "info-circle"; + } +}; + +export class AlertMessage extends React.Component { + render() { + const { + level, + dismiss, + link, + title, + text, + } = this.props; + + let description: React.ReactNode = text; + + if (link) { + description = {text}; + } + + const type = mapAlertLevelToType(level); + const iconType = getIconType(level); + return ( + } + closable + onClose={dismiss} + closeText={ +
+ × +
+ } + type={type} /> + ); + } +} diff --git a/pkg/ui/src/views/shared/components/alertMessage/index.ts b/pkg/ui/src/views/shared/components/alertMessage/index.ts new file mode 100644 index 000000000000..b3d7c3a243ba --- /dev/null +++ b/pkg/ui/src/views/shared/components/alertMessage/index.ts @@ -0,0 +1,11 @@ +// Copyright 2020 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +export * from "./alertMessage";