//education principles
educationPrincipleBackground: COLORS.indigo[25],
educationPrincipleBorder: COLORS.indigo[300],
+
+ // SonarLint PromotionNotification
+ promotionNotification: COLORS.blueGrey[35],
+ promotionNotificationBackground: COLORS.blueGrey[700],
+ promotionNotificationSeparator: COLORS.blueGrey[500],
},
// contrast colors to be used for text when using a color background with the same name
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-.toaster {
- position: fixed;
- right: 10px;
- bottom: 10px;
- max-width: 550px;
- background-color: var(--darkBackground);
- z-index: var(--popupZIndex);
- box-shadow: 1px 1px 5px 0px black;
- color: var(--darkBackgroundFontColor);
- border-radius: 4px;
-}
-
-.toaster-content {
- border-right: 1px solid var(--darkBackgroundSeparator);
-}
-
-.toaster-link {
- color: var(--darkBackgroundFontColor);
-}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import styled from '@emotion/styled';
+import { ButtonPrimary, ButtonSecondary, themeBorder, themeColor } from 'design-system';
import * as React from 'react';
import { dismissNotice } from '../../../api/users';
-import { ButtonLink } from '../../../components/controls/buttons';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
-import { isLoggedIn, NoticeType } from '../../../types/users';
+import { NoticeType, isLoggedIn } from '../../../types/users';
import { CurrentUserContextInterface } from '../current-user/CurrentUserContext';
import withCurrentUserContext from '../current-user/withCurrentUserContext';
-import './PromotionNotification.css';
export function PromotionNotification(props: CurrentUserContextInterface) {
- const { currentUser } = props;
+ const { currentUser, updateDismissedNotices } = props;
- if (!isLoggedIn(currentUser) || currentUser.dismissedNotices[NoticeType.SONARLINT_AD]) {
- return null;
- }
-
- const onClick = () => {
- dismissNotice(NoticeType.SONARLINT_AD)
+ const onClick = React.useCallback(() => {
+ return dismissNotice(NoticeType.SONARLINT_AD)
.then(() => {
- props.updateDismissedNotices(NoticeType.SONARLINT_AD, true);
+ updateDismissedNotices(NoticeType.SONARLINT_AD, true);
})
.catch(() => {
/* noop */
});
- };
+ }, [updateDismissedNotices]);
+
+ if (!isLoggedIn(currentUser) || currentUser.dismissedNotices[NoticeType.SONARLINT_AD]) {
+ return null;
+ }
return (
- <div className="toaster sw-flex sw-items-center sw-px-4">
+ <PromotionNotificationWrapper className="it__promotion_notification sw-z-global-popup sw-rounded-1 sw-flex sw-items-center sw-px-4">
<div className="sw-mr-2">
<img alt="SonarQube + SonarLint" height={80} src={`${getBaseUrl()}/images/sq-sl.svg`} />
</div>
- <div className="toaster-content sw-flex-1 sw-px-2 sw-py-4">
+ <PromotionNotificationContent className="sw-flex-1 sw-px-2 sw-py-4">
<span className="sw-body-sm-highlight">{translate('promotion.sonarlint.title')}</span>
<p className="sw-mt-2">{translate('promotion.sonarlint.content')}</p>
- </div>
- <div className="toaster-actions spacer-left padded-left display-flex-column display-flex-center">
- <a
- className="button button-primary sw-mb-4"
- href="https://www.sonarsource.com/products/sonarlint/?referrer=sonarqube-welcome"
- rel="noreferrer"
+ </PromotionNotificationContent>
+ <div className="sw-ml-2 sw-pl-2 sw-flex sw-flex-col sw-items-stretch">
+ <ButtonPrimary
+ className="sw-mb-4"
+ to="https://www.sonarsource.com/products/sonarlint/?referrer=sonarqube-welcome"
onClick={onClick}
- target="_blank"
>
{translate('learn_more')}
- </a>
- <ButtonLink className="toaster-link" onClick={onClick}>
+ </ButtonPrimary>
+ <ButtonSecondary className="sw-justify-center" onClick={onClick}>
{translate('dismiss')}
- </ButtonLink>
+ </ButtonSecondary>
</div>
- </div>
+ </PromotionNotificationWrapper>
);
}
export default withCurrentUserContext(PromotionNotification);
+
+const PromotionNotificationWrapper = styled.div`
+ position: fixed;
+ right: 10px;
+ bottom: 10px;
+ max-width: 600px;
+ box-shadow: 1px 1px 5px 0px black;
+
+ background: ${themeColor('promotionNotificationBackground')};
+ color: ${themeColor('promotionNotification')};
+`;
+
+const PromotionNotificationContent = styled.div`
+ border-right: ${themeBorder('default', 'promotionNotificationSeparator')};
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import userEvent from '@testing-library/user-event';
+import * as React from 'react';
+import { dismissNotice } from '../../../../api/users';
+import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { byRole, byText } from '../../../../helpers/testSelector';
+import { CurrentUser, NoticeType } from '../../../../types/users';
+import PromotionNotification from '../PromotionNotification';
+
+jest.mock('../../../../api/users', () => ({
+ dismissNotice: jest.fn().mockResolvedValue(undefined),
+}));
+
+it('should not render when anonymous', () => {
+ renderPromotionNotification(mockCurrentUser({ isLoggedIn: false }));
+
+ expect(byText('promotion.sonarlint.title').query()).not.toBeInTheDocument();
+});
+
+it('should not render if previously dismissed', () => {
+ renderPromotionNotification(
+ mockLoggedInUser({ dismissedNotices: { [NoticeType.SONARLINT_AD]: true } }),
+ );
+
+ expect(byText('promotion.sonarlint.title').query()).not.toBeInTheDocument();
+});
+
+it('should be dismissable', async () => {
+ const user = userEvent.setup();
+
+ renderPromotionNotification();
+
+ expect(byText('promotion.sonarlint.title').get()).toBeInTheDocument();
+ const dismissButton = byRole('button', { name: 'dismiss' }).get();
+
+ expect(dismissButton).toBeInTheDocument();
+ await user.click(dismissButton);
+
+ expect(dismissNotice).toHaveBeenCalledWith(NoticeType.SONARLINT_AD);
+});
+
+function renderPromotionNotification(currentUser: CurrentUser = mockLoggedInUser()) {
+ return renderComponent(<PromotionNotification />, '', {
+ currentUser,
+ });
+}