Merge pull request #988 from automatisch/show-alert-for-task-usage

feat: show usage alert as of threshold
This commit is contained in:
Ömer Faruk Aydın
2023-03-09 13:45:55 +01:00
committed by GitHub
12 changed files with 138 additions and 22 deletions

View File

@@ -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 | HTMLElement>(null);
@@ -83,6 +82,8 @@ export default function AppBar(props: AppBarProps): React.ReactElement {
</Toolbar>
</Container>
<UsageAlert />
<AccountDropdownMenu
anchorEl={accountMenuAnchorElement}
id={accountMenuId}

View File

@@ -10,9 +10,7 @@ import { IconButton } from './style';
export default function ConditionalIconButton(props: any): React.ReactElement {
const { icon, ...buttonProps } = props;
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), {
noSsr: true,
});
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
if (matchSmallScreens) {
return (

View File

@@ -31,9 +31,7 @@ type DrawerProps = {
export default function Drawer(props: DrawerProps): React.ReactElement {
const { links = [], bottomLinks = [], ...drawerProps } = props;
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), {
noSsr: true,
});
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
const formatMessage = useFormatMessage();
const closeOnClick = (event: React.SyntheticEvent) => {

View File

@@ -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);

View File

@@ -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,
});
if (!matchSmallScreens) {
window.$chatwoot.toggleBubbleVisibility("show");
}, [currentUser, ready]);
}
}, [currentUser, ready, matchSmallScreens]);
React.useLayoutEffect(function hideChatwoot() {
if (matchSmallScreens) {
window.$chatwoot?.toggleBubbleVisibility('hide');
} else {
window.$chatwoot?.toggleBubbleVisibility('show');
}
}, [matchSmallScreens])
return (
<React.Fragment />

View File

@@ -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);

View File

@@ -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<any>(
(props, ref) => <a ref={ref} target="_blank" {...props} role={undefined} />,
);
export default function UsageAlert() {
const formatMessage = useFormatMessage();
const usageAlert = useUsageAlert();
if (!usageAlert.showAlert) return (<React.Fragment />);
return (
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open
>
<Alert
icon={false}
sx={{ fontWeight: 500, minWidth: 410 }}
severity={usageAlert.hasExceededLimit ? 'error' : 'warning'}
>
<Stack direction="row" gap={4} mb={1}>
<Typography
variant="subtitle2"
sx={{ display: 'flex', alignItems: 'center' }}
>
{usageAlert.alertMessage}
</Typography>
<Button
component={LinkBehavior}
size="small"
href={usageAlert.url}
sx={{ minWidth: 100 }}
>
{formatMessage('usageAlert.viewPlans')}
</Button>
</Stack>
<LinearProgress
variant="determinate"
value={usageAlert.consumptionPercentage}
/>
</Alert>
</Snackbar>
);
}

View File

@@ -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;

View File

@@ -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,
};
}

View File

@@ -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"
}

View File

@@ -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,

View File

@@ -251,6 +251,11 @@ const extendedTheme = createTheme({
}),
},
},
MuiUseMediaQuery: {
defaultProps: {
noSsr: true,
},
},
MuiTab: {
styleOverrides: {
root: ({ theme }) => ({