diff --git a/package.json b/package.json
index ee5092c..4191c69 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
"react-data-components": "^1.1.1",
"react-dialog": "^1.0.2",
"react-dom": "^15.5.4",
+ "react-draggable": "^2.2.6",
"react-geosuggest": "^2.5.0",
"react-geosuggest-sw": "^1.4.13",
"react-google-maps": "^7.2.0",
@@ -67,8 +68,10 @@
"react-imgix": "^7.1.1",
"react-jquery-datatables": "^0.7.1",
"react-materialui-notifications": "^0.5.1",
+ "react-onclickoutside": "^5.10.0",
"react-places-autocomplete": "^5.4.2",
"react-redux": "^5.0.5",
+ "react-resizable": "^1.7.1",
"react-router": "^3.0.4",
"react-router-redux": "^4.0.8",
"react-tap-event-plugin": "^2.0.1",
diff --git a/src/components/Shared/ValidationErrorsInfoDialog.js b/src/components/Shared/ValidationErrorsInfoDialog.js
index 9251358..1bb9b85 100644
--- a/src/components/Shared/ValidationErrorsInfoDialog.js
+++ b/src/components/Shared/ValidationErrorsInfoDialog.js
@@ -1,8 +1,8 @@
import React, { Component } from 'react';
import FlatButton from 'material-ui/FlatButton';
-import Dialog from 'react-dialog'
+import Dialog from './draggable-dialog';
-import 'react-dialog/css/index.css';
+import './draggable-dialog/css/index.css';
export class ValidationErrorsInfoDialog extends React.Component {
diff --git a/src/components/Shared/draggable-dialog/DialogBody.js b/src/components/Shared/draggable-dialog/DialogBody.js
new file mode 100644
index 0000000..455e04a
--- /dev/null
+++ b/src/components/Shared/draggable-dialog/DialogBody.js
@@ -0,0 +1,18 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+class DialogBody extends React.Component {
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
+
+DialogBody.propTypes = {
+ children: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
+};
+
+export default DialogBody;
\ No newline at end of file
diff --git a/src/components/Shared/draggable-dialog/DialogFooter.js b/src/components/Shared/draggable-dialog/DialogFooter.js
new file mode 100644
index 0000000..6fe70d5
--- /dev/null
+++ b/src/components/Shared/draggable-dialog/DialogFooter.js
@@ -0,0 +1,43 @@
+import React from "react";
+import PropTypes from "prop-types";
+import cs from "classnames";
+
+const DialogFooter = (props) => {
+ const buttons = props.buttons;
+ if (!buttons || buttons.length == 0) {
+ return false;
+ }
+
+ const dialogButtons = buttons.map(function (button, index) {
+ if (React.isValidElement(button)) {
+ return button;
+ }
+
+ const { text, onClick, className } = button;
+
+ return (
+
+ );
+ }, this);
+
+ return (
+
+ );
+};
+
+DialogFooter.propTypes = {
+ buttons: PropTypes.array,
+ onClose: PropTypes.func.isRequired
+};
+
+export default DialogFooter;
\ No newline at end of file
diff --git a/src/components/Shared/draggable-dialog/DialogTitle.js b/src/components/Shared/draggable-dialog/DialogTitle.js
new file mode 100644
index 0000000..66c7f0b
--- /dev/null
+++ b/src/components/Shared/draggable-dialog/DialogTitle.js
@@ -0,0 +1,65 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+const DialogTitle = ({ title, hasCloseIcon, onClose, allowMinimize, isMinimized, onMinimize, allowMaximize, isMaximized, onMaximize, onRestore }) => {
+ let closeIcon;
+ if (hasCloseIcon !== false) {
+ closeIcon = (
+
+ );
+ }
+
+ let minimizeIcon;
+ if (allowMinimize) {
+ if (isMinimized) {
+ minimizeIcon = (
+
+ );
+ } else {
+ minimizeIcon = (
+
+ );
+ }
+ }
+
+ let maximizeIcon;
+ if (allowMaximize) {
+ if (isMaximized) {
+ maximizeIcon = (
+
+ );
+ } else {
+ maximizeIcon = (
+
+ );
+ }
+ }
+
+ return (
+
+ );
+};
+
+DialogTitle.propTypes = {
+ hasCloseIcon: PropTypes.bool,
+ allowMinimize: PropTypes.bool,
+ allowMaximize: PropTypes.bool,
+ isMinimized: PropTypes.bool,
+ isMaximized: PropTypes.bool,
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
+ onClose: PropTypes.func.isRequired,
+ onMinimize: PropTypes.func,
+ onMaximize: PropTypes.func,
+ onRestore: PropTypes.func
+};
+
+export default DialogTitle;
\ No newline at end of file
diff --git a/src/components/Shared/draggable-dialog/css/img/close.png b/src/components/Shared/draggable-dialog/css/img/close.png
new file mode 100644
index 0000000..c06b094
Binary files /dev/null and b/src/components/Shared/draggable-dialog/css/img/close.png differ
diff --git a/src/components/Shared/draggable-dialog/css/img/maximize.png b/src/components/Shared/draggable-dialog/css/img/maximize.png
new file mode 100644
index 0000000..1749537
Binary files /dev/null and b/src/components/Shared/draggable-dialog/css/img/maximize.png differ
diff --git a/src/components/Shared/draggable-dialog/css/img/minimize.png b/src/components/Shared/draggable-dialog/css/img/minimize.png
new file mode 100644
index 0000000..a763cbb
Binary files /dev/null and b/src/components/Shared/draggable-dialog/css/img/minimize.png differ
diff --git a/src/components/Shared/draggable-dialog/css/img/restore.png b/src/components/Shared/draggable-dialog/css/img/restore.png
new file mode 100644
index 0000000..6823ec3
Binary files /dev/null and b/src/components/Shared/draggable-dialog/css/img/restore.png differ
diff --git a/src/components/Shared/draggable-dialog/css/index.css b/src/components/Shared/draggable-dialog/css/index.css
new file mode 100644
index 0000000..879b079
--- /dev/null
+++ b/src/components/Shared/draggable-dialog/css/index.css
@@ -0,0 +1,150 @@
+body {
+ width: 100%;
+ min-height: 700px;
+}
+
+a {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.ui-dialog-overlay {
+ background: #aaaaaa;
+ opacity: .3;
+ filter: Alpha(Opacity=30);
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 100;
+}
+
+.ui-dialog {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ outline: 0 none;
+ padding: 0 !important;;
+ z-index: 101;
+ background-color: white;
+ border: 1px solid #f6f6f6;
+}
+
+.ui-dialog.maximized{
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100% !important;
+ height: 100% !important;
+}
+
+.ui-dialog.minimized{
+ position: fixed;
+ bottom: 0;
+ right: 0;
+}
+
+.ui-dialog .ui-dialog-titlebar {
+ position: relative;
+ font-size: 1em;
+ border-radius: 3px;
+ padding: 0.5em;
+ height: 35px;
+ border-bottom: 1px solid #f6f6f6;
+}
+
+.ui-dialog.react-draggable .ui-dialog-titlebar {
+ cursor: move;
+}
+
+.ui-dialog .ui-dialog-titlebar .action-items {
+ float: right;
+ position: relative;
+}
+
+.ui-dialog .ui-dialog-titlebar .title {
+ float: left;
+ margin-right: .5em;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 1.5em;
+}
+
+.icon {
+ width: 24px;
+ height: 24px;
+ display: block;
+ float: left;
+ margin: 5px;
+ cursor: pointer;
+ background-size: cover;
+}
+
+.icon.icon-close {
+ width: 20px;
+ height: 20px;
+ background-image: url("./img/close.png");
+}
+
+.icon.icon-minimize {
+ background-image: url("./img/minimize.png");
+}
+
+.icon.icon-maximize {
+ background-image: url("./img/maximize.png");
+}
+
+.icon.icon-restore {
+ background-image: url("./img/restore.png");
+}
+
+.ui-dialog .ui-dialog-content {
+ background: none repeat scroll 0 0 transparent;
+ border: 0 none;
+ overflow: auto;
+ position: relative;
+ padding: 0.5em;
+}
+
+.ui-dialog .ui-dialog-buttonpane {
+ position: absolute;
+ width: 100%;
+ bottom: 0;
+ text-align: right;
+ border-width: 1px 0 0 0;
+ border-top: 1px solid #f6f6f6;
+}
+
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{
+ padding: 0.5em;
+}
+
+.ui-dialog .ui-dialog-buttonpane button {
+ margin: 0 .5em 0 .5em;
+ cursor: pointer;
+ background-color: #f6f6f6;
+ padding: 0.5em 1em;
+ outline: none;
+ border: 1px solid #CCCCCC;
+ border-radius: 3px;
+}
+
+.ui-dialog .ui-dialog-buttonpane button:hover{
+ background-color: #CCCCCC;
+ border: 1px solid #BBBBBB;
+}
+
+.ui-dialog .react-resizable-handle {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ bottom: 0;
+ right: 0;
+ background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+');
+ background-position: bottom right;
+ padding: 0 3px 3px 0;
+ background-repeat: no-repeat;
+ background-origin: content-box;
+ box-sizing: border-box;
+ cursor: se-resize;
+}
\ No newline at end of file
diff --git a/src/components/Shared/draggable-dialog/index.js b/src/components/Shared/draggable-dialog/index.js
new file mode 100644
index 0000000..30a66bd
--- /dev/null
+++ b/src/components/Shared/draggable-dialog/index.js
@@ -0,0 +1,171 @@
+import React from "react";
+import PropTypes from "prop-types";
+import cs from "classnames";
+import Draggable from "react-draggable";
+import { Resizable } from "react-resizable";
+import DialogTitle from "./DialogTitle";
+import DialogBody from "./DialogBody";
+import DialogFooter from "./DialogFooter";
+import EventStack from "active-event-stack";
+
+class Dialog extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ height: props.height,
+ width: props.width,
+ isMinimized: false,
+ isMaximized: false
+ };
+ }
+
+ componentWillMount() {
+ /**
+ * This is done in the componentWillMount instead of the componentDidMount
+ * because this way, a modal that is a child of another will have register
+ * for events after its parent
+ */
+ this.eventToken = EventStack.addListenable([
+ ["keydown", this.handleGlobalKeydown]
+ ]);
+ }
+
+ componentWillUnmount = () => {
+ EventStack.removeListenable(this.eventToken);
+ }
+
+ handleGlobalKeydown = (e) => {
+ if (this.props.closeOnEscape && e.keyCode == 27) {
+ e.stopPropagation();
+ this.onClose();
+ }
+
+ return false;
+ }
+
+ onClose = () => {
+ if (this.props.onClose) {
+ this.props.onClose();
+ }
+ }
+
+ onMinimize = () => {
+ this.setState({ isMinimized: true, isMaximized: false });
+ }
+
+ onMaximize = () => {
+ this.setState({ isMinimized: false, isMaximized: true });
+ }
+
+ onRestore = () => {
+ this.setState({ isMinimized: false, isMaximized: false });
+ }
+
+ onResize = (event, { element, size }) => {
+ this.setState({ width: size.width, height: size.height });
+ }
+
+ getDialogTitle = () => {
+ return (
+
+ );
+ }
+
+ render() {
+ const { height, width, isMinimized, isMaximized } = this.state;
+ const { modal, isDraggable, isResizable, buttons, children, position } = this.props;
+ const { x = -width / 2, y = -height / 2 } = position;
+
+ let dialog = (
+
+ {this.getDialogTitle()}
+ {
+ !isMinimized && {children}
+ }
+ {
+ !isMinimized &&
+ }
+
+
+ );
+
+ if (!isMinimized && !isMaximized && isResizable) {
+ dialog = (
+
+ {dialog}
+
+ );
+ }
+
+ if (!isMinimized && !isMaximized && isDraggable !== false) {
+ dialog = (
+
+ {dialog}
+
+ );
+ }
+
+ return (
+
+ {dialog}
+ {modal &&
}
+
+ );
+ }
+}
+
+Dialog.propTypes = {
+ height: PropTypes.number,
+ width: PropTypes.number,
+ modal: PropTypes.bool,
+ position: PropTypes.shape({
+ x: PropTypes.number,
+ y: PropTypes.number
+ }),
+ hasCloseIcon: PropTypes.bool,
+ allowMinimize: PropTypes.bool,
+ allowMaximize: PropTypes.bool,
+ isResizable: PropTypes.bool,
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
+ closeOnEscape: PropTypes.bool,
+ onClose: PropTypes.func,
+ children: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.element]).isRequired,
+ buttons: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.shape({
+ text: PropTypes.string,
+ onClick: PropTypes.func
+ })),
+ PropTypes.arrayOf(PropTypes.element)
+ ])
+};
+
+Dialog.defaultProps = {
+ height: 300,
+ width: 500,
+ modal: false,
+ closeOnEscape: true,
+ isDraggable: false,
+ isResizable: false,
+ title: '',
+ hasCloseIcon: true,
+ allowMinimize: false,
+ allowMaximize: false,
+ onClose: null,
+ buttons: null,
+ position: { x: -250, y: -150 }
+};
+
+export default Dialog;
\ No newline at end of file