1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-01-26 18:48:15 +02:00

Cherry-Pick: Adding the "contact admin" link (#3229) (#3263)

* Adding the "contact admin" link (#3229)

* Allowing to contact admin to upgrade from the card limit notification box

* Adding the styles

* Improving the interface

* i18n changes

* Fixing linter errors

* Using the right notificationBox

* Fixing tests

* Fixing tests

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Jesús Espino 2022-07-06 00:24:07 +02:00 committed by GitHub
parent b5d8f2b9e1
commit 3f451d568b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 450 additions and 331 deletions

View File

@ -323,10 +323,11 @@
"login.log-in-title": "Log in",
"login.register-button": "or create an account if you don't have one",
"notification-box-card-limit-reached.close-tooltip": "Snooze for 10 days",
"notification-box-card-limit-reached.link": "upgrade to a paid plan",
"notification-box-card-limit-reached.contact-link": "notify your admin",
"notification-box-card-limit-reached.link": "Upgrade to a paid plan",
"notification-box-card-limit-reached.title": "{cards} cards hidden from board",
"notification-box-cards-hidden.title": "Your action hidden another card",
"notification-box.card-limit-reached.not-admin.text": "To access archived cards, contact your admin to upgrade to a paid plan.",
"notification-box-cards-hidden.title": "This action has hidden another card",
"notification-box.card-limit-reached.not-admin.text": "To access archived cards, you can {contactLink} to upgrade to a paid plan.",
"notification-box.card-limit-reached.text": "Card limit reached, to view older cards, {link}",
"register.login-button": "or log in if you already have an account",
"register.signup-title": "Sign up for your account",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
.NotifyAdminSuccessNotify {
position: fixed;
bottom: 173px;
height: unset;
.NotificationBox__icon {
font-size: 24px;
svg {
stroke: var(--online-indicator);
}
}
}

View File

@ -10,9 +10,11 @@ import {IUser, UserConfigPatch} from '../user'
import {getMe, patchProps, getCardLimitSnoozeUntil, getCardHiddenWarningSnoozeUntil} from '../store/users'
import {getCurrentBoardHiddenCardsCount, getCardHiddenWarning} from '../store/cards'
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../telemetry/telemetryClient'
import CheckIcon from '../widgets/icons/check'
import NotificationBox from '../widgets/notificationBox/notificationBox'
import octoClient from '../octoClient'
import NotificationBox from '../widgets/notification-box'
import './cardLimitNotification.scss'
type Props = {
showHiddenCardNotification: boolean
@ -25,6 +27,7 @@ const checkSnoozeInterval = 1000 * 60 * 5
const CardLimitNotification = (props: Props) => {
const intl = useIntl()
const [time, setTime] = useState(Date.now())
const [showNotifyAdminSuccess, setShowNotifyAdminSuccess] = useState<boolean>(false)
const hiddenCards = useAppSelector<number>(getCurrentBoardHiddenCardsCount)
const cardHiddenWarning = useAppSelector<boolean>(getCardHiddenWarning)
@ -108,6 +111,13 @@ const CardLimitNotification = (props: Props) => {
}
}, [show])
const handleContactAdminClicked = useCallback(async () => {
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.LimitCardCTAPerformed)
await octoClient.notifyAdminUpgrade()
setShowNotifyAdminSuccess(true)
}, [me?.id])
const onClick = useCallback(() => {
(window as any).openPricingModal()()
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.LimitCardLimitLinkOpen, {})
@ -153,7 +163,26 @@ const CardLimitNotification = (props: Props) => {
{!hasPermissionToUpgrade &&
<FormattedMessage
id='notification-box.card-limit-reached.not-admin.text'
defaultMessage='To access archived cards, contact your admin to upgrade to a paid plan.'
defaultMessage='To access archived cards, you can {contactLink} to upgrade to a paid plan.'
values={{
contactLink: (
<a
onClick={handleContactAdminClicked}
>
<FormattedMessage
id='notification-box-card-limit-reached.contact-link'
defaultMessage='notify your admin'
/>
</a>),
}}
/>}
{showNotifyAdminSuccess &&
<NotificationBox
className='NotifyAdminSuccessNotify'
icon={<CheckIcon/>}
title={intl.formatMessage({id: 'ViewLimitDialog.notifyAdmin.Success', defaultMessage: 'Your admin has been notified'})}
onClose={() => setShowNotifyAdminSuccess(false)}
/>}
</NotificationBox>
)

View File

@ -32,6 +32,7 @@ jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
const mockedUtils = mocked(Utils, true)
const mockedMutator = mocked(Mutator, true)
mockedUtils.createGuid.mockReturnValue('test-id')
mockedUtils.generateClassName = jest.requireActual('../utils').Utils.generateClassName
describe('components/centerPanel', () => {
const board = TestBlockFactory.createBoard()
board.id = '1'

View File

@ -46,6 +46,7 @@ export const TelemetryActions = {
CloudMoreInfo: 'cloud_more_info',
ViewLimitReached: 'limit_ViewLimitReached',
ViewLimitCTAPerformed: 'limit_ViewLimitLinkOpen',
LimitCardCTAPerformed: 'limit_CardLimitCTAPerformed',
LimitCardLimitReached: 'limit_cardLimitReached',
LimitCardLimitLinkOpen: 'limit_cardLimitLinkOpen',
}

View File

@ -1,140 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`widgets/NotificationBox should match snapshot with close with tooltip 1`] = `
<div>
<div
class="NotificationBox"
>
<div
class="content"
>
<p
class="title"
>
title
</p>
CONTENT
</div>
<div
class="octo-tooltip tooltip-top"
data-tooltip="tooltip"
>
<button
class="IconButton"
type="button"
>
<i
class="CompassIcon icon-close CloseIcon"
/>
</button>
</div>
</div>
</div>
`;
exports[`widgets/NotificationBox should match snapshot with close without tooltip 1`] = `
<div>
<div
class="NotificationBox"
>
<div
class="content"
>
<p
class="title"
>
title
</p>
CONTENT
</div>
<button
class="IconButton"
type="button"
>
<i
class="CompassIcon icon-close CloseIcon"
/>
</button>
</div>
</div>
`;
exports[`widgets/NotificationBox should match snapshot with icon 1`] = `
<div>
<div
class="NotificationBox"
>
<div
class="NotificationBox__icon"
>
ICON
</div>
<div
class="content"
>
<p
class="title"
>
title
</p>
CONTENT
</div>
</div>
</div>
`;
exports[`widgets/NotificationBox should match snapshot with icon and close with tooltip 1`] = `
<div>
<div
class="NotificationBox"
>
<div
class="NotificationBox__icon"
>
ICON
</div>
<div
class="content"
>
<p
class="title"
>
title
</p>
CONTENT
</div>
<div
class="octo-tooltip tooltip-top"
data-tooltip="tooltip"
>
<button
class="IconButton"
type="button"
>
<i
class="CompassIcon icon-close CloseIcon"
/>
</button>
</div>
</div>
</div>
`;
exports[`widgets/NotificationBox should match snapshot without icon and close 1`] = `
<div>
<div
class="NotificationBox"
>
<div
class="content"
>
<p
class="title"
>
title
</p>
CONTENT
</div>
</div>
</div>
`;

View File

@ -1,44 +0,0 @@
.NotificationBox {
position: fixed;
bottom: 52px;
right: 32px;
border-radius: 4px;
background: rgb(var(--center-channel-bg-rgb));
box-shadow: rgba(var(--center-channel-color-rgb), 0.1) 0 0 0 1px,
rgba(var(--center-channel-color-rgb), 0.1) 0 2px 4px;
display: flex;
padding: 22px;
width: 400px;
height: 116px;
z-index: 1000;
.NotificationBox__icon {
margin-right: 10px;
}
.content {
font-size: 14px;
font-weight: 400;
.title {
font-size: 14px;
font-weight: 600;
margin-bottom: 0;
line-height: 25px;
}
}
.IconButton {
margin-left: auto;
}
.octo-tooltip {
font-size: 12px;
font-weight: 600;
.IconButton {
font-size: 14px;
font-weight: 400;
}
}
}

View File

@ -1,85 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import {render} from '@testing-library/react'
import '@testing-library/jest-dom'
import {wrapIntl} from '../testUtils'
import NotificationBox from './notification-box'
describe('widgets/NotificationBox', () => {
beforeEach(() => {
// Quick fix to disregard console error when unmounting a component
console.error = jest.fn()
document.execCommand = jest.fn()
})
test('should match snapshot without icon and close', () => {
const component = wrapIntl(
<NotificationBox
title='title'
>
{'CONTENT'}
</NotificationBox>,
)
const {container} = render(component)
expect(container).toMatchSnapshot()
})
test('should match snapshot with icon', () => {
const component = wrapIntl(
<NotificationBox
title='title'
icon='ICON'
>
{'CONTENT'}
</NotificationBox>,
)
const {container} = render(component)
expect(container).toMatchSnapshot()
})
test('should match snapshot with close without tooltip', () => {
const component = wrapIntl(
<NotificationBox
title='title'
onClose={() => null}
>
{'CONTENT'}
</NotificationBox>,
)
const {container} = render(component)
expect(container).toMatchSnapshot()
})
test('should match snapshot with close with tooltip', () => {
const component = wrapIntl(
<NotificationBox
title='title'
onClose={() => null}
closeTooltip='tooltip'
>
{'CONTENT'}
</NotificationBox>,
)
const {container} = render(component)
expect(container).toMatchSnapshot()
})
test('should match snapshot with icon and close with tooltip', () => {
const component = wrapIntl(
<NotificationBox
title='title'
icon='ICON'
onClose={() => null}
closeTooltip='tooltip'
>
{'CONTENT'}
</NotificationBox>,
)
const {container} = render(component)
expect(container).toMatchSnapshot()
})
})

View File

@ -1,57 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import IconButton from './buttons/iconButton'
import CloseIcon from './icons/close'
import Tooltip from './tooltip'
import './notification-box.scss'
type Props = {
title: string
icon?: React.ReactNode
children: React.ReactNode
onClose?: () => void
closeTooltip?: string
}
function renderClose(onClose?: () => void, closeTooltip?: string) {
if (!onClose) {
return null
}
if (closeTooltip) {
return (
<Tooltip title={closeTooltip}>
<IconButton
icon={<CloseIcon/>}
onClick={onClose}
/>
</Tooltip>)
}
return (
<IconButton
icon={<CloseIcon/>}
onClick={onClose}
/>)
}
function NotificationBox(props: Props): JSX.Element {
return (
<div className='NotificationBox'>
{props.icon &&
<div className='NotificationBox__icon'>
{props.icon}
</div>}
<div className='content'>
<p className='title'>{props.title}</p>
{props.children}
</div>
{renderClose(props.onClose, props.closeTooltip)}
</div>
)
}
export default React.memo(NotificationBox)