+ );
+};
+
+EmptyState.defaultProps = {
+ children: null,
+ title: null,
+};
+
+EmptyState.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.element),
+ PropTypes.element,
+ PropTypes.string,
+ ]),
+ classes: PropTypes.object.isRequired,
+ icon: PropTypes.object.isRequired,
+ title: PropTypes.string,
+};
+
+export default withStyles(styles, { name: 'EmptyState' })(EmptyState);
diff --git a/src/components/pages/spaces/dialog-add-space/home.js b/src/components/pages/spaces/dialog-add-space/home.js
new file mode 100644
index 00000000..c6cf40a3
--- /dev/null
+++ b/src/components/pages/spaces/dialog-add-space/home.js
@@ -0,0 +1,254 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+import React, { useRef } from 'react';
+import PropTypes from 'prop-types';
+
+import AppSearchAPIConnector from '@elastic/search-ui-app-search-connector';
+import { SearchProvider, WithSearch, Paging } from '@elastic/react-search-ui';
+import '@elastic/react-search-ui-views/lib/styles/styles.css';
+
+import Grid from '@material-ui/core/Grid';
+import Typography from '@material-ui/core/Typography';
+import SearchIcon from '@material-ui/icons/Search';
+import CircularProgress from '@material-ui/core/CircularProgress';
+
+import connectComponent from '../../../../helpers/connect-component';
+
+import AppCard from './app-card';
+import NoConnection from './no-connection';
+import EmptyState from './empty-state';
+import CreateCustomSpaceCard from './create-custom-space-card';
+
+const styles = (theme) => ({
+ paper: {
+ zIndex: 1,
+ },
+ scrollContainer: {
+ flex: 1,
+ padding: 0,
+ overflow: 'auto',
+ },
+ grid: {
+ marginBottom: theme.spacing(1),
+ },
+ homeContainer: {
+ flex: 1,
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ overflow: 'hidden',
+ },
+ badConfigContainer: {
+ flex: 1,
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ overflow: 'hidden',
+ justifyContent: 'center',
+ padding: theme.spacing(1),
+ },
+ contentContainer: {
+ padding: theme.spacing(1),
+ },
+ progressContainer: {
+ position: 'absolute',
+ bottom: 5,
+ left: 5,
+ zIndex: 2,
+ },
+});
+
+const connector = process.env.REACT_APP_ELASTIC_CLOUD_APP_SEARCH_SEARCH_KEY
+ ? new AppSearchAPIConnector({
+ searchKey: process.env.REACT_APP_ELASTIC_CLOUD_APP_SEARCH_SEARCH_KEY,
+ engineName: process.env.REACT_APP_ELASTIC_CLOUD_APP_SEARCH_ENGINE_NAME,
+ endpointBase: process.env.REACT_APP_ELASTIC_CLOUD_APP_SEARCH_API_ENDPOINT,
+ }) : null;
+
+const filters = [
+ { field: 'type', values: ['Multisite'], type: 'all' },
+];
+
+const Home = ({ classes, installedAppIds }) => {
+ const scrollContainerRef = useRef(null);
+
+ if (!connector) {
+ return (
+
+
+ AppSearch environment variables are required for "Catalog". Learn more at: https://github.com/webcatalog/webcatalog-app/blob/master/README.md#development
+
+
+
+ );
+};
+
+Home.defaultProps = {};
+
+Home.propTypes = {
+ classes: PropTypes.object.isRequired,
+ installedAppIds: PropTypes.arrayOf(PropTypes.string).isRequired,
+};
+
+const mapStateToProps = (state) => ({
+ installedAppIds: state.appManagement.sortedAppIds,
+});
+
+export default connectComponent(
+ Home,
+ mapStateToProps,
+ null,
+ styles,
+);
diff --git a/src/components/pages/spaces/dialog-add-space/index.js b/src/components/pages/spaces/dialog-add-space/index.js
new file mode 100644
index 00000000..491bdfa2
--- /dev/null
+++ b/src/components/pages/spaces/dialog-add-space/index.js
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ Dialog,
+ DialogContent,
+ DialogTitle,
+ Typography,
+ IconButton,
+} from '@material-ui/core';
+import CloseIcon from '@material-ui/icons/Close';
+
+import connectComponent from '../../../../helpers/connect-component';
+import { close } from '../../../../state/dialog-add-space/actions';
+
+import Home from './home';
+
+const styles = (theme) => ({
+ closeButton: {
+ position: 'absolute',
+ right: theme.spacing(1),
+ top: theme.spacing(1),
+ color: theme.palette.grey[500],
+ },
+});
+
+const DialogAddSpace = ({ open, onClose, classes }) => (
+
+);
+
+DialogAddSpace.propTypes = {
+ classes: PropTypes.object.isRequired,
+ open: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+};
+
+const mapStateToProps = (state) => ({
+ open: state.dialogAddSpace.open,
+});
+
+const actionCreator = {
+ close,
+};
+
+export default connectComponent(
+ DialogAddSpace,
+ mapStateToProps,
+ actionCreator,
+ styles,
+);
diff --git a/src/components/pages/spaces/dialog-add-space/no-connection.js b/src/components/pages/spaces/dialog-add-space/no-connection.js
new file mode 100755
index 00000000..31122300
--- /dev/null
+++ b/src/components/pages/spaces/dialog-add-space/no-connection.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import Button from '@material-ui/core/Button';
+import ErrorIcon from '@material-ui/icons/Error';
+import Typography from '@material-ui/core/Typography';
+import { withStyles } from '@material-ui/core/styles';
+
+const styles = {
+ root: {
+ alignItems: 'center',
+ display: 'flex',
+ flex: 1,
+ flexDirection: 'column',
+ height: '100%',
+ width: '100%',
+ },
+ icon: {
+ height: 64,
+ width: 64,
+ },
+ tryAgainButton: {
+ marginTop: 16,
+ },
+};
+
+const NoConnection = (props) => {
+ const {
+ classes,
+ onTryAgainButtonClick,
+ } = props;
+
+ return (
+
+
+
+
+ Failed to Connect to Server
+
+
+ Please check your Internet connection.
+
+
+
+ );
+};
+
+NoConnection.propTypes = {
+ classes: PropTypes.object.isRequired,
+ onTryAgainButtonClick: PropTypes.func.isRequired,
+};
+
+export default withStyles(styles, { name: 'NoConnection' })(NoConnection);
diff --git a/src/components/pages/spaces/index.js b/src/components/pages/spaces/index.js
new file mode 100644
index 00000000..fff0d9bf
--- /dev/null
+++ b/src/components/pages/spaces/index.js
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { Typography, Grid } from '@material-ui/core';
+
+import connectComponent from '../../../helpers/connect-component';
+
+import DefinedAppBar from './defined-app-bar';
+import InstalledSpaces from './installed-spaces';
+import AddCard from './add-card';
+import DialogAddSpace from './dialog-add-space';
+
+const styles = (theme) => ({
+ root: {
+ height: '100%',
+ display: 'flex',
+ flexDirection: 'column',
+ overflow: 'hidden',
+ flex: 1,
+ },
+ scrollContainer: {
+ flex: 1,
+ paddingTop: theme.spacing(6),
+ paddingBottom: theme.spacing(6),
+ paddingLeft: theme.spacing(2),
+ paddingRight: theme.spacing(2),
+ overflow: 'auto',
+ boxSizing: 'border-box',
+ },
+ descriptionContainer: {
+ margin: '0 auto',
+ textAlign: 'center',
+ },
+ gridContainer: {
+ paddingTop: theme.spacing(15),
+ },
+});
+
+const Preferences = ({
+ classes,
+}) => (
+
+
+
+
+
+ With WebCatalog Spaces, you can organize your apps into
+ separate distraction-free environments.
+
+ Create spaces for friends and family, or split between work and play.
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+Preferences.propTypes = {
+ classes: PropTypes.object.isRequired,
+};
+
+export default connectComponent(
+ Preferences,
+ null,
+ null,
+ styles,
+);
diff --git a/src/components/pages/spaces/installed-spaces.js b/src/components/pages/spaces/installed-spaces.js
new file mode 100644
index 00000000..3c836bbe
--- /dev/null
+++ b/src/components/pages/spaces/installed-spaces.js
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import connectComponent from '../../../helpers/connect-component';
+import AppCard from '../../shared/app-card';
+
+// only map app obj to each wrapper
+// to avoid re-rendering the whole list (apps changed very often)
+const AppCardWrapper = ({ isSpace, id }) => {
+ if (!isSpace) return null;
+ return (
+
+ );
+};
+
+AppCardWrapper.propTypes = {
+ isSpace: PropTypes.bool.isRequired,
+ id: PropTypes.string.isRequired,
+};
+
+const LinkedAppCardWrapper = connectComponent(
+ AppCardWrapper,
+ (state, ownProps) => ({
+ isSpace: state.appManagement.apps[ownProps.id]
+ && !state.appManagement.apps[ownProps.id].url,
+ }),
+);
+
+const InstalledSpaces = ({ sortedAppIds }) => (
+ <>
+ {sortedAppIds.map((appId) => (
+
+ ))}
+ >
+);
+
+InstalledSpaces.propTypes = {
+ sortedAppIds: PropTypes.arrayOf(PropTypes.string).isRequired,
+};
+
+const mapStateToProps = (state) => ({
+ sortedAppIds: state.appManagement.sortedAppIds,
+});
+
+export default connectComponent(
+ InstalledSpaces,
+ mapStateToProps,
+ null,
+ null,
+);
diff --git a/src/components/root/container.js b/src/components/root/container.js
index 856db069..580eb26b 100644
--- a/src/components/root/container.js
+++ b/src/components/root/container.js
@@ -2,41 +2,27 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/* eslint-disable no-constant-condition */
-import React, { useRef } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
-import AppSearchAPIConnector from '@elastic/search-ui-app-search-connector';
-import {
- SearchProvider,
-} from '@elastic/react-search-ui';
-import '@elastic/react-search-ui-views/lib/styles/styles.css';
-
import Grid from '@material-ui/core/Grid';
-import Typography from '@material-ui/core/Typography';
import connectComponent from '../../helpers/connect-component';
import {
- ROUTE_CATEGORIES,
ROUTE_HOME,
ROUTE_INSTALLED,
ROUTE_PREFERENCES,
+ ROUTE_SPACES,
} from '../../constants/routes';
import Preferences from '../pages/preferences';
import Installed from '../pages/installed';
-import Categories from '../pages/categories';
import Home from '../pages/home';
+import Spaces from '../pages/spaces';
import Sidebar from './sidebar';
-const connector = process.env.REACT_APP_ELASTIC_CLOUD_APP_SEARCH_SEARCH_KEY
- ? new AppSearchAPIConnector({
- searchKey: process.env.REACT_APP_ELASTIC_CLOUD_APP_SEARCH_SEARCH_KEY,
- engineName: process.env.REACT_APP_ELASTIC_CLOUD_APP_SEARCH_ENGINE_NAME,
- endpointBase: process.env.REACT_APP_ELASTIC_CLOUD_APP_SEARCH_API_ENDPOINT,
- }) : null;
-
const styles = (theme) => ({
root: {
height: '100%',
@@ -74,87 +60,20 @@ const styles = (theme) => ({
},
});
-const filters = [];
-
const Container = ({
classes,
route,
-}) => {
- const scrollContainerRef = useRef(null);
-
- if (!connector) {
- return (
-
-
- Elastic Cloud App Search environment variables are required for "Discover". Learn more at: https://github.com/webcatalog/webcatalog-app/blob/master/README.md#development
-
-