refactor UI of header alerts

This commit is contained in:
Simon Larsen 2024-10-04 11:01:36 -07:00
parent 7eebd75088
commit 30db4e58ff
No known key found for this signature in database
GPG Key ID: 96C5DCA24769DBCA
4 changed files with 216 additions and 75 deletions

View File

@ -1,6 +1,8 @@
import Icon from "../Icon/Icon";
import Icon, { ThickProp } from "../Icon/Icon";
import IconProp from "Common/Types/Icon/IconProp";
import React, { ReactElement } from "react";
import Tooltip from "../Tooltip/Tooltip";
import { GetReactElementFunction } from "../../Types/FunctionTypes";
export enum HeaderAlertType {
SUCCESS = "Success",
@ -15,45 +17,60 @@ export interface ComponentProps {
title: string;
className?: string | undefined;
alertType: HeaderAlertType;
tooltip?: string | undefined;
}
const HeaderAlert: (props: ComponentProps) => ReactElement = (
props: ComponentProps,
): ReactElement => {
let bgColor: string = "bg-indigo-500"; // default color info.
let textColor: string = "text-indigo-500"; // default color info.
switch (props.alertType) {
case HeaderAlertType.SUCCESS:
bgColor = "bg-green-500 hover:bg-green-600";
textColor = "text-green-500 hover:text-green-600";
break;
case HeaderAlertType.ERROR:
bgColor = "bg-red-500 hover:bg-red-600";
textColor = "text-red-500 hover:text-red-600";
break;
case HeaderAlertType.WARNING:
bgColor = "bg-yellow-500 hover:bg-yellow-600";
textColor = "text-yellow-500 hover:text-yellow-600";
break;
case HeaderAlertType.INFO:
bgColor = "bg-indigo-500 hover:bg-indigo-600";
textColor = "text-indigo-500 hover:text-indigo-600";
break;
}
return (
<div
className={`rounded-md m-3 p-3 ${bgColor} ${props.className} cursor-pointer ml-0 p-3 pr-4`}
onClick={() => {
props.onClick && props.onClick();
}}
>
<div className="flex ">
<div className="flex-shrink-0">
<Icon icon={props.icon} className="h-5 w-5 text-white" />
</div>
<div className="ml-1 flex-1 md:flex md:justify-between">
<p className={`text-sm text-white`}>{props.title}</p>
const getElement: GetReactElementFunction = (): ReactElement => {
return (
<div
className={`cursor-pointer hover:bg-gray-100 p-1 h-7 pl-2 pr-2 -mt-2 -ml-7 mr-1 rounded-full ${props.className}`}
onClick={() => {
props.onClick && props.onClick();
}}
>
<div className="flex ">
<div className={`flex-shrink-0 mt-0.5 ${textColor}`}>
<Icon
icon={props.icon}
thick={ThickProp.Thick}
className={`h-4 w-4 stroke-[3px] font-bold ${textColor}`}
/>
</div>
<div className="ml-1 flex-1 md:flex md:justify-between">
<p className={`text-sm font-semibold ${textColor}`}>
{props.title}
</p>
</div>
</div>
</div>
</div>
);
);
};
if (props.tooltip) {
return <Tooltip text={props.tooltip}>{getElement()}</Tooltip>;
}
return getElement();
};
export default HeaderAlert;

View File

@ -0,0 +1,28 @@
import React, { ReactElement } from "react";
export interface ComponentProps {
children: Array<ReactElement>;
}
const HeaderAlertGroup: (props: ComponentProps) => ReactElement = (
props: ComponentProps,
): ReactElement => {
return (
<div className="rounded-lg m-2 flex border-2 border-gray-200">
{props.children.map((child: ReactElement, index: number) => {
const isLastElement: boolean = index === props.children.length - 1;
return (
<div key={index} className="p-4 flex">
{child}
{!isLastElement && (
<div className="border-r-2 border-gray-200"></div>
)}
</div>
);
})}
</div>
);
};
export default HeaderAlertGroup;

View File

@ -19,6 +19,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
refreshToggle?: string | undefined;
className?: string | undefined;
alertType: HeaderAlertType;
tooltip?: string | undefined;
}
const HeaderModelAlert: <TBaseModel extends BaseModel>(
@ -86,6 +87,7 @@ const HeaderModelAlert: <TBaseModel extends BaseModel>(
onClick={props.onClick}
className={props.className}
alertType={props.alertType}
tooltip={props.tooltip}
/>
);
};

View File

@ -18,6 +18,7 @@ import HeaderAlert, {
HeaderAlertType,
} from "Common/UI/Components/HeaderAlert/HeaderAlert";
import HeaderModelAlert from "Common/UI/Components/HeaderAlert/HeaderModelAlert";
import HeaderAlertGroup from "Common/UI/Components/HeaderAlert/HeaderAlertGroup";
import { SizeProp } from "Common/UI/Components/Icon/Icon";
import { BILLING_ENABLED, getAllEnvVars } from "Common/UI/Config";
import Navigation from "Common/UI/Utils/Navigation";
@ -34,6 +35,7 @@ import React, {
import Realtime from "Common/UI/Utils/Realtime";
import DashboardNavigation from "../../Utils/Navigation";
import ModelEventType from "Common/Types/Realtime/ModelEventType";
import Alert from "Common/Models/DatabaseModels/Alert";
export interface ComponentProps {
projects: Array<Project>;
@ -50,11 +52,18 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
const [activeIncidentToggleRefresh, setActiveIncidentToggleRefresh] =
useState<string>(OneUptimeDate.getCurrentDate().toString());
const [activeAlertToggleRefresh, setActiveAlertToggleRefresh] =
useState<string>(OneUptimeDate.getCurrentDate().toString());
const refreshIncidentCount: VoidFunction = () => {
setActiveIncidentToggleRefresh(OneUptimeDate.getCurrentDate().toString());
};
useEffect(() => {
const refreshAlertCount: VoidFunction = () => {
setActiveAlertToggleRefresh(OneUptimeDate.getCurrentDate().toString());
};
const realtimeIncidentCountRefresh: () => VoidFunction = (): VoidFunction => {
const stopListeningOnCreate: VoidFunction =
Realtime.listenToModelEvent<Incident>(
{
@ -91,12 +100,72 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
},
);
return () => {
const stopListening: VoidFunction = () => {
// on unmount.
stopListeningOnCreate();
stopListeningOnUpdate();
stopListeningOnDelete();
};
return stopListening;
};
const realtimeAlertCountRefresh: () => VoidFunction = (): VoidFunction => {
const stopListeningOnCreate: VoidFunction =
Realtime.listenToModelEvent<Alert>(
{
eventType: ModelEventType.Create,
modelType: Alert,
tenantId: DashboardNavigation.getProjectId()!,
},
() => {
refreshAlertCount();
},
);
const stopListeningOnUpdate: VoidFunction =
Realtime.listenToModelEvent<Alert>(
{
eventType: ModelEventType.Update,
modelType: Alert,
tenantId: DashboardNavigation.getProjectId()!,
},
() => {
refreshAlertCount();
},
);
const stopListeningOnDelete: VoidFunction =
Realtime.listenToModelEvent<Alert>(
{
eventType: ModelEventType.Delete,
modelType: Alert,
tenantId: DashboardNavigation.getProjectId()!,
},
() => {
refreshAlertCount();
},
);
const stopListening: VoidFunction = () => {
// on unmount.
stopListeningOnCreate();
stopListeningOnUpdate();
stopListeningOnDelete();
};
return stopListening;
};
useEffect(() => {
const realtimeIncidentStop: VoidFunction = realtimeIncidentCountRefresh();
const realtimeAlertStop: VoidFunction = realtimeAlertCountRefresh();
return () => {
realtimeIncidentStop();
realtimeAlertStop();
};
}, []);
const showAddCardButton: boolean = Boolean(
@ -143,61 +212,86 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
/>
<div className="flex ml-3">
<HeaderModelAlert<TeamMember>
icon={IconProp.Folder}
modelType={TeamMember}
query={{
userId: User.getUserId(),
hasAcceptedInvitation: false,
}}
alertType={HeaderAlertType.INFO}
singularName="Project Invitation"
pluralName="Project Invitations"
requestOptions={{
isMultiTenantRequest: true,
}}
onClick={() => {
Navigation.navigate(RouteMap[PageMap.PROJECT_INVITATIONS]!);
}}
/>
<HeaderModelAlert<Incident>
icon={IconProp.Alert}
modelType={Incident}
alertType={HeaderAlertType.ERROR}
query={{
currentIncidentState: {
order: 1,
},
}}
refreshToggle={activeIncidentToggleRefresh}
singularName="New Incident"
pluralName="New Incidents"
requestOptions={{
isMultiTenantRequest: true,
}}
onClick={() => {
Navigation.navigate(RouteMap[PageMap.NEW_INCIDENTS]!);
}}
/>
{showTrialButton && (
<HeaderAlert
icon={IconProp.Clock}
<HeaderAlertGroup>
<HeaderModelAlert<TeamMember>
icon={IconProp.Folder}
modelType={TeamMember}
query={{
userId: User.getUserId(),
hasAcceptedInvitation: false,
}}
alertType={HeaderAlertType.INFO}
title={`Trial ends in ${OneUptimeDate.getNumberOfDaysBetweenDatesInclusive(
OneUptimeDate.getCurrentDate(),
props.selectedProject!.trialEndsAt!,
)} ${
OneUptimeDate.getNumberOfDaysBetweenDatesInclusive(
singularName=""
pluralName=""
tooltip="Looks like you have pending project invitations. Please click here to review and accept them."
requestOptions={{
isMultiTenantRequest: true,
}}
onClick={() => {
Navigation.navigate(RouteMap[PageMap.PROJECT_INVITATIONS]!);
}}
/>
<HeaderModelAlert<Incident>
icon={IconProp.Alert}
modelType={Incident}
alertType={HeaderAlertType.ERROR}
query={{
currentIncidentState: {
order: 1,
},
}}
refreshToggle={activeIncidentToggleRefresh}
singularName=""
pluralName=""
tooltip="View all active incidents"
requestOptions={{
isMultiTenantRequest: true,
}}
onClick={() => {
Navigation.navigate(RouteMap[PageMap.NEW_INCIDENTS]!);
}}
/>
<HeaderModelAlert<Alert>
icon={IconProp.ExclaimationCircle}
modelType={Alert}
alertType={HeaderAlertType.ERROR}
query={{
currentAlertState: {
order: 1,
},
}}
refreshToggle={activeAlertToggleRefresh}
singularName=""
pluralName=""
tooltip="View all active alerts"
onClick={() => {
Navigation.navigate(RouteMap[PageMap.ALERTS]!);
}}
/>
{showTrialButton ? (
<HeaderAlert
icon={IconProp.Clock}
tooltip="Your trial ends soon"
alertType={HeaderAlertType.INFO}
title={`${OneUptimeDate.getNumberOfDaysBetweenDatesInclusive(
OneUptimeDate.getCurrentDate(),
props.selectedProject!.trialEndsAt!,
) > 1
? "days"
: "day"
}`}
/>
)}
)} ${
OneUptimeDate.getNumberOfDaysBetweenDatesInclusive(
OneUptimeDate.getCurrentDate(),
props.selectedProject!.trialEndsAt!,
) > 1
? "days"
: "day"
}`}
/>
) : (
<></>
)}
</HeaderAlertGroup>
</div>
</>
}