diff --git a/packages/web/src/components/AppBar/index.tsx b/packages/web/src/components/AppBar/index.tsx index 56d52bd3..dfb4f4d1 100644 --- a/packages/web/src/components/AppBar/index.tsx +++ b/packages/web/src/components/AppBar/index.tsx @@ -12,6 +12,7 @@ import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import * as URLS from 'config/urls'; import AccountDropdownMenu from 'components/AccountDropdownMenu'; +import UsageAlert from 'components/UsageAlert/index.ee'; import Container from 'components/Container'; import { FormattedMessage } from 'react-intl'; import { Link } from './style'; @@ -29,9 +30,7 @@ export default function AppBar(props: AppBarProps): React.ReactElement { const { drawerOpen, onDrawerOpen, onDrawerClose, maxWidth = false } = props; const theme = useTheme(); - const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { - noSsr: true, - }); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); const [accountMenuAnchorElement, setAccountMenuAnchorElement] = React.useState(null); @@ -83,6 +82,8 @@ export default function AppBar(props: AppBarProps): React.ReactElement { + + { diff --git a/packages/web/src/components/Layout/index.tsx b/packages/web/src/components/Layout/index.tsx index 05e3838c..09c738de 100644 --- a/packages/web/src/components/Layout/index.tsx +++ b/packages/web/src/components/Layout/index.tsx @@ -52,9 +52,7 @@ export default function PublicLayout({ }: PublicLayoutProps): React.ReactElement { const version = useVersion(); const theme = useTheme(); - const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), { - noSsr: true, - }); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg')); const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens); const openDrawer = () => setDrawerOpen(true); diff --git a/packages/web/src/components/LiveChat/Chatwoot.ee.tsx b/packages/web/src/components/LiveChat/Chatwoot.ee.tsx index a45f7f3e..67182189 100644 --- a/packages/web/src/components/LiveChat/Chatwoot.ee.tsx +++ b/packages/web/src/components/LiveChat/Chatwoot.ee.tsx @@ -1,4 +1,6 @@ import * as React from 'react'; +import { useTheme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; import appConfig from 'config/app'; import useCurrentUser from 'hooks/useCurrentUser'; @@ -8,7 +10,9 @@ type ChatwootProps = { } const Chatwoot = ({ ready }: ChatwootProps) => { + const theme = useTheme(); const currentUser = useCurrentUser(); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); React.useEffect(function initiateChatwoot() { window.chatwootSDK.run({ @@ -30,8 +34,18 @@ const Chatwoot = ({ ready }: ChatwootProps) => { name: currentUser.fullName, }); - window.$chatwoot.toggleBubbleVisibility("show"); - }, [currentUser, ready]); + if (!matchSmallScreens) { + window.$chatwoot.toggleBubbleVisibility("show"); + } + }, [currentUser, ready, matchSmallScreens]); + + React.useLayoutEffect(function hideChatwoot() { + if (matchSmallScreens) { + window.$chatwoot?.toggleBubbleVisibility('hide'); + } else { + window.$chatwoot?.toggleBubbleVisibility('show'); + } + }, [matchSmallScreens]) return ( diff --git a/packages/web/src/components/SettingsLayout/index.tsx b/packages/web/src/components/SettingsLayout/index.tsx index 488349ea..ddeb90da 100644 --- a/packages/web/src/components/SettingsLayout/index.tsx +++ b/packages/web/src/components/SettingsLayout/index.tsx @@ -49,9 +49,7 @@ export default function SettingsLayout({ }: SettingsLayoutProps): React.ReactElement { const { isCloud } = useAutomatischInfo(); const theme = useTheme(); - const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), { - noSsr: true, - }); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg')); const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens); const openDrawer = () => setDrawerOpen(true); diff --git a/packages/web/src/components/UsageAlert/index.ee.tsx b/packages/web/src/components/UsageAlert/index.ee.tsx new file mode 100644 index 00000000..fe9a4653 --- /dev/null +++ b/packages/web/src/components/UsageAlert/index.ee.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import Alert from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; +import Typography from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; +import Button from '@mui/material/Button'; +import LinearProgress from '@mui/material/LinearProgress'; + +import useFormatMessage from 'hooks/useFormatMessage'; +import useUsageAlert from 'hooks/useUsageAlert.ee'; + +const LinkBehavior = React.forwardRef( + (props, ref) => , +); + +export default function UsageAlert() { + const formatMessage = useFormatMessage(); + const usageAlert = useUsageAlert(); + + if (!usageAlert.showAlert) return (); + + return ( + + + + + {usageAlert.alertMessage} + + + + + + + + + ); +} diff --git a/packages/web/src/config/app.ts b/packages/web/src/config/app.ts index 184dbe2b..ea01596c 100644 --- a/packages/web/src/config/app.ts +++ b/packages/web/src/config/app.ts @@ -7,6 +7,7 @@ const config: Config = { graphqlUrl: process.env.REACT_APP_GRAPHQL_URL as string, notificationsUrl: process.env.REACT_APP_NOTIFICATIONS_URL as string, chatwootBaseUrl: 'https://app.chatwoot.com', + supportEmailAddress: 'support@automatisch.io' }; export default config; diff --git a/packages/web/src/hooks/useUsageAlert.ee.ts b/packages/web/src/hooks/useUsageAlert.ee.ts new file mode 100644 index 00000000..2bfd802a --- /dev/null +++ b/packages/web/src/hooks/useUsageAlert.ee.ts @@ -0,0 +1,46 @@ +import useFormatMessage from './useFormatMessage'; +import useUsageData from './useUsageData.ee'; +import usePaymentPortalUrl from './usePaymentPortalUrl.ee'; + +type UseUsageAlertReturn = { + showAlert: boolean; + hasExceededLimit?: boolean; + alertMessage?: string; + url?: string; + consumptionPercentage?: number; +}; + +export default function useUsageAlert(): UseUsageAlertReturn { + const { url, loading: paymentPortalUrlLoading } = usePaymentPortalUrl(); + const { + allowedTaskCount, + consumedTaskCount, + nextResetAt, + loading: usageDataLoading + } = useUsageData(); + const formatMessage = useFormatMessage(); + + if (paymentPortalUrlLoading || usageDataLoading) { + return { showAlert: false }; + } + + const hasLoaded = !paymentPortalUrlLoading || usageDataLoading; + const withinUsageThreshold = consumedTaskCount > allowedTaskCount * 0.7; + const consumptionPercentage = consumedTaskCount / allowedTaskCount * 100; + const showAlert = hasLoaded && withinUsageThreshold; + const hasExceededLimit = consumedTaskCount >= allowedTaskCount; + + const alertMessage = formatMessage('usageAlert.informationText', { + allowedTaskCount, + consumedTaskCount, + relativeResetDate: nextResetAt?.toRelative(), + }); + + return { + showAlert, + hasExceededLimit, + alertMessage, + consumptionPercentage, + url, + }; +} diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index ddfda549..dee7e9cd 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -136,5 +136,7 @@ "resetPasswordForm.submit": "Reset password", "resetPasswordForm.passwordFieldLabel": "Password", "resetPasswordForm.confirmPasswordFieldLabel": "Confirm password", - "resetPasswordForm.passwordUpdated": "The password has been updated. Now, you can login." -} + "resetPasswordForm.passwordUpdated": "The password has been updated. Now, you can login.", + "usageAlert.informationText": "Tasks: {consumedTaskCount}/{allowedTaskCount} (Resets {relativeResetDate})", + "usageAlert.viewPlans": "View plans" +} \ No newline at end of file diff --git a/packages/web/src/pages/Application/index.tsx b/packages/web/src/pages/Application/index.tsx index f3db7ad9..2bee8da6 100644 --- a/packages/web/src/pages/Application/index.tsx +++ b/packages/web/src/pages/Application/index.tsx @@ -51,9 +51,7 @@ const ReconnectConnection = (props: any): React.ReactElement => { export default function Application(): React.ReactElement | null { const theme = useTheme(); - const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { - noSsr: true, - }); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); const formatMessage = useFormatMessage(); const connectionsPathMatch = useMatch({ path: URLS.APP_CONNECTIONS_PATTERN, diff --git a/packages/web/src/styles/theme.ts b/packages/web/src/styles/theme.ts index 4624bb5b..11a8fe7c 100644 --- a/packages/web/src/styles/theme.ts +++ b/packages/web/src/styles/theme.ts @@ -251,6 +251,11 @@ const extendedTheme = createTheme({ }), }, }, + MuiUseMediaQuery: { + defaultProps: { + noSsr: true, + }, + }, MuiTab: { styleOverrides: { root: ({ theme }) => ({