Merge pull request #322 from bluewave-labs/feat/alert-refactor
Refactored toast alerts
This commit is contained in:
20 files changed
+211
-546
No files matched your search
@@ -1,9 +1,7 @@
|
||||
.alert {
|
||||
margin: 0;
|
||||
width: fit-content;
|
||||
padding: var(--env-var-spacing-1-plus);
|
||||
}
|
||||
.alert, .alert button{
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
}
|
||||
.alert .MuiStack-root {
|
||||
max-width: 85%;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import { Box, IconButton, Stack } from "@mui/material";
|
||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||||
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
|
||||
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import "./index.css";
|
||||
import Button from "../Button";
|
||||
|
||||
/**
|
||||
* Icons mapping for different alert variants.
|
||||
@@ -22,10 +24,13 @@ const icons = {
|
||||
* @param {'info' | 'error' | 'warning'} props.variant - The type of alert.
|
||||
* @param {string} [props.title] - The title of the alert.
|
||||
* @param {string} [props.body] - The body text of the alert.
|
||||
* @param {boolean} [props.isToast] - Indicates if the alert is used as a toast notification.
|
||||
* @param {boolean} [props.hasIcon] - Whether to display an icon in the alert.
|
||||
* @param {function} props.onClick - Toast dismiss function.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
|
||||
const Alert = ({ variant, title, body }) => {
|
||||
const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
|
||||
const theme = useTheme();
|
||||
const { bg, border, color } = theme.alert[variant];
|
||||
const icon = icons[variant];
|
||||
@@ -34,19 +39,67 @@ const Alert = ({ variant, title, body }) => {
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-start"
|
||||
alignItems={hasIcon ? "" : "center"}
|
||||
className="alert row-stack"
|
||||
gap={theme.gap.ml}
|
||||
sx={{
|
||||
padding: hasIcon ? theme.gap.ml : `${theme.gap.small} ${theme.gap.ml}`,
|
||||
backgroundColor: bg,
|
||||
border: `solid 1px ${border}`,
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
}}
|
||||
>
|
||||
{icon && <Box sx={{ color: color }}>{icon}</Box>}
|
||||
{hasIcon && <Box sx={{ color: color }}>{icon}</Box>}
|
||||
<Stack direction="column" gap="2px" sx={{ flex: 1, color: color }}>
|
||||
{title && <Box sx={{ fontWeight: "700" }}>{title}</Box>}
|
||||
{body && <Box sx={{ fontWeight: "400" }}>{body}</Box>}
|
||||
{hasIcon && isToast && (
|
||||
<Button
|
||||
level="tertiary"
|
||||
label="Dismiss"
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
fontWeight: "600",
|
||||
width: "fit-content",
|
||||
mt: theme.gap.small,
|
||||
padding: 0,
|
||||
minWidth: 0,
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
"& .MuiTouchRipple-root": {
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
></Button>
|
||||
)}
|
||||
</Stack>
|
||||
{isToast && (
|
||||
<IconButton
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
alignSelf: "flex-start",
|
||||
ml: "auto",
|
||||
mr: "-5px",
|
||||
mt: hasIcon ? "-5px" : 0,
|
||||
padding: "5px",
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
"& .MuiTouchRipple-root": {
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CloseIcon
|
||||
sx={{
|
||||
fontSize: "20px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -55,6 +108,16 @@ Alert.propTypes = {
|
||||
variant: PropTypes.oneOf(["info", "error", "warning"]).isRequired,
|
||||
title: PropTypes.string,
|
||||
body: PropTypes.string,
|
||||
isToast: PropTypes.bool,
|
||||
hasIcon: PropTypes.bool,
|
||||
onClick: function (props, propName, componentName) {
|
||||
if (props.isToast && !props[propName]) {
|
||||
return new Error(
|
||||
`Prop '${propName}' is required when 'isToast' is true in '${componentName}'.`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
export default Alert;
|
||||
@@ -8,6 +8,7 @@ import { editPasswordValidation } from "../../../Validation/validation";
|
||||
import Alert from "../../Alert";
|
||||
import { update } from "../../../Features/Auth/authSlice";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
|
||||
/**
|
||||
* PasswordPanel component manages the form for editing password.
|
||||
@@ -78,15 +79,23 @@ const PasswordPanel = () => {
|
||||
} else {
|
||||
const action = await dispatch(update({ authToken, localData }));
|
||||
if (action.payload.success) {
|
||||
// TODO: Add toast/notification for password update success
|
||||
createToast({
|
||||
variant: "info",
|
||||
body: "Your password was changed successfully.",
|
||||
hasIcon: false,
|
||||
});
|
||||
setLocalData({
|
||||
password: "",
|
||||
newPassword: "",
|
||||
confirm: "",
|
||||
});
|
||||
} else {
|
||||
// TODO: Add toast/notification for password update rejected
|
||||
// TODO: Check for other errors?
|
||||
createToast({
|
||||
variant: "info",
|
||||
body: "Your password input was incorrect.",
|
||||
hasIcon: false,
|
||||
});
|
||||
setErrors({ password: "*" + action.payload.msg + "." });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import ImageIcon from "@mui/icons-material/Image";
|
||||
import ProgressUpload from "../../ProgressBars";
|
||||
import { formatBytes } from "../../../Utils/fileUtils";
|
||||
import { clearMonitorState } from "../../../Features/Monitors/monitorsSlice";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
|
||||
/**
|
||||
* ProfilePanel component displays a form for editing user profile information
|
||||
@@ -58,6 +59,7 @@ const ProfilePanel = () => {
|
||||
|
||||
// Handles input field changes and performs validation
|
||||
const handleChange = (event) => {
|
||||
errors["unchanged"] && clearError("unchanged");
|
||||
const { value, id } = event.target;
|
||||
const name = idToName[id];
|
||||
setLocalData((prev) => ({
|
||||
@@ -85,7 +87,7 @@ const ProfilePanel = () => {
|
||||
delete: false,
|
||||
});
|
||||
|
||||
//TODO - potentitally remove once image compression functions are implemented
|
||||
//TODO - potentitally remove, will revisit in the future
|
||||
intervalRef.current = setInterval(() => {
|
||||
const buffer = 12;
|
||||
setProgress((prev) => {
|
||||
@@ -151,14 +153,11 @@ const ProfilePanel = () => {
|
||||
deleteProfileImage: false,
|
||||
}));
|
||||
setIsOpen("");
|
||||
errors["unchanged"] && clearError("unchanged");
|
||||
};
|
||||
|
||||
console.log(
|
||||
localData.deleteProfileImage === false && localData.file === undefined
|
||||
);
|
||||
|
||||
// Handles form submission to update user profile
|
||||
const handleSaveProfile = (event) => {
|
||||
const handleSaveProfile = async (event) => {
|
||||
event.preventDefault();
|
||||
if (
|
||||
localData.firstname === user.firstname &&
|
||||
@@ -166,12 +165,29 @@ const ProfilePanel = () => {
|
||||
localData.deleteProfileImage === undefined &&
|
||||
localData.file === undefined
|
||||
) {
|
||||
// TODO: Add toast/notification for unchanged profile data
|
||||
createToast({
|
||||
variant: "warning",
|
||||
body: "Unable to update profile: No changes detected.",
|
||||
hasIcon: false,
|
||||
});
|
||||
setErrors({ unchanged: "unable to update profile" });
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(update({ authToken, localData }));
|
||||
// TODO: Add toast/notification for profile update success
|
||||
const action = await dispatch(update({ authToken, localData }));
|
||||
if (action.payload.success) {
|
||||
createToast({
|
||||
variant: "info",
|
||||
body: "Your profile data was changed successfully.",
|
||||
hasIcon: false,
|
||||
});
|
||||
} else {
|
||||
createToast({
|
||||
variant: "error",
|
||||
body: "There was an error updating your profile data.",
|
||||
hasIcon: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Removes current profile image from UI
|
||||
@@ -180,6 +196,7 @@ const ProfilePanel = () => {
|
||||
...prev,
|
||||
deleteProfileImage: true,
|
||||
}));
|
||||
errors["unchanged"] && clearError("unchanged");
|
||||
};
|
||||
|
||||
// Initiates the account deletion process
|
||||
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
import "./announcementUpdateSubscription.css";
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {string} props.title - Title text for the update subscription dialog (required)
|
||||
* @param {string} props.text - Body text for the update subscription dialog (required)
|
||||
* @param {string} props.cancel - Text for the negative (cancel) button (required)
|
||||
* @param {string} props.positive - Text for the positive button (required)
|
||||
* @param {string} props.header - Title text for the email subscription section (required)
|
||||
* @param {string} props.button - Text for the subscribe button (required)
|
||||
* @returns {JSX.Element} - Renders the announcement update subscription component
|
||||
*/
|
||||
|
||||
const AnnouncementUpdateSubscription = ({
|
||||
title,
|
||||
text,
|
||||
cancel,
|
||||
positive,
|
||||
header,
|
||||
button,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const fontLookup = {
|
||||
default: theme.font.default.font,
|
||||
};
|
||||
|
||||
const fontFamily = fontLookup["default"];
|
||||
|
||||
return (
|
||||
<div className="update-subscription" style={{ fontFamily: fontFamily }}>
|
||||
<div className="update-subscription-dialog">
|
||||
<div className="subscription-dialog-title">{title}</div>
|
||||
<div className="v-spacing-small"></div>
|
||||
<div className="subscription-dialog-text">{text}</div>
|
||||
<div className="v-spacing-medium"></div>
|
||||
<div className="subscription-dialog-controls">
|
||||
<button className="controls-negative">{cancel}</button>
|
||||
<button className="controls-positive">{positive}</button>
|
||||
</div>
|
||||
<div className="v-spacing-medium"></div>
|
||||
</div>
|
||||
<div className="v-spacing-medium"></div>
|
||||
<div className="update-subscription-title">{header}</div>
|
||||
<div className="subscription-email">
|
||||
<input
|
||||
className="subscription-email-field"
|
||||
type="text"
|
||||
placeholder="olivia@untitledui.com"
|
||||
/>
|
||||
<button className="subscribe-button">{button}</button>
|
||||
</div>
|
||||
<div className="v-spacing-medium"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AnnouncementUpdateSubscription.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
cancel: PropTypes.string.isRequired,
|
||||
positive: PropTypes.string.isRequired,
|
||||
header: PropTypes.string.isRequired,
|
||||
button: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default AnnouncementUpdateSubscription;
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
.update-subscription {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.update-subscription,
|
||||
.update-subscription-dialog {
|
||||
max-width: 770px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #eaecf0;
|
||||
}
|
||||
|
||||
.update-subscription-dialog {
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
.subscription-dialog-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--env-var-color-1);
|
||||
}
|
||||
|
||||
.v-spacing-small {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.v-spacing-medium {
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.subscription-dialog-text {
|
||||
font-size: 13px;
|
||||
color: var(--env-var-color-2);
|
||||
}
|
||||
|
||||
.subscription-dialog-controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.controls-negative {
|
||||
padding: 12px 16px;
|
||||
margin-right: 12px;
|
||||
border-radius: var(--env-var-radius-1);
|
||||
border: 1px solid var(--env-var-color-6);
|
||||
background-color: var(--env-var-color-8);
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
}
|
||||
|
||||
.controls-positive,
|
||||
.subscribe-button {
|
||||
padding: 12px 16px;
|
||||
margin-right: 12px;
|
||||
border-radius: var(--env-var-radius-1);
|
||||
border: 1px solid var(--env-var-color-7);
|
||||
background-color: var(--env-var-color-3);
|
||||
color: var(--env-var-color-8);
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
}
|
||||
|
||||
.update-subscription-title {
|
||||
color: var(--env-var-color-5);
|
||||
padding: 0 20px;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.subscription-email {
|
||||
display: flex;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.subscription-email-field {
|
||||
width: 290px;
|
||||
height: 24px;
|
||||
border-radius: var(--env-var-radius-1);
|
||||
border: 1px solid var(--env-var-color-4);
|
||||
box-shadow: 0px 1px 2px 0px rgba(101, 182, 28, 0.05);
|
||||
padding: 5px 15px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.subscribe-button {
|
||||
width: 100px;
|
||||
height: 34px;
|
||||
padding: 2px 15px;
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import "./announcementsDualButton.css";
|
||||
import React from "react";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {string} props.subject - The announcement subject text
|
||||
* @param {string} props.body - The announcement body text content
|
||||
* @param {string} props.esc - The text for the escape button
|
||||
* @param {string} props.primary - The text for the primary button
|
||||
* @returns {JSX.Element} - Renders the announcement dual button component
|
||||
*/
|
||||
|
||||
const AnnouncementsDualButton = ({ subject, body, esc, primary }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const fontLookup = {
|
||||
default: theme.font.default.font,
|
||||
};
|
||||
|
||||
const fontFamily = fontLookup["default"];
|
||||
|
||||
return (
|
||||
<div
|
||||
className="announcement-without-tile"
|
||||
style={{ fontFamily: fontFamily }}
|
||||
>
|
||||
<div className="announcement-without-content">
|
||||
<div className="announcement-without-content-subject">{subject}</div>
|
||||
<div className="v-spacing"></div>
|
||||
<div className="announcement-without-content-body">{body}</div>
|
||||
<div className="announcement-content-controllers">
|
||||
<div className="controllers-button-esc">{esc}</div>
|
||||
<div className="controllers-button-primary">{primary}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-spacing"></div>
|
||||
<div className="announcement-close">
|
||||
<CloseIcon style={{ fill: "#98A2B3" }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AnnouncementsDualButton.propTypes = {
|
||||
subject: PropTypes.string.isRequired,
|
||||
body: PropTypes.string.isRequired,
|
||||
esc: PropTypes.string.isRequired,
|
||||
primary: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default AnnouncementsDualButton;
|
||||
@@ -1,25 +0,0 @@
|
||||
.announcement-without-tile {
|
||||
margin: 20px;
|
||||
max-width: 343px;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
border: 1px solid var(--env-var-color-4);
|
||||
padding: 16px;
|
||||
border-radius: var(--env-var-radius-1);
|
||||
}
|
||||
|
||||
.h-spacing {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.announcement-without-content-subject {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
color: var(--env-var-color-5);
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.announcement-without-content-body {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
color: var(--env-var-color-2);
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
import "./announcementsDualButtonWithIcon.css";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {React.ReactNode} props.icon - Optional React node for an announcement icon
|
||||
* @param {string} props.subject - The announcement subject text
|
||||
* @param {string} props.body - The announcement body text content
|
||||
* @param {string} props.esc - The text for the escape button (usually "Close")
|
||||
* @param {string} props.primary - The text for the primary button
|
||||
* @param {function} props.closeToast - Function to close the toast notification
|
||||
* @returns {JSX.Element} - Renders the announcement dual button with icon component
|
||||
*/
|
||||
|
||||
const AnnouncementsDualButtonWithIcon = ({
|
||||
icon,
|
||||
subject,
|
||||
body,
|
||||
esc,
|
||||
primary,
|
||||
closeToast,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const fontLookup = {
|
||||
default: theme.font.default.font,
|
||||
};
|
||||
|
||||
const fontFamily = fontLookup["default"];
|
||||
|
||||
return (
|
||||
<div className="announcement-tile" style={{ fontFamily: fontFamily }}>
|
||||
{icon && <div className="announcement-icon">{icon}</div>}
|
||||
<div className="announcement-content">
|
||||
{subject && (
|
||||
<div className="announcement-content-subject">{subject}</div>
|
||||
)}
|
||||
{body && <div className="announcement-content-body">{body}</div>}
|
||||
{(esc || primary) && (
|
||||
<div className="announcement-content-controllers">
|
||||
{esc && (
|
||||
<div className="controllers-button-esc" onClick={closeToast}>
|
||||
{esc}
|
||||
</div>
|
||||
)}
|
||||
{primary && (
|
||||
<div className="controllers-button-primary">{primary}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="announcement-close" onClick={closeToast}>
|
||||
<CloseIcon style={{ fill: "#98A2B3" }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AnnouncementsDualButtonWithIcon.propTypes = {
|
||||
icon: PropTypes.object,
|
||||
subject: PropTypes.string,
|
||||
body: PropTypes.string,
|
||||
esc: PropTypes.string,
|
||||
primary: PropTypes.string,
|
||||
closeToast: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AnnouncementsDualButtonWithIcon;
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
.announcement-tile {
|
||||
margin: 20px;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
border: 1px solid var(--env-var-color-4);
|
||||
padding: 16px;
|
||||
border-radius: var(--env-var-radius-1);
|
||||
}
|
||||
|
||||
.info-icon-frame {
|
||||
display: flex;
|
||||
width: var(--env-var-default-2);
|
||||
height: var(--env-var-default-2);
|
||||
border: 1px solid var(--env-var-color-6);
|
||||
border-radius: var(--env-var-radius-2);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.announcement-content {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.announcement-content-subject {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
color: var(--env-var-color-5);
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.announcement-content-body {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
color: var(--env-var-color-2);
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.announcement-content-controllers {
|
||||
display: flex;
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.controllers-button-esc {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.controllers-button-primary {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
font-weight: 600;
|
||||
color: var(--env-var-color-3);
|
||||
}
|
||||
|
||||
.announcement-close {
|
||||
width: var(--env-var-default-1);
|
||||
height: var(--env-var-default-1);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import "./announcementsMessageBar.css";
|
||||
import React from "react";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {string} props.message - The announcement message text content (required)
|
||||
* @returns {JSX.Element} - Renders the announcement message bar component
|
||||
*/
|
||||
const AnnouncementsMessageBar = ({ message }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const fontLookup = {
|
||||
default: theme.font.default.font,
|
||||
};
|
||||
|
||||
const fontFamily = fontLookup["default"];
|
||||
|
||||
return (
|
||||
<div className="announcement-tile" style={{ fontFamily: fontFamily }}>
|
||||
<div className="announcement-messagebar-body">{message}</div>
|
||||
<div className="announcement-close">
|
||||
<CloseIcon style={{ fill: "#98A2B3" }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AnnouncementsMessageBar.propTypes = {
|
||||
message: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default AnnouncementsMessageBar;
|
||||
@@ -1,8 +0,0 @@
|
||||
.announcement-messagebar-body {
|
||||
margin: 20px;
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
padding: 0 16px 0 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.Toastify__toast-container {
|
||||
width: auto;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert";
|
||||
import AnnouncementsDualButtonWithIcon from "./AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
import "./index.css";
|
||||
|
||||
/**
|
||||
* ToastComponent displays a notification that is triggered by error messages.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - The component props
|
||||
* @param {string} props.subject - The subject or title of the toast message
|
||||
* @param {string} props.body - The body content of the toast message
|
||||
* @param {string} props.esc - The text for the dismiss action button
|
||||
* @param {string} props.primary - The text for the primary action button
|
||||
* @returns {JSX.Element} JSX element representing the ToastComponent
|
||||
*/
|
||||
|
||||
const ToastComponent = ({ subject, body, esc, primary }) => {
|
||||
const displayMsg = () => {
|
||||
toast(
|
||||
({ closeToast, toastProps }) => (
|
||||
<AnnouncementsDualButtonWithIcon
|
||||
icon={<ComplexAlert theme="red" />}
|
||||
subject={subject}
|
||||
body={body}
|
||||
esc={esc}
|
||||
primary={primary}
|
||||
closeToast={closeToast}
|
||||
/>
|
||||
),
|
||||
{ closeButton: false }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={displayMsg}>Click me</button>
|
||||
<ToastContainer className="Toastify__toast-container" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ToastComponent.propTypes = {
|
||||
subject: PropTypes.string.isRequired,
|
||||
body: PropTypes.string.isRequired,
|
||||
esc: PropTypes.string.isRequired,
|
||||
primary: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ToastComponent;
|
||||
@@ -8,6 +8,8 @@ import ProfilePanel from "../../Components/TabPanels/Account/ProfilePanel";
|
||||
import PasswordPanel from "../../Components/TabPanels/Account/PasswordPanel";
|
||||
import TeamPanel from "../../Components/TabPanels/Account/TeamPanel";
|
||||
import { useNavigate } from "react-router";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
|
||||
/**
|
||||
* Account component renders a settings page with tabs for Profile, Password, and Team settings.
|
||||
@@ -73,6 +75,7 @@ const Account = ({ open = "profile" }) => {
|
||||
<PasswordPanel />
|
||||
<TeamPanel />
|
||||
</TabContext>
|
||||
<ToastContainer />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,6 +40,8 @@ import { getMonitorsByUserId } from "../../Features/Monitors/monitorsSlice";
|
||||
import ImageField from "../../Components/TextFields/Image";
|
||||
import ProgressUpload from "../../Components/ProgressBars";
|
||||
import Alert from "../../Components/Alert";
|
||||
import { createToast } from "../../Utils/toastUtils";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
|
||||
const cols = [
|
||||
{
|
||||
@@ -421,11 +423,20 @@ const Demo = () => {
|
||||
</Stack>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<Stack justifyContent="center" alignItems="center">
|
||||
<ImageField />
|
||||
<ImageField
|
||||
id="test-image-field"
|
||||
onChange={() => console.log("changed")}
|
||||
/>
|
||||
</Stack>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<Stack justifyContent="center" alignItems="center">
|
||||
<ProgressUpload icon={<ImageIcon />} label="image.jpg" size="2 MB" />
|
||||
<ProgressUpload
|
||||
icon={<ImageIcon />}
|
||||
label="image.jpg"
|
||||
size="2 MB"
|
||||
progress={50}
|
||||
onClick={() => console.log("click")}
|
||||
/>
|
||||
</Stack>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<Stack justifyContent="center" alignItems="center">
|
||||
@@ -449,8 +460,38 @@ const Demo = () => {
|
||||
body="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
/>
|
||||
</Box>
|
||||
<Box width="500px" mt="5px">
|
||||
<Alert
|
||||
variant="info"
|
||||
title="We've just released a new feature"
|
||||
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid pariatur, ipsum dolor."
|
||||
isToast={true}
|
||||
/>
|
||||
</Box>
|
||||
<Box width="500px" mt="5px">
|
||||
<Alert
|
||||
variant="info"
|
||||
body="Your password is incorrect."
|
||||
isToast={true}
|
||||
hasIcon={false}
|
||||
/>
|
||||
</Box>
|
||||
<Box width="500px" mt="5px">
|
||||
<Button
|
||||
level="primary"
|
||||
label="Toast"
|
||||
onClick={() =>
|
||||
createToast({
|
||||
variant: "info",
|
||||
body: "Your password is incorrect.",
|
||||
hasIcon: false,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<ToastContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,16 +4,9 @@ import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
||||
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
|
||||
import WebsiteTextField from "../../Components/TextFields/Website/WebsiteTextField";
|
||||
import DescriptionTextField from "../../Components/TextFields/Description/DescriptionTextField";
|
||||
import AnnouncementsDualButton from "../../Components/Toast/AnnouncementsDualButton/AnnouncementsDualButton";
|
||||
import AnnouncementsDualButtonWithIcon from "../../Components/Toast/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
|
||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||||
import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert";
|
||||
import AnnouncementsMessageBar from "../../Components/Toast/AnnouncementsMessageBar/AnnouncementsMessageBar";
|
||||
import AnnouncementUpdateSubscription from "../../Components/Toast/AnnouncementUpdateSubscription/AnnouncementUpdateSubscription";
|
||||
import PlayGroundCharts from "./PlayGround-Charts";
|
||||
import PlayGroundPopupModals from "./PlayGround-Popup-Modals";
|
||||
import PlayGroundTooltips from "./PlayGround-Tooltips";
|
||||
import Toast from "../../Components/Toast";
|
||||
|
||||
// This Component is just for the development and test
|
||||
// purposes and just to see what my components look like while development
|
||||
@@ -53,7 +46,6 @@ function PlayGround() {
|
||||
hasCopyButton={true}
|
||||
hintText="This is a hint text to help user."
|
||||
/>
|
||||
<Toast />
|
||||
|
||||
<hr />
|
||||
{/* Now, illustration of the Description text fields */}
|
||||
@@ -66,55 +58,6 @@ function PlayGround() {
|
||||
hasError={true}
|
||||
hintText="This is a hint text to help user."
|
||||
/>
|
||||
<AnnouncementsDualButtonWithIcon
|
||||
icon={
|
||||
<div className="info-icon-frame">
|
||||
<InfoOutlinedIcon style={{ fill: "#344054" }} />
|
||||
</div>
|
||||
}
|
||||
subject="We’ve just released a new feature"
|
||||
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
|
||||
pariatur, ipsum dolor."
|
||||
esc="Dismiss"
|
||||
primary="View changes"
|
||||
/>
|
||||
|
||||
<AnnouncementsDualButtonWithIcon
|
||||
icon={<ComplexAlert theme="red" />}
|
||||
subject="There was a problem with that action"
|
||||
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
|
||||
pariatur, ipsum dolor."
|
||||
esc="Dismiss"
|
||||
primary="Learn more"
|
||||
/>
|
||||
|
||||
<AnnouncementsDualButtonWithIcon
|
||||
icon={<ComplexAlert theme="green" />}
|
||||
subject="Successfully updated profile"
|
||||
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
|
||||
pariatur, ipsum dolor."
|
||||
esc="Dismiss"
|
||||
primary="Learn more"
|
||||
/>
|
||||
|
||||
<AnnouncementsDualButton
|
||||
subject="We’ve just released a new feature"
|
||||
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
|
||||
pariatur, ipsum dolor."
|
||||
esc="Dismiss"
|
||||
primary="View changes"
|
||||
/>
|
||||
|
||||
<AnnouncementsMessageBar message="New employee created successfully" />
|
||||
|
||||
<AnnouncementUpdateSubscription
|
||||
title="We’ve just released a new update!"
|
||||
text="Check out the all new dashboard view. Pages and now load faster."
|
||||
cancel="Dismiss"
|
||||
positive="Changelog"
|
||||
header="Subscribe to updates"
|
||||
button="Subscribe"
|
||||
/>
|
||||
<hr />
|
||||
<PlayGroundCharts />
|
||||
<hr />
|
||||
|
||||
@@ -109,9 +109,9 @@ const theme = createTheme({
|
||||
border: "#f04438",
|
||||
},
|
||||
warning: {
|
||||
color: "#f79009",
|
||||
bg: "#fffaef",
|
||||
border: "#fecf60"
|
||||
color: "#DC6803",
|
||||
bg: "#fffcf5",
|
||||
border: "#fec84b"
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { toast } from "react-toastify";
|
||||
import Alert from "../Components/Alert";
|
||||
|
||||
/**
|
||||
* @param {object} props
|
||||
* @param {'info' | 'error' | 'warning'} - The variant of the alert (e.g., "info", "error").
|
||||
* @param {string} props.title - The title of the alert.
|
||||
* @param {string} props.body - The body/content of the alert.
|
||||
* @param {boolean} props.hasIcon - Whether the alert should include an icon.
|
||||
* @param {object} [props.config] - Additional configuration props for the toast.
|
||||
*/
|
||||
|
||||
export const createToast = ({ variant, title, body, hasIcon, config = {} }) => {
|
||||
const toastConfig = {
|
||||
position: "bottom-right",
|
||||
autoClose: 3000,
|
||||
hideProgressBar: true,
|
||||
closeButton: false,
|
||||
...config,
|
||||
};
|
||||
|
||||
toast(
|
||||
({ closeToast }) => (
|
||||
<Alert
|
||||
variant={variant}
|
||||
title={title}
|
||||
body={body}
|
||||
isToast={true}
|
||||
hasIcon={hasIcon}
|
||||
onClick={closeToast}
|
||||
/>
|
||||
),
|
||||
toastConfig
|
||||
);
|
||||
};
|
||||
|
||||
createToast.propTypes = {
|
||||
variant: PropTypes.oneOf(["info", "error", "warning"]).isRequired,
|
||||
title: PropTypes.string,
|
||||
body: PropTypes.string,
|
||||
hasIcon: PropTypes.bool,
|
||||
config: PropTypes.object,
|
||||
};
|
||||
@@ -124,6 +124,18 @@ button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.Toastify__toast-container {
|
||||
width: auto;
|
||||
}
|
||||
.Toastify [class^="Toastify__toast"] {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.Toastify__toast{
|
||||
min-height: 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
|
||||
Reference in new issue
Block a user