diff --git a/web/apps/app-listing.react.js b/web/apps/app-listing.react.js
new file mode 100644
--- /dev/null
+++ b/web/apps/app-listing.react.js
@@ -0,0 +1,83 @@
+// @flow
+
+import { faCheckCircle } from '@fortawesome/free-regular-svg-icons';
+import { faPlusCircle } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import classnames from 'classnames';
+import * as React from 'react';
+import { useDispatch } from 'react-redux';
+
+import {
+ disableAppActionType,
+ enableAppActionType,
+} from 'lib/reducers/enabled-apps-reducer';
+import type { SupportedApps } from 'lib/types/enabled-apps';
+
+import SWMansionIcon from '../SWMansionIcon.react';
+import css from './apps.css';
+
+type Props = {
+ +id: SupportedApps | 'chat',
+ +readOnly: boolean,
+ +enabled: boolean,
+ +name: string,
+ +icon: 'message-square' | 'calendar',
+ +copy: string,
+};
+
+function AppListing(props: Props): React.Node {
+ const { id, readOnly, enabled, name, icon, copy } = props;
+ const dispatch = useDispatch();
+
+ const switchAppState = React.useCallback(
+ () =>
+ dispatch({
+ type: enabled ? disableAppActionType : enableAppActionType,
+ payload: id,
+ }),
+ [dispatch, enabled, id],
+ );
+
+ const actionButton = React.useMemo(() => {
+ const switchIcon = enabled ? faCheckCircle : faPlusCircle;
+ if (readOnly) {
+ const readOnlyIconClasses = classnames(
+ css.appListingIcon,
+ css.appListingIconState,
+ css.iconReadOnly,
+ );
+ return (
+
+
+
+ );
+ }
+ const iconClasses = classnames(
+ css.appListingIcon,
+ css.appListingIconState,
+ {
+ [css.iconEnabled]: enabled,
+ [css.iconDisabled]: !enabled,
+ },
+ );
+ return (
+
+
+
+ );
+ }, [enabled, readOnly, switchAppState]);
+ return (
+
+
+
+
+
+
{name}
+ {copy}
+
+ {actionButton}
+
+ );
+}
+
+export default AppListing;
diff --git a/web/apps/apps-directory.react.js b/web/apps/apps-directory.react.js
--- a/web/apps/apps-directory.react.js
+++ b/web/apps/apps-directory.react.js
@@ -1,9 +1,56 @@
// @flow
import * as React from 'react';
+import { useSelector } from 'react-redux';
+
+import AppListing from './app-listing.react';
+import css from './apps.css';
+
+const APP_DIRECTORY_DATA = [
+ {
+ id: 'chat',
+ defaultEnabled: true,
+ readOnly: true,
+ name: 'Chat',
+ icon: 'message-square',
+ copy: 'Keep in touch with your community',
+ },
+ {
+ id: 'calendar',
+ defaultEnabled: true,
+ readOnly: false,
+ name: 'Calendar',
+ icon: 'calendar',
+ copy: 'Shared calendar for your community',
+ },
+];
function AppsDirectory(): React.Node {
- return Apps directory
;
+ const enabledApps = useSelector(state => state.enabledApps);
+
+ const appData = React.useMemo(
+ () =>
+ APP_DIRECTORY_DATA.map(app => {
+ const { defaultEnabled, ...data } = app;
+ return {
+ ...data,
+ enabled: enabledApps[app.id] ?? defaultEnabled,
+ };
+ }),
+ [enabledApps],
+ );
+
+ const appItems = React.useMemo(
+ () => appData.map(item => ),
+ [appData],
+ );
+
+ return (
+
+
Choose Apps
+
{appItems}
+
+ );
}
export default AppsDirectory;
diff --git a/web/apps/apps.css b/web/apps/apps.css
new file mode 100644
--- /dev/null
+++ b/web/apps/apps.css
@@ -0,0 +1,63 @@
+div.appsDirectoryContainer {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+h4.appsHeader {
+ color: var(--fg);
+ padding: 20px 0px 40px 40px;
+ font-weight: var(--semi-bold);
+}
+
+div.appsDirectoryList {
+ margin-left: 20px;
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+}
+
+div.appListingContainer {
+ color: var(--fg);
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+div.appListingTextContainer {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+}
+
+h5.appName {
+ font-weight: var(--semi-bold);
+ margin-bottom: 4px;
+}
+
+small.appCopy {
+ font-size: var(--xs-font-12);
+}
+
+div.appListingIcon {
+ padding: 0 20px;
+ align-self: stretch;
+ display: flex;
+ align-items: center;
+}
+
+div.appListingIconState {
+ font-size: var(--xl-font-20);
+}
+
+div.iconReadOnly {
+ color: var(--app-list-icon-read-only-color);
+}
+
+div.iconEnabled {
+ color: var(--app-list-icon-enabled-color);
+}
+
+div.iconDisabled {
+ color: var(--app-list-icon-disabled-color);
+}
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -108,4 +108,7 @@
--thread-menu-color-hover: var(--shades-white-100);
--thread-menu-color-dangerous: var(--error-primary);
--thread-menu-color-dangerous-hover: var(--error-light-50);
+ --app-list-icon-read-only-color: var(--shades-black-60);
+ --app-list-icon-enabled-color: var(--success-primary);
+ --app-list-icon-disabled-color: var(--shades-white-80);
}