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,76 @@
+// @flow
+
+import { faCheckCircle } from '@fortawesome/free-regular-svg-icons';
+import { faPlusCircle } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+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 enableApp = React.useCallback(
+ () => dispatch({ type: enableAppActionType, payload: id }),
+ [dispatch, id],
+ );
+
+ const disableApp = React.useCallback(() => {
+ dispatch({ type: disableAppActionType, payload: id });
+ }, [dispatch, id]);
+
+ const actionButton = React.useMemo(() => {
+ const switchIcon = enabled ? faCheckCircle : faPlusCircle;
+ if (readOnly) {
+ return (
+
+
+
+ );
+ }
+ const color = enabled ? '#00c853' : '#ebebeb';
+ return (
+
+
+
+ );
+ }, [readOnly, disableApp, enableApp, enabled]);
+ return (
+
+
+
+
+
+ {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, ...result } = {
+ ...app,
+ enabled: enabledApps[app.id] ?? app.defaultEnabled,
+ };
+ return result;
+ }),
+ [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,57 @@
+div.appsDirectoryContainer {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+div.appsHeader {
+ color: var(--fg);
+ font-size: var(--xl-font-20);
+ margin: 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: white;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+div.appListingTextContainer {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+}
+
+div.appName {
+ font-size: var(--l-font-18);
+ font-weight: var(--semi-bold);
+ margin-bottom: 4px;
+}
+
+div.appCopy {
+ font-size: var(--xs-font-12);
+}
+
+div.appListingIcon {
+ padding: 0 20px;
+ align-self: stretch;
+ display: flex;
+ align-items: center;
+}
+
+div.appListingIconState {
+ padding: 0 20px;
+ align-self: stretch;
+ display: flex;
+ align-items: center;
+ font-size: var(--xl-font-20);
+}