diff --git a/src/features/resources/api/useResourcesApi.js b/src/features/resources/api/useResourcesApi.js index 9dc1c1e..ce54d41 100644 --- a/src/features/resources/api/useResourcesApi.js +++ b/src/features/resources/api/useResourcesApi.js @@ -1,6 +1,6 @@ import { useCallback } from "react"; import useHttpRequest from "../../../hooks/useHttpRequest"; -import { get } from "../../../utils/axios"; +import { get, del } from "../../../utils/axios"; import { defaultResourcesFilters } from "../../../constants/resourcesConstants"; const cdn = process.env.REACT_APP_CDN_URL; @@ -50,9 +50,19 @@ const useResourcesApi = () => { [exec] ); + const deleteResource = useCallback( + (resourceId, options) => { + const endpoint = `${cdn}/admin/resource?ResourceId=${resourceId}`; + const promise = exec(() => del(endpoint), options); + return promise; + }, + [exec] + ); + return { getResources, - getResource + getResource, + deleteResource }; }; diff --git a/src/features/resources/edit/components/DeleteDialog.js b/src/features/resources/edit/components/DeleteDialog.js new file mode 100644 index 0000000..886d0e3 --- /dev/null +++ b/src/features/resources/edit/components/DeleteDialog.js @@ -0,0 +1,43 @@ +import React from "react"; +import PropTypes from "prop-types"; +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogContentText from "@material-ui/core/DialogContentText"; +import DialogTitle from "@material-ui/core/DialogTitle"; + +const DeleteDialog = ({ open, onClose, onConfirm, title }) => { + return ( + + {title} + + + {`Are you sure you want to delete the resource '${title}'?`} + + + + + + + + ); +}; + +DeleteDialog.propTypes = { + open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + onConfirm: PropTypes.func.isRequired, + title: PropTypes.string.isRequired +}; + +export default DeleteDialog; diff --git a/src/features/resources/edit/components/MimeTypeComponent.js b/src/features/resources/edit/components/MimeTypeComponent.js index 0f013ca..157778a 100644 --- a/src/features/resources/edit/components/MimeTypeComponent.js +++ b/src/features/resources/edit/components/MimeTypeComponent.js @@ -19,8 +19,8 @@ const MimeTypeComponent = ({ mimeType, mimeTypes, onPropertyChange }) => { {isAutomaticMimeType ? ( { color="primary" /> } - label="Auto mime type" + label="Auto MIME type" /> diff --git a/src/features/resources/edit/components/ResourceComponent.js b/src/features/resources/edit/components/ResourceComponent.js index bd8cd61..9810f64 100644 --- a/src/features/resources/edit/components/ResourceComponent.js +++ b/src/features/resources/edit/components/ResourceComponent.js @@ -1,85 +1,173 @@ import React from "react"; import PropTypes from "prop-types"; -import { Grid, Paper, ButtonBase, TextField } from "@material-ui/core"; +import { + Grid, + Paper, + ButtonBase, + TextField, + FormControlLabel, + Checkbox, + Button +} from "@material-ui/core"; +import Autocomplete from "@material-ui/lab/Autocomplete"; +import { Save as SaveIcon, Delete as DeleteIcon } from "@material-ui/icons"; import { makeStyles } from "@material-ui/core/styles"; import style from "../styles"; import ResourcePreviewComponent from "./ResourcePreviewComponent"; import MimeTypeComponent from "./MimeTypeComponent"; +import DeleteDialog from "./DeleteDialog"; import { onTextFieldChange } from "../../../../utils/adapters"; const useStyles = makeStyles(style); -const ResourceComponent = ({ resource, mimeTypes, onPropertyChange }) => { +const ResourceComponent = ({ + resource, + mimeTypes, + resourceCategories, + onPropertyChange, + deleteProps, + processing +}) => { const classes = useStyles(); return ( - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + onPropertyChange("categoryId")(value.categoryId) + } + getOptionLabel={(option) => { + const optionIsObject = + typeof option === "object" && option !== null; + if (optionIsObject) return option.categoryName; + + const category = resourceCategories.find( + (z) => z.categoryId === option + ); + return category.categoryName; + }} + getOptionSelected={(option, value) => + option.categoryId === value + } + renderInput={(params) => ( + + )} + /> + + + + onPropertyChange("secured")(event.target.checked) + } + name="resources-is-secured" + color="primary" + /> + } + label="Secured" + /> + - - - - - - - + + + + + + + + - - - - - + + + ); }; ResourceComponent.propTypes = { resource: PropTypes.object.isRequired, mimeTypes: PropTypes.array.isRequired, - onPropertyChange: PropTypes.func.isRequired + resourceCategories: PropTypes.array.isRequired, + onPropertyChange: PropTypes.func.isRequired, + deleteProps: PropTypes.object.isRequired, + processing: PropTypes.bool.isRequired }; export default ResourceComponent; diff --git a/src/features/resources/edit/components/ResourceContainer.js b/src/features/resources/edit/components/ResourceContainer.js index ceb473f..e6ccea1 100644 --- a/src/features/resources/edit/components/ResourceContainer.js +++ b/src/features/resources/edit/components/ResourceContainer.js @@ -6,18 +6,23 @@ import { LoadingText } from "../../../../components"; import ResourceComponent from "./ResourceComponent"; import { makeStyles } from "@material-ui/core/styles"; import style from "../styles"; +import { useToast } from "../../../../hooks"; const useStyles = makeStyles(style); const ResourceContainer = () => { const [state, setState] = useState(null); const [loading, setLoading] = useState(false); - const [mimeTypes, setMimeTypes] = useState([]); + const [mimeTypes, setMimeTypes] = useState(null); + const [resourceCategories, setResourceCategories] = useState(null); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [processing, setProcessing] = useState(null); const classes = useStyles(); const params = useParams(); - const { getResource } = useResourcesApi(); - const { getMimeTypes } = useDictionariesApi(); + const { getResource, deleteResource } = useResourcesApi(); + const { getMimeTypes, getResourceCategories } = useDictionariesApi(); + const { success } = useToast(); const isNew = useMemo(() => params.id === "new", [params.id]); @@ -25,6 +30,10 @@ const ResourceContainer = () => { getMimeTypes().then((r) => setMimeTypes(r)); }, [getMimeTypes]); + useEffect(() => { + getResourceCategories().then((r) => setResourceCategories(r)); + }, [getResourceCategories]); + useEffect(() => { if (isNew) return; const resourceId = parseInt(params.id); @@ -39,7 +48,17 @@ const ResourceContainer = () => { setState((prev) => ({ ...prev, [prop]: value })); }; - if (loading || state === null) return ; + const handleDelete = async (resourceId) => { + setProcessing("delete"); + const response = await deleteResource(resourceId); + if (response && response.resourceId === state.resourceId) + success(`Resource '${state.resourceName}' was successfully deleted.`); + setProcessing(null); + setDeleteDialogOpen(false); + }; + + if (loading || !state || !mimeTypes || !resourceCategories) + return ; return ( <> @@ -48,7 +67,15 @@ const ResourceContainer = () => { setDeleteDialogOpen(true), + onClose: () => setDeleteDialogOpen(false), + onConfirm: () => handleDelete(state.resourceId) + }} + processing={processing} /> diff --git a/src/features/resources/edit/styles.js b/src/features/resources/edit/styles.js index 1bba25a..4a7d6e2 100644 --- a/src/features/resources/edit/styles.js +++ b/src/features/resources/edit/styles.js @@ -12,6 +12,9 @@ const style = (theme) => { display: "block", maxWidth: "100%", maxHeight: "100%" + }, + button: { + marginRight: theme.spacing(1) } }; }; diff --git a/src/hooks/useHttpRequest.js b/src/hooks/useHttpRequest.js index 8a523e5..85b999a 100644 --- a/src/hooks/useHttpRequest.js +++ b/src/hooks/useHttpRequest.js @@ -6,7 +6,24 @@ const useHttpRequest = () => { const handleError = useCallback( (err) => { - const message = `${err.title} ${err.correlationId}`; + let message; + switch (err?.statusCode) { + case 500: + message = "Internal server error."; + break; + + case 404: + message = err.serverMessage; + break; + + case 401: + message = `Unauthorized: ${err.serverMessage}`; + break; + + default: + message = "An unexpected error has occurred."; + } + error(message); }, [error]