@devtron-labs/devtron-fe-common-lib 0.0.6-beta-8 → 0.0.6-beta-9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/Common/Drawer/Drawer.d.ts +16 -0
  2. package/dist/Common/EmptyState/EmptyState.d.ts +24 -0
  3. package/dist/Common/Error.d.ts +15 -0
  4. package/dist/Common/Helper.d.ts +1 -0
  5. package/dist/Common/Modals/Modal.d.ts +9 -0
  6. package/dist/Common/Modals/OpaqueModal.d.ts +12 -0
  7. package/dist/Common/Modals/VisibleModal.d.ts +15 -0
  8. package/dist/Common/Modals/VisibleModal2.d.ts +12 -0
  9. package/dist/Common/Progressing.d.ts +13 -0
  10. package/dist/Common/Reload.d.ts +5 -0
  11. package/dist/Common/ToastBody.d.ts +23 -0
  12. package/dist/Common/index.d.ts +11 -0
  13. package/dist/b5d52f036bdcd3ed.png +0 -0
  14. package/dist/index.js +1 -1
  15. package/package.json +44 -39
  16. package/rollup.config.js +16 -14
  17. package/src/Assets/Icon/ic-close.svg +9 -0
  18. package/src/Assets/Icon/ic-progressing.svg +6 -0
  19. package/src/Assets/Img/ic-loading-failure.png +0 -0
  20. package/src/Assets/Img/ic-not-authorized.svg +62 -0
  21. package/src/Common/Constants.ts +1 -1
  22. package/src/Common/Drawer/Drawer.scss +39 -0
  23. package/src/Common/Drawer/Drawer.tsx +51 -0
  24. package/src/Common/EmptyState/EmptyState.tsx +42 -0
  25. package/src/Common/EmptyState/emptyState.scss +83 -0
  26. package/src/Common/Error.tsx +55 -0
  27. package/src/Common/Helper.tsx +31 -0
  28. package/src/Common/Modals/Modal.tsx +53 -0
  29. package/src/Common/Modals/OpaqueModal.tsx +36 -0
  30. package/src/Common/Modals/VisibleModal.tsx +55 -0
  31. package/src/Common/Modals/VisibleModal2.tsx +35 -0
  32. package/src/Common/Progressing.tsx +60 -0
  33. package/src/Common/Reload.tsx +18 -0
  34. package/src/Common/ServerError.ts +19 -18
  35. package/src/Common/ToastBody.tsx +64 -0
  36. package/src/Common/Types.ts +8 -9
  37. package/src/Common/index.ts +12 -1
  38. package/src/custom.d.ts +13 -0
  39. package/tsconfig.json +2 -1
@@ -0,0 +1,39 @@
1
+ .drawer{
2
+ position: absolute;
3
+ overflow: hidden;
4
+ &.left, &.right{
5
+ top: 0;
6
+ bottom: 0;
7
+ width: 0;
8
+ transition: width 200ms;
9
+ &.show{
10
+ min-width: var(--minWidth);
11
+ width: var(--width);
12
+ max-width: var(--maxWidth);
13
+ }
14
+ }
15
+
16
+ &.top, &.bottom{
17
+ left: 0;
18
+ right: 0;
19
+ height: 0;
20
+ transition: height 200ms;
21
+ &.show{
22
+ height: var(--height);
23
+ }
24
+ }
25
+ &.right{
26
+ right: 0;
27
+ }
28
+ &.left{
29
+ left: 0;
30
+
31
+ }
32
+ &.top{
33
+ top: 0;
34
+ }
35
+
36
+ &.bottom{
37
+ bottom: 0;
38
+ }
39
+ }
@@ -0,0 +1,51 @@
1
+ import React, { useRef, useEffect } from 'react'
2
+ import { VisibleModal } from '../Modals/VisibleModal'
3
+ import './Drawer.scss'
4
+
5
+ interface drawerInterface {
6
+ position: 'left' | 'right' | 'bottom' | 'top'
7
+ children?: any
8
+ backdrop?: boolean
9
+ onClose?: (e: any) => void
10
+ width?: string
11
+ minWidth?: string
12
+ maxWidth?: string
13
+ height?: string
14
+ parentClassName?: string
15
+ onEscape?: (e?: any) => void
16
+ }
17
+
18
+ const Drawer: React.FC<drawerInterface> = ({
19
+ children,
20
+ position,
21
+ height,
22
+ width,
23
+ minWidth,
24
+ maxWidth,
25
+ parentClassName,
26
+ onEscape,
27
+ }) => {
28
+ const drawerRef = useRef(null)
29
+ useEffect(() => {
30
+ setTimeout(() => drawerRef.current?.classList?.add('show'), 1)
31
+ return () => drawerRef.current?.classList?.remove('show')
32
+ }, [])
33
+ const style = {}
34
+ if (position === 'left' || position === 'right') {
35
+ style['--width'] = width
36
+ style['--minWidth'] = minWidth
37
+ style['--maxWidth'] = maxWidth
38
+ }
39
+ if (position === 'top' || position === 'bottom') {
40
+ style['--height'] = height
41
+ }
42
+ return (
43
+ <VisibleModal className="drawer--container" parentClassName={parentClassName || ''} onEscape={onEscape}>
44
+ <aside style={style} ref={drawerRef} className={`drawer ${position}`}>
45
+ {children}
46
+ </aside>
47
+ </VisibleModal>
48
+ )
49
+ }
50
+
51
+ export default Drawer
@@ -0,0 +1,42 @@
1
+ import React from 'react'
2
+ import './emptyState.scss';
3
+ import { ReactComponent as Progressing } from '../../Assets/Icon/ic-progressing.svg';
4
+
5
+ function EmptyState({ children }) {
6
+ return (
7
+ <div className="flex column empty-state" style={{ width: '100%', height: '100%' }}>
8
+ {children}
9
+ </div>
10
+ )
11
+ }
12
+
13
+ function Image({ children }) {
14
+ return children
15
+ }
16
+
17
+ function Title({ children }) {
18
+ return children
19
+ }
20
+
21
+ function Subtitle({ children, className }:{children: any, className?: string}) {
22
+ return <p className={`subtitle ${className}`}>{children}</p>
23
+ }
24
+
25
+ function Button({ children }) {
26
+ return children
27
+ }
28
+
29
+ const Loading: React.FC<{ text: string }> = function (props) {
30
+ return <>
31
+ <Progressing className="dc__block empty-state__loader" />
32
+ <p className="empty-state__loading-text">{props.text}</p>
33
+ </>
34
+ }
35
+
36
+ EmptyState.Image = Image
37
+ EmptyState.Title = Title
38
+ EmptyState.Subtitle = Subtitle
39
+ EmptyState.Button = Button
40
+ EmptyState.Loading = Loading
41
+
42
+ export default EmptyState
@@ -0,0 +1,83 @@
1
+ .empty-state {
2
+ height: 100%;
3
+ img {
4
+ height: 40%;
5
+ max-height: 200px;
6
+ width: auto;
7
+ max-width: 250px;
8
+ }
9
+ svg {
10
+ height: 40%;
11
+ max-height: 200px;
12
+ width: auto;
13
+ }
14
+ h1,
15
+ h2,
16
+ h3,
17
+ h4,
18
+ h5,
19
+ h6 {
20
+ color: var(--N900);
21
+ }
22
+ .title {
23
+ font-weight: 600;
24
+ }
25
+ .subtitle {
26
+ font-size: 13px;
27
+ width: 250px;
28
+ font-weight: 400;
29
+ text-align: center;
30
+ line-height: 1.5;
31
+ margin-bottom: 20px;
32
+ }
33
+ .cta {
34
+ height: 36px;
35
+ }
36
+ p {
37
+ width: 250px;
38
+ text-align: center;
39
+ font-size: 13px;
40
+ color: var(--N700);
41
+ }
42
+ strong {
43
+ font-size: 16px;
44
+ margin-bottom: 4px;
45
+ color: var(--N900);
46
+ font-weight: normal;
47
+ }
48
+ .select-popup {
49
+ height: min-content;
50
+ max-height: 250px;
51
+ overflow: auto;
52
+ padding: 8px 0;
53
+ }
54
+ .empty-state__loader {
55
+ width: 34px;
56
+ height: 34px;
57
+ margin-bottom: 16px;
58
+ }
59
+ .empty-state__loading-text {
60
+ font-size: 12px;
61
+ width: 200px;
62
+ font-weight: normal;
63
+ font-stretch: normal;
64
+ font-style: normal;
65
+ line-height: 1.5;
66
+ letter-spacing: normal;
67
+ text-align: center;
68
+ color: var(--N900);
69
+ }
70
+ .button__icon {
71
+ width:16px;
72
+ height:16px;
73
+ margin-left: 8px;
74
+ }
75
+ //TODO: use name
76
+ // .subtitle a {
77
+ // color: inherit;
78
+ // }
79
+ // .subtitle a:focus,
80
+ // .subtitle a:hover {
81
+ // color: inherit;
82
+ // }
83
+ }
@@ -0,0 +1,55 @@
1
+ import React, { Component } from 'react'
2
+ import EmptyState from './EmptyState/EmptyState'
3
+ import notAuthorized from '../Assets/Img/ic-not-authorized.svg'
4
+ import Reload from './Reload'
5
+
6
+ export class ErrorScreenManager extends Component<{ code?: number; reload?: (...args) => any; subtitle?: string ;reloadClass?: string}> {
7
+ getMessage() {
8
+ switch (this.props.code) {
9
+ case 400:
10
+ return 'Bad Request'
11
+ case 401:
12
+ return 'Unauthorized'
13
+ case 403:
14
+ return <ErrorScreenNotAuthorized subtitle={this.props.subtitle} />
15
+ case 404:
16
+ return 'Not Found'
17
+ case 500:
18
+ return 'Internal Server Error'
19
+ case 502:
20
+ return 'Bad Gateway'
21
+ case 503:
22
+ return 'Service Temporarily Unavailable'
23
+ default:
24
+ return <Reload className={this.props.reloadClass} />
25
+ }
26
+ }
27
+ render() {
28
+ let msg = this.getMessage()
29
+ return (
30
+ <div>
31
+ <h1>{msg}</h1>
32
+ </div>
33
+ )
34
+ }
35
+ }
36
+
37
+ export class ErrorScreenNotAuthorized extends Component<{ subtitle: string }> {
38
+ render() {
39
+ return (
40
+ <EmptyState>
41
+ <EmptyState.Image>
42
+ <img src={notAuthorized} alt="Not Authorized" />
43
+ </EmptyState.Image>
44
+ <EmptyState.Title>
45
+ <h3 className="title">Not authorized</h3>
46
+ </EmptyState.Title>
47
+ <EmptyState.Subtitle>
48
+ {this.props.subtitle
49
+ ? this.props.subtitle
50
+ : "Looks like you don't have access to information on this page. Please contact your manager to request access."}
51
+ </EmptyState.Subtitle>
52
+ </EmptyState>
53
+ )
54
+ }
55
+ }
@@ -0,0 +1,31 @@
1
+ import { ServerErrors } from "./ServerError"
2
+ import { toast } from 'react-toastify'
3
+ import * as Sentry from '@sentry/browser'
4
+ import { toastAccessDenied } from "./ToastBody"
5
+
6
+
7
+ export function showError(serverError, showToastOnUnknownError = true, hideAccessError = false) {
8
+ if (serverError instanceof ServerErrors && Array.isArray(serverError.errors)) {
9
+ serverError.errors.map(({ userMessage, internalMessage }) => {
10
+ if (serverError.code === 403 && userMessage === 'unauthorized') {
11
+ if (!hideAccessError) {
12
+ toastAccessDenied()
13
+ }
14
+ } else {
15
+ toast.error(userMessage || internalMessage)
16
+ }
17
+ })
18
+ } else {
19
+ if (serverError.code !== 403 && serverError.code !== 408) {
20
+ Sentry.captureException(serverError)
21
+ }
22
+
23
+ if (showToastOnUnknownError) {
24
+ if (serverError.message) {
25
+ toast.error(serverError.message)
26
+ } else {
27
+ toast.error('Some Error Occurred')
28
+ }
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,53 @@
1
+ import React, { useEffect } from 'react';
2
+ import ReactDOM from 'react-dom'
3
+ export const Modal = ({ style = {}, children, modal = false, rootClassName = "", onClick = null, callbackRef = null }) => {
4
+ const innerRef = React.useRef(null)
5
+ function handleClick(e) {
6
+ if (typeof onClick !== 'function') return
7
+ if (innerRef && innerRef.current.contains(e.target)) {
8
+ onClick(e, 'in')
9
+ }
10
+ else {
11
+ onClick(e, 'out')
12
+ }
13
+ }
14
+
15
+ function disableWheel(e) {
16
+ if (innerRef && innerRef.current.contains(e.target)) {
17
+ if (innerRef.current.clientHeight === innerRef.current.scrollHeight) {
18
+ e.preventDefault();
19
+ }
20
+ }
21
+ else {
22
+ e.preventDefault();
23
+ }
24
+ }
25
+ useEffect(() => {
26
+ document.addEventListener('click', handleClick)
27
+ let modal = document.getElementById("visible-modal");
28
+ if (modal) modal.classList.add("show")
29
+ document.body.addEventListener('wheel', disableWheel, { passive: false })
30
+ return () => {
31
+ document.body.removeEventListener('wheel', disableWheel)
32
+ document.removeEventListener('click', handleClick)
33
+ if (modal) modal.classList.remove("show")
34
+
35
+ }
36
+ }, []
37
+ )
38
+ return ReactDOM.createPortal(
39
+ <div tabIndex={0} onClick={onClick}
40
+ ref={
41
+ el => {
42
+ if (typeof callbackRef === 'function') {
43
+ callbackRef(el);
44
+ }
45
+ innerRef.current = el;
46
+ }
47
+ }
48
+ id="popup"
49
+ className={`${rootClassName} popup ${modal ? 'modal' : ''}`}
50
+ style={{ ...style }}>
51
+ {children}
52
+ </div>, document.getElementById('visible-modal'));
53
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom';
3
+ import closeIcon from '../../Assets/Icon/ic-close.svg'
4
+
5
+ export interface OpaqueModalProps {
6
+ className?: string;
7
+ onHide?: any;
8
+ noBackground?: boolean;
9
+ }
10
+
11
+ export class OpaqueModal extends React.Component<OpaqueModalProps> {
12
+ modalRef = document.getElementById('full-screen-modal');
13
+
14
+ componentDidMount() {
15
+ if (this.props.noBackground)
16
+ this.modalRef.classList.add("show");
17
+ else this.modalRef.classList.add("show-with-bg");
18
+ }
19
+
20
+ componentWillUnmount() {
21
+ this.modalRef.classList.remove("show");
22
+ this.modalRef.classList.remove("show-with-bg");
23
+ }
24
+
25
+ render() {
26
+ const { className = "", onHide = null } = { ...this.props };
27
+
28
+ return ReactDOM.createPortal(
29
+ <div className={`full-screen-modal__body-container ${className}`}>
30
+ {this.props.children}
31
+ {typeof onHide === 'function' && <div className="close-btn icon-dim-24" onClick={e => onHide(false)}>
32
+ <img className="close-img" src={closeIcon} alt="close" />
33
+ </div>}
34
+ </div>, document.getElementById('full-screen-modal'))
35
+ }
36
+ }
@@ -0,0 +1,55 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom'
3
+
4
+ export class VisibleModal extends React.Component<{
5
+ className: string
6
+ parentClassName?: string
7
+ noBackground?: boolean
8
+ close?: (e) => void
9
+ onEscape?: (e) => void
10
+ }> {
11
+ modalRef = document.getElementById('visible-modal')
12
+
13
+ constructor(props) {
14
+ super(props)
15
+ this.escFunction = this.escFunction.bind(this)
16
+ }
17
+
18
+ escFunction(event) {
19
+ if (event.keyCode === 27 || event.key === 'Escape') {
20
+ if (this.props.onEscape) {
21
+ this.props.onEscape(event)
22
+ } else if (this.props.close) {
23
+ this.props.close(event)
24
+ }
25
+ }
26
+ }
27
+
28
+ componentDidMount() {
29
+ document.addEventListener('keydown', this.escFunction)
30
+ this.modalRef.classList.add(this.props.noBackground ? 'show' : 'show-with-bg')
31
+
32
+ if (this.props.parentClassName) {
33
+ this.modalRef.classList.add(this.props.parentClassName)
34
+ }
35
+ }
36
+
37
+ componentWillUnmount() {
38
+ document.removeEventListener('keydown', this.escFunction)
39
+ this.modalRef.classList.remove('show')
40
+ this.modalRef.classList.remove('show-with-bg')
41
+
42
+ if (this.props.parentClassName) {
43
+ this.modalRef.classList.remove(this.props.parentClassName)
44
+ }
45
+ }
46
+
47
+ render() {
48
+ return ReactDOM.createPortal(
49
+ <div className={`visible-modal__body ${this.props.className}`} onClick={this.props?.close}>
50
+ {this.props.children}
51
+ </div>,
52
+ document.getElementById('visible-modal'),
53
+ )
54
+ }
55
+ }
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+
4
+ export class VisibleModal2 extends React.Component<{ className: string; close?: (e) => void; }> {
5
+
6
+ modalRef = document.getElementById('visible-modal-2');
7
+
8
+ constructor(props) {
9
+ super(props);
10
+ this.escFunction = this.escFunction.bind(this);
11
+ }
12
+
13
+ escFunction(event) {
14
+ if (event.keyCode === 27 && this.props.close) {
15
+ this.props.close(event);
16
+ }
17
+ }
18
+
19
+ componentDidMount() {
20
+ document.addEventListener("keydown", this.escFunction);
21
+ this.modalRef.classList.add("show-with-bg");
22
+ }
23
+
24
+ componentWillUnmount() {
25
+ document.removeEventListener("keydown", this.escFunction);
26
+ this.modalRef.classList.remove("show-with-bg");
27
+ }
28
+
29
+ render() {
30
+ return ReactDOM.createPortal(
31
+ <div className={`visible-modal__body ${this.props.className}`} onClick={this.props?.close}>
32
+ {this.props.children}
33
+ </div>, document.getElementById('visible-modal-2'))
34
+ }
35
+ }
@@ -0,0 +1,60 @@
1
+ import React from 'react'
2
+
3
+ interface ProgressingProps {
4
+ pageLoader?: boolean
5
+ loadingText?: string
6
+ size?: number
7
+ fullHeight?: boolean
8
+ theme?: 'white' | 'default'
9
+ styles?: React.CSSProperties
10
+ children?: React.ReactNode
11
+ }
12
+
13
+ export function Progressing({ pageLoader, size, theme, styles }: ProgressingProps): JSX.Element {
14
+ const loaderSize = size ? `${size}px` : pageLoader ? '48px' : '20px'
15
+ return (
16
+ <div className={`loader ${theme || 'default'}-background`} style={styles}>
17
+ <div style={{ width: loaderSize, height: loaderSize }}>
18
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" className="loader__svg">
19
+ <g fill="none" fillRule="evenodd" strokeLinecap="round">
20
+ <animateTransform
21
+ attributeName="transform"
22
+ attributeType="XML"
23
+ dur="0.5s"
24
+ from="0 12 12"
25
+ repeatCount="indefinite"
26
+ to="360 12 12"
27
+ type="rotate"
28
+ />
29
+ <path
30
+ fill="#06C"
31
+ fillRule="nonzero"
32
+ d="M12 2.5A9.5 9.5 0 1 1 2.5 12a1.5 1.5 0 0 1 3 0A6.5 6.5 0 1 0 12 5.5a1.5 1.5 0 0 1 0-3z"
33
+ />
34
+ </g>
35
+ </svg>
36
+ </div>
37
+ </div>
38
+ )
39
+ }
40
+
41
+ export function DetailsProgressing({
42
+ loadingText,
43
+ size = 24,
44
+ fullHeight = false,
45
+ children,
46
+ styles,
47
+ }: ProgressingProps): JSX.Element {
48
+ return (
49
+ <div
50
+ className={`details-loader bcn-0 flex column fs-14 fw-6 ${fullHeight ? 'h-100' : 'details-loader-height'}`}
51
+ style={styles}
52
+ >
53
+ <span style={{ width: `${size}px`, height: `${size}px` }}>
54
+ <Progressing size={size} />
55
+ </span>
56
+ {loadingText && <span className="mt-10">{loadingText}</span>}
57
+ {children}
58
+ </div>
59
+ )
60
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react'
2
+ import loadingFailure from '../Assets/Img/ic-loading-failure.png';
3
+
4
+ export default function Reload({ reload = null, className = '' }) {
5
+ function refresh(e){
6
+ window.location.reload();
7
+ }
8
+ return (
9
+ <article className={`flex ${className}`} style={{ width: '100%', height: '100%' }}>
10
+ <div className="flex column" style={{ width: '250px', textAlign: 'center' }}>
11
+ <img src={loadingFailure} style={{ width: '100%', height: 'auto', marginBottom: '12px' }} alt="load-error" />
12
+ <h3 className="title dc__bold">Failed to load</h3>
13
+ <div className="dc__empty__subtitle" style={{ marginBottom: '20px' }}>We could not load this page. Please give us another try.</div>
14
+ <button type="button" className="cta ghosted" onClick={typeof reload === 'function' ? reload : refresh}>Retry</button>
15
+ </div>
16
+ </article>
17
+ )
18
+ }
@@ -1,30 +1,31 @@
1
1
  export class ServerError {
2
- code: number;
3
- internalMessage: string | null;
4
- userMessage: string | null;
5
- moreInfo?: string | null;
2
+ code: number
3
+ internalMessage: string | null
4
+ userMessage: string | null
5
+ moreInfo?: string | null
6
6
 
7
7
  constructor(error) {
8
- this.code = error.code || 0;
9
- this.userMessage = error.userMessage || '';
10
- this.internalMessage = error.internalMessage || '';
11
- this.moreInfo = error.moreInfo || '';
8
+ this.code = error.code || 0
9
+ this.userMessage = error.userMessage || ''
10
+ this.internalMessage = error.internalMessage || ''
11
+ this.moreInfo = error.moreInfo || ''
12
12
  }
13
13
  }
14
14
 
15
15
  export class ServerErrors extends Error {
16
- code: number;
17
- errors: ServerError[];
16
+ code: number
17
+ errors: ServerError[]
18
18
 
19
19
  constructor(obj: { code: number; errors: ServerError[] }) {
20
- super();
21
- this.code = obj.code;
20
+ super()
21
+ this.code = obj.code
22
22
  let message = obj.errors.reduce((str: string, err: ServerError) => {
23
- str += `${err.internalMessage || err.userMessage}`;
24
- return str;
25
- }, '');
26
- this.name = `[${obj.code.toString()}]`;
27
- this.message = message;
28
- this.errors = obj.errors.map((err) => new ServerError(err));
23
+ str += `${err.internalMessage || err.userMessage}`
24
+ return str
25
+ }, '')
26
+ this.name = `[${obj.code.toString()}]`
27
+ this.message = message
28
+ this.errors = obj.errors.map((err) => new ServerError(err))
29
+ Object.setPrototypeOf(this, ServerErrors.prototype)
29
30
  }
30
31
  }
@@ -0,0 +1,64 @@
1
+ import React from 'react'
2
+ import { toast } from 'react-toastify'
3
+
4
+ export class ToastBody extends React.Component<{
5
+ title: string
6
+ subtitle?: string
7
+ }> {
8
+ render() {
9
+ return (
10
+ <div className="toast">
11
+ <div className="toast__title">{this.props.title}</div>
12
+ {this.props.subtitle && <div className="toast__subtitle">{this.props.subtitle}</div>}
13
+ </div>
14
+ )
15
+ }
16
+ }
17
+
18
+ export class ToastBody3 extends React.Component<{
19
+ text: string
20
+ onClick: (...args) => void
21
+ buttonText: string
22
+ }> {
23
+ render() {
24
+ return (
25
+ <div className="flex left column dc__app-update-toast">
26
+ <span className="info">{this.props.text}</span>
27
+ <button type="button" onClick={this.props.onClick}>
28
+ {this.props.buttonText}
29
+ </button>
30
+ </div>
31
+ )
32
+ }
33
+ }
34
+
35
+ export class ToastBodyWithButton extends React.Component<{
36
+ title: string
37
+ subtitle?: string
38
+ onClick: (...args) => void
39
+ buttonText: string
40
+ }> {
41
+ render() {
42
+ return (
43
+ <div className="toast dc__app-update-toast">
44
+ <div className="toast__title">{this.props.title}</div>
45
+ {this.props.subtitle && <div className="toast__subtitle">{this.props.subtitle}</div>}
46
+ <button type="button" onClick={this.props.onClick} style={{ float: 'right' }}>
47
+ {this.props.buttonText}
48
+ </button>
49
+ </div>
50
+ )
51
+ }
52
+ }
53
+
54
+ export const toastAccessDenied = (subtitle?: string) => {
55
+ return toast.info(
56
+ <ToastBody
57
+ title="Access denied"
58
+ subtitle={subtitle || "You do not have required access to perform this action"}
59
+ />,
60
+ {
61
+ className: 'devtron-toast unauthorized',
62
+ },
63
+ )
64
+ }