remove unused tabpanel components
This commit is contained in:
1 parent
40a5abab91
commit
68c8058d47
4 files changed
-663
No files matched your search
@@ -1,202 +0,0 @@
|
||||
import { useState, useContext, useEffect } from "react";
|
||||
import { Button, Box, Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import TabPanel from "@mui/lab/TabPanel";
|
||||
|
||||
import ConfigBox from "../../../Components/ConfigBox";
|
||||
import { StatusFormContext } from "../../../Pages/StatusPage/CreateStatusContext";
|
||||
import { useSelector } from "react-redux";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { networkService } from "../../../main";
|
||||
import ServersList from "./ServersList";
|
||||
import Checkbox from "../../Inputs/Checkbox";
|
||||
import { publicPageSettingsValidation } from "../../../Validation/validation";
|
||||
import { buildErrors } from "../../../Validation/error";
|
||||
|
||||
/**
|
||||
* Content Panel is used to compose the second part of the status page
|
||||
* for the servers/monitors to watch for in its public page presence and some
|
||||
* other server related configurations etc
|
||||
*
|
||||
*/
|
||||
const ContentPanel = () => {
|
||||
const theme = useTheme();
|
||||
const { form, setForm, errors, setErrors, handleBlur, handelCheckboxChange } =
|
||||
useContext(StatusFormContext);
|
||||
const [cards, setCards] = useState([]);
|
||||
const { user, authToken } = useSelector((state) => state.auth);
|
||||
const [monitors, setMonitors] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitors = async () => {
|
||||
try {
|
||||
const response = await networkService.getMonitorsByTeamId({
|
||||
authToken: authToken,
|
||||
teamId: user.teamId,
|
||||
limit: null, // donot return any checks for the monitors
|
||||
types: ["http"], // status page is available only for the uptime type
|
||||
});
|
||||
if (response.data.data.monitors.length == 0) {
|
||||
setErrors({ monitors: "Please config monitors to setup status page" });
|
||||
}
|
||||
const fullMonitors = response.data.data.monitors;
|
||||
setMonitors(fullMonitors);
|
||||
if (form.monitors.length > 0) {
|
||||
const initiCards = form.monitors.map((mid, idx) => ({
|
||||
id: "" + idx,
|
||||
val: fullMonitors.filter((fM) =>
|
||||
mid._id ? fM._id == mid._id : fM._id == mid
|
||||
)[0],
|
||||
}));
|
||||
setCards(initiCards);
|
||||
}
|
||||
} catch (error) {
|
||||
createToast({ body: "Failed to fetch monitors data" });
|
||||
logger.error("Failed to fetch monitors", error);
|
||||
}
|
||||
};
|
||||
fetchMonitors();
|
||||
}, [user, authToken]);
|
||||
const handleAddNew = () => {
|
||||
if (cards.length === monitors.length) return;
|
||||
const newCards = [...cards, { id: "" + Math.random(), val: {} }];
|
||||
setCards(newCards);
|
||||
};
|
||||
const removeCard = (id) => {
|
||||
const newCards = cards.filter((c) => c?.id != id);
|
||||
setCards(newCards);
|
||||
setForm({
|
||||
...form,
|
||||
monitors: newCards.filter((c) => c.val !== undefined).map((c) => c.val),
|
||||
});
|
||||
};
|
||||
|
||||
const handleServersBlur = () => {
|
||||
const { error } = publicPageSettingsValidation.validate(
|
||||
{ monitors: form.monitors },
|
||||
{
|
||||
abortEarly: false,
|
||||
}
|
||||
);
|
||||
setErrors((prev) => {
|
||||
return buildErrors(prev, "monitors", error);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<TabPanel
|
||||
value="Contents"
|
||||
sx={{
|
||||
"& h1, & p, & input": {
|
||||
color: theme.palette.text.tertiary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
component="form"
|
||||
className="status-contents-form"
|
||||
noValidate
|
||||
spellCheck="false"
|
||||
gap={theme.spacing(12)}
|
||||
mt={theme.spacing(6)}
|
||||
>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">Status page servers</Typography>
|
||||
<Typography component="p">
|
||||
You can add any number of servers that you monitor to your status page.
|
||||
You can also reorder them for the best viewing experience.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack
|
||||
className="status-contents-server-list"
|
||||
sx={{
|
||||
margin: theme.spacing(6),
|
||||
border: "solid",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderWidth: "1px",
|
||||
transition: "0.2s",
|
||||
"&:hover": {
|
||||
borderColor: theme.palette.primary.main,
|
||||
backgroundColor: "hsl(215, 87%, 51%, 0.05)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Typography
|
||||
component="p"
|
||||
alignSelf={"center"}
|
||||
>
|
||||
{" "}
|
||||
Servers list{" "}
|
||||
</Typography>
|
||||
<Button
|
||||
color="info"
|
||||
onClick={handleAddNew}
|
||||
disabled={monitors.length === 0 || monitors.length <= cards.length}
|
||||
>
|
||||
Add new
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<ServersList
|
||||
monitors={monitors}
|
||||
cards={cards}
|
||||
setCards={setCards}
|
||||
form={form}
|
||||
setForm={setForm}
|
||||
removeItem={removeCard}
|
||||
onBlur={handleServersBlur}
|
||||
/>
|
||||
|
||||
{errors["monitors"] && (
|
||||
<Typography
|
||||
component="span"
|
||||
className="input-error"
|
||||
color={theme.palette.error.main}
|
||||
sx={{
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
{errors["monitors"]}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">Features</Typography>
|
||||
<Typography component="p">Show more details on the status page</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack sx={{ margin: theme.spacing(6) }}>
|
||||
<Checkbox
|
||||
id="showBarcode"
|
||||
label={`Show Barcode`}
|
||||
isChecked={form.showBarcode}
|
||||
onChange={handelCheckboxChange}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
<Checkbox
|
||||
id="showUptimePercentage"
|
||||
label={`Show Uptime Percentage`}
|
||||
isChecked={form.showUptimePercentage}
|
||||
onChange={handelCheckboxChange}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
</Stack>
|
||||
</TabPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContentPanel;
|
||||
@@ -1,257 +0,0 @@
|
||||
import { useState, useRef, useContext } from "react";
|
||||
import { Box, Button, Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import TabPanel from "@mui/lab/TabPanel";
|
||||
import ImageIcon from "@mui/icons-material/Image";
|
||||
|
||||
import ConfigBox from "../../../Components/ConfigBox";
|
||||
import TextInput from "../../Inputs/TextInput";
|
||||
import ImageField from "../../Inputs/Image";
|
||||
import timezones from "../../../Utils/timezones.json";
|
||||
import Select from "../../Inputs/Select";
|
||||
import { logoImageValidation } from "../../../Validation/validation";
|
||||
import { formatBytes } from "../../../Utils/fileUtils";
|
||||
import ProgressUpload from "../../ProgressBars";
|
||||
import { StatusFormContext } from "../../../Pages/StatusPage/CreateStatusContext";
|
||||
import ColorPicker from "../../Inputs/ColorPicker";
|
||||
import Checkbox from "../../Inputs/Checkbox";
|
||||
|
||||
/**
|
||||
* General settings panel is ued to compose part of the public static page
|
||||
* for general informations like company name, subdomain url, logo and color etc
|
||||
*/
|
||||
const GeneralSettingsPanel = () => {
|
||||
const theme = useTheme();
|
||||
const {
|
||||
form,
|
||||
setForm,
|
||||
errors,
|
||||
setErrors,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
handelCheckboxChange,
|
||||
} = useContext(StatusFormContext);
|
||||
const [logo, setLogo] = useState(form.logo);
|
||||
|
||||
const [progress, setProgress] = useState({ value: 0, isLoading: false });
|
||||
const intervalRef = useRef(null);
|
||||
const STATUS_PAGE = import.meta.env.VITE_STATU_PAGE_URL ?? "status-page";
|
||||
|
||||
// Clears specific error from errors state
|
||||
const clearError = (err) => {
|
||||
setErrors((prev) => {
|
||||
const updatedErrors = { ...prev };
|
||||
if (updatedErrors[err]) delete updatedErrors[err];
|
||||
return updatedErrors;
|
||||
});
|
||||
};
|
||||
const removeLogo = () => {
|
||||
errors["logo"] && clearError("logo");
|
||||
setLogo({});
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
logo: logo?.src,
|
||||
}));
|
||||
// interrupt interval if image upload is canceled prior to completing the process
|
||||
clearInterval(intervalRef.current);
|
||||
setProgress({ value: 0, isLoading: false });
|
||||
};
|
||||
|
||||
const handleColorChange = (newValue) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
color: newValue,
|
||||
}));
|
||||
};
|
||||
|
||||
const validateField = (toValidate, schema, name = "logo") => {
|
||||
const { error } = schema.validate(toValidate, { abortEarly: false });
|
||||
setErrors((prev) => {
|
||||
let prevErrors = { ...prev };
|
||||
if (error) prevErrors[name] = error?.details[0].message;
|
||||
else delete prevErrors[name];
|
||||
return prevErrors;
|
||||
});
|
||||
if (error) return true;
|
||||
};
|
||||
|
||||
const handleLogo = (event) => {
|
||||
const pic = event.target?.files?.[0];
|
||||
let error = validateField({ type: pic?.type, size: pic?.size }, logoImageValidation);
|
||||
if (error) return;
|
||||
|
||||
const newLogo = {
|
||||
src: URL.createObjectURL(pic),
|
||||
name: pic.name,
|
||||
type: pic.type,
|
||||
size: pic.size,
|
||||
};
|
||||
setProgress((prev) => ({ ...prev, isLoading: true }));
|
||||
setLogo(newLogo);
|
||||
setForm({ ...form, logo: newLogo });
|
||||
intervalRef.current = setInterval(() => {
|
||||
const buffer = 12;
|
||||
setProgress((prev) => {
|
||||
if (prev.value + buffer >= 100) {
|
||||
clearInterval(intervalRef.current);
|
||||
return { value: 100, isLoading: false };
|
||||
}
|
||||
return { ...prev, value: prev.value + buffer };
|
||||
});
|
||||
}, 120);
|
||||
};
|
||||
|
||||
return (
|
||||
<TabPanel value="General settings">
|
||||
<Stack
|
||||
component="form"
|
||||
className="status-general-settings-form"
|
||||
noValidate
|
||||
spellCheck="false"
|
||||
gap={theme.spacing(12)}
|
||||
mt={theme.spacing(6)}
|
||||
>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">Access</Typography>
|
||||
<Typography component="p">
|
||||
If your status page is ready, you can mark it as published.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Checkbox
|
||||
id="publish"
|
||||
label={`Published and visible to the public`}
|
||||
isChecked={form.publish}
|
||||
value={form.publish}
|
||||
onChange={handelCheckboxChange}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">Basic Information</Typography>
|
||||
<Typography component="p">
|
||||
Define company name and the subdomain that your status page points to.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(18)}>
|
||||
<TextInput
|
||||
id="companyName"
|
||||
type="text"
|
||||
label="Company name"
|
||||
value={form.companyName}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
helperText={errors["companyName"]}
|
||||
error={errors["companyName"] ? true : false}
|
||||
/>
|
||||
<TextInput
|
||||
id="url"
|
||||
type="url"
|
||||
label="Your status page address"
|
||||
disabled
|
||||
value={form.url ?? "/" + STATUS_PAGE}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
helperText={errors["url"]}
|
||||
error={errors["url"] ? true : false}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">Appearance</Typography>
|
||||
<Typography component="p">
|
||||
Define the default look and feel of your public status page.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Select
|
||||
id="timezone"
|
||||
name="timezone"
|
||||
label="Display timezone"
|
||||
value={form.timezone}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
items={timezones}
|
||||
error={errors["display-timezone"]}
|
||||
/>
|
||||
<Stack direction={"column"}>
|
||||
<Typography
|
||||
component="h3"
|
||||
color={theme.palette.text.secondary}
|
||||
fontWeight={500}
|
||||
fontSize={13}
|
||||
sx={{ mb: theme.spacing(2) }}
|
||||
>
|
||||
Logo
|
||||
</Typography>
|
||||
<ImageField
|
||||
id="logo"
|
||||
src={form.logo?.src ?? logo?.src}
|
||||
loading={progress.isLoading && progress.value !== 100}
|
||||
onChange={handleLogo}
|
||||
isRound={false}
|
||||
/>
|
||||
{progress.isLoading || progress.value !== 0 || errors["logo"] ? (
|
||||
<ProgressUpload
|
||||
icon={<ImageIcon />}
|
||||
label={logo?.name}
|
||||
size={formatBytes(logo?.size)}
|
||||
progress={progress.value}
|
||||
onClick={removeLogo}
|
||||
error={errors["logo"]}
|
||||
/>
|
||||
) : logo && logo.type ? (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={removeLogo}
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxWidth: "200px",
|
||||
alignSelf: "center",
|
||||
mt: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
Remove Logo
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
<Stack direction={"column"}>
|
||||
<Typography
|
||||
component="h3"
|
||||
color={theme.palette.text.secondary}
|
||||
fontWeight={500}
|
||||
fontSize={13}
|
||||
>
|
||||
Color
|
||||
</Typography>
|
||||
|
||||
<ColorPicker
|
||||
id="color"
|
||||
value={form.color}
|
||||
error={errors["color"]}
|
||||
onChange={handleColorChange}
|
||||
onBlur={handleBlur}
|
||||
></ColorPicker>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
</Stack>
|
||||
</TabPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralSettingsPanel;
|
||||
@@ -1,62 +0,0 @@
|
||||
import { ServerStartAdornment, ServerEndAdornment } from "../../../../Inputs/TextInput/Adornments";
|
||||
import Search from "../../../../Inputs/Search";
|
||||
import {useState} from "react"
|
||||
import React from "react";
|
||||
import { Stack } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} id The Id of the Server component
|
||||
* @param {*} monitors The server monitors options
|
||||
* @param {*} value - Current input value for the Autocomplete
|
||||
* @param {*} removeItem The function used to remove a single server
|
||||
* @param {*} onChange The Change handler function to handle when the server value is changed
|
||||
* used to update the server(monitor) lists*
|
||||
* @param {*} onBlur Function to call when the input is blured
|
||||
* @param {*} dragHandleProps the dragHandleProps passed on to the designated dom node
|
||||
* so that there will be a draging indicator when mouse over it
|
||||
* @returns A single server whose value is one of the existing monitors
|
||||
*/
|
||||
|
||||
const Server = ({ id, value, monitors, onChange, removeItem, onBlur, dragHandleProps }) => {
|
||||
const [search, setSearch] = useState("");
|
||||
const handleSearch = (val) => {
|
||||
setSearch(val);
|
||||
};
|
||||
return (
|
||||
<Stack direction={"row"}>
|
||||
<Search
|
||||
id={id}
|
||||
multiple={false}
|
||||
isAdorned={false}
|
||||
options={monitors ? monitors : []}
|
||||
filteredBy="name"
|
||||
inputValue={search}
|
||||
onBlur={onBlur}
|
||||
value={value}
|
||||
startAdornment={<ServerStartAdornment dragHandleProps={dragHandleProps} />}
|
||||
endAdornment={
|
||||
<ServerEndAdornment
|
||||
id={id}
|
||||
removeItem={removeItem}
|
||||
/>
|
||||
}
|
||||
handleInputChange={handleSearch}
|
||||
handleChange={onChange}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Server.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
monitors: PropTypes.array.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
removeItem: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onBlur: PropTypes.func.isRequired,
|
||||
dragHandleProps: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default Server;
|
||||
@@ -1,142 +0,0 @@
|
||||
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
|
||||
import Server from "./Server";
|
||||
import update from "immutability-helper";
|
||||
import PropTypes from "prop-types";
|
||||
import { Stack, useTheme } from "@mui/material";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} monitors The server monitors options
|
||||
* @param {*} cards A set of servers/monitors user can add/remove/drag/drop to be displayed in status page
|
||||
* each card can be autocompleted/selected from the existing monitors list
|
||||
* @param {*} setCards Function to set the cards
|
||||
* @param {*} form The Status page form
|
||||
* @param {*} setForm Function to set the form
|
||||
* @param {*} removeItem The function used to remove a single server
|
||||
* @param {*} onBlur Function to call when the input is blured*
|
||||
* @returns A list of user selected Servers/Monitors
|
||||
*/
|
||||
|
||||
|
||||
const ServersList = ({ monitors, cards, setCards, form, setForm, removeItem, onBlur }) => {
|
||||
const theme = useTheme()
|
||||
const grid = parseInt(theme.spacing(4));
|
||||
|
||||
const handleCardChange = (event, val) => {
|
||||
let newCards;
|
||||
const { id } = event.target;
|
||||
let idx = cards.findIndex((a) => {
|
||||
let found = false;
|
||||
let optionIdx = id.indexOf("-option");
|
||||
if (optionIdx !== -1) found = a.id == id.substr(0, optionIdx);
|
||||
else found = a.id == id;
|
||||
return found;
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let x = JSON.parse(JSON.stringify(cards[idx]));
|
||||
x.val = val;
|
||||
newCards = update(cards, { $splice: [[idx, 1, x]] });
|
||||
} else {
|
||||
newCards = update(cards, { $push: [{ id: id, val: val }] });
|
||||
}
|
||||
setCards(newCards);
|
||||
setForm({ ...form, monitors: newCards.map((c) => c.val) });
|
||||
};
|
||||
|
||||
const moveCard = (dragIndex, hoverIndex) => {
|
||||
const dragCard = cards[dragIndex];
|
||||
const newCards = update(cards, {
|
||||
$splice: [
|
||||
[dragIndex, 1],
|
||||
[hoverIndex, 0, dragCard],
|
||||
],
|
||||
});
|
||||
setCards(newCards);
|
||||
setForm({ ...form, monitors: newCards.map((c) => c.val) });
|
||||
};
|
||||
|
||||
const handleDragEnd = (result) => {
|
||||
// dropped outside the list
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
moveCard(result.source.index, result.destination.index);
|
||||
};
|
||||
|
||||
const getItemStyle = (isDragging, draggableStyle) => ({
|
||||
// some basic styles to make the items look a bit nicer
|
||||
userSelect: "none",
|
||||
padding: grid,
|
||||
margin: `0 0 ${grid}px 0`,
|
||||
|
||||
// change background colour if dragging
|
||||
background: isDragging ? "#D0D5DD" : "#F8F9F8",
|
||||
|
||||
// styles we need to apply on draggables
|
||||
...draggableStyle,
|
||||
});
|
||||
|
||||
const getListStyle = (isDraggingOver) => ({
|
||||
background: isDraggingOver ? "lightblue" : "white",
|
||||
padding: grid,
|
||||
});
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={handleDragEnd}>
|
||||
<Droppable droppableId="droppable">
|
||||
{(provided, snapshot) => (
|
||||
<Stack
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
sx={{...getListStyle(snapshot.isDraggingOver)}}
|
||||
|
||||
>
|
||||
{cards.map((item, index) => (
|
||||
<Draggable
|
||||
key={item.id}
|
||||
draggableId={item.id}
|
||||
index={index}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<Stack
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
sx={{...getItemStyle(
|
||||
snapshot.isDragging,
|
||||
provided.draggableProps.style
|
||||
)}}
|
||||
>
|
||||
<Server
|
||||
key={index}
|
||||
id={item?.id ?? "" + Math.random()}
|
||||
monitors={monitors}
|
||||
value={item.val ?? {}}
|
||||
onChange={handleCardChange}
|
||||
removeItem={removeItem}
|
||||
dragHandleProps={provided.dragHandleProps}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Stack>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
||||
|
||||
ServersList.propTypes = {
|
||||
monitors: PropTypes.array.isRequired,
|
||||
cards: PropTypes.array.isRequired,
|
||||
setCards: PropTypes.func.isRequired,
|
||||
form: PropTypes.object.isRequired,
|
||||
setForm: PropTypes.func.isRequired,
|
||||
removeItem: PropTypes.func.isRequired,
|
||||
onBlur: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ServersList;
|
||||
Reference in new issue
Block a user