From 88de231f015a298f5b1191181def8b32de5d1853 Mon Sep 17 00:00:00 2001
From: Adam Whitlock <8332986+adamwhitlock1@users.noreply.github.com>
Date: Tue, 17 Dec 2024 14:49:44 -0700
Subject: [PATCH] Aedp/aw/255/vadx panel improvements and testing (#33559)
* updates for panel tabs
* kb shortcut for panel, form data viewer, logged in button, better toggle reset, cleaned up styling
* test: unit tests for ChapterAnalyzer tool, update propTypes
---
.../hooks/useLocalStorage.js | 15 +-
.../hooks/useMockedLogin.js | 30 ++-
.../endpoints/in-progress-forms/22-1990.js | 2 +-
.../endpoints/in-progress-forms/26-1880.js | 2 +-
.../mocks/server.js | 4 +-
.../TaskBlue/ProfileInformationEditView.jsx | 2 +-
.../TaskGray/shared/components/WIP.jsx | 2 +-
.../shared/components/VADXPlugin.jsx | 43 +++-
.../vadx/context/vadx.js | 37 +---
.../vadx/panel/FloatingButton.jsx | 22 +-
.../vadx/panel/Panel.jsx | 13 +-
.../vadx/panel/tabs/FormTab.jsx | 59 ------
.../vadx/panel/tabs/OtherTab.jsx | 22 --
.../vadx/panel/{ => tabs}/Tabs.jsx | 8 +-
.../vadx/panel/tabs/TogglesTab.jsx | 127 -----------
.../vadx/panel/tabs/form/ChapterAnalyzer.jsx | 139 ++++++++++++
.../tabs/form/ChapterAnalyzer.unit.spec.js | 197 ++++++++++++++++++
.../vadx/panel/tabs/form/FormDataViewer.jsx | 115 ++++++++++
.../vadx/panel/tabs/form/FormTab.jsx | 72 +++++++
.../vadx/panel/tabs/other/ActiveElement.jsx | 21 ++
.../other}/HeadingHierarchyInspector.jsx | 12 +-
.../vadx/panel/tabs/other/LoggedInState.jsx | 19 ++
.../vadx/panel/tabs/other/MemoryUsage.jsx | 36 ++++
.../vadx/panel/tabs/other/OtherTab.jsx | 17 ++
.../vadx/panel/tabs/toggles/TogglesTab.jsx | 180 ++++++++++++++++
.../site-wide/feature-toggles/actionTypes.js | 1 +
.../feature-toggles/reducers/index.js | 3 +
27 files changed, 924 insertions(+), 276 deletions(-)
delete mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/FormTab.jsx
delete mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/OtherTab.jsx
rename src/applications/_mock-form-ae-design-patterns/vadx/panel/{ => tabs}/Tabs.jsx (91%)
delete mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/TogglesTab.jsx
create mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/ChapterAnalyzer.jsx
create mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/ChapterAnalyzer.unit.spec.js
create mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormDataViewer.jsx
create mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormTab.jsx
create mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/ActiveElement.jsx
rename src/applications/_mock-form-ae-design-patterns/vadx/panel/{ => tabs/other}/HeadingHierarchyInspector.jsx (91%)
create mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/LoggedInState.jsx
create mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/MemoryUsage.jsx
create mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/OtherTab.jsx
create mode 100644 src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/toggles/TogglesTab.jsx
diff --git a/src/applications/_mock-form-ae-design-patterns/hooks/useLocalStorage.js b/src/applications/_mock-form-ae-design-patterns/hooks/useLocalStorage.js
index 810859636ea8..fd86dbbfa505 100644
--- a/src/applications/_mock-form-ae-design-patterns/hooks/useLocalStorage.js
+++ b/src/applications/_mock-form-ae-design-patterns/hooks/useLocalStorage.js
@@ -1,5 +1,4 @@
import { useState, useEffect } from 'react';
-
/**
* useLocalStorage is a hook that provides a way to store and retrieve values from localStorage
* @param {string} key - The key to store the value under
@@ -11,10 +10,17 @@ export const useLocalStorage = (key, defaultValue) => {
let currentValue;
try {
- currentValue = JSON.parse(
- localStorage.getItem(key) || String(defaultValue),
- );
+ const item = localStorage.getItem(key);
+ if (item === null) {
+ currentValue = defaultValue;
+ } else if (item.startsWith('{') || item.startsWith('[')) {
+ currentValue = JSON.parse(item);
+ } else {
+ currentValue = item;
+ }
} catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('Error getting value from localStorage', error);
currentValue = defaultValue;
}
@@ -30,6 +36,7 @@ export const useLocalStorage = (key, defaultValue) => {
const clearValue = () => {
localStorage.removeItem(key);
+ setValue(defaultValue);
};
return [value, setValue, clearValue];
diff --git a/src/applications/_mock-form-ae-design-patterns/hooks/useMockedLogin.js b/src/applications/_mock-form-ae-design-patterns/hooks/useMockedLogin.js
index db016ed8a8e9..6c0c6cfff355 100644
--- a/src/applications/_mock-form-ae-design-patterns/hooks/useMockedLogin.js
+++ b/src/applications/_mock-form-ae-design-patterns/hooks/useMockedLogin.js
@@ -1,37 +1,51 @@
-import { useEffect } from 'react';
-import { useDispatch } from 'react-redux';
+import { useEffect, useMemo } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
import { teardownProfileSession } from 'platform/user/profile/utilities';
import { updateLoggedInStatus } from 'platform/user/authentication/actions';
import { useLocalStorage } from './useLocalStorage';
export const useMockedLogin = () => {
- const [, setHasSession] = useLocalStorage('hasSession', '');
+ const [
+ localHasSession,
+ setLocalHasSession,
+ clearLocalHasSession,
+ ] = useLocalStorage('hasSession', '');
+
+ const loggedInFromState = useSelector(
+ state => state?.user?.login?.currentlyLoggedIn,
+ );
+
+ const loggedIn = useMemo(
+ () => localHasSession === 'true' || loggedInFromState,
+ [localHasSession, loggedInFromState],
+ );
+
const dispatch = useDispatch();
const logIn = () => {
- setHasSession('true');
+ setLocalHasSession('true');
dispatch(updateLoggedInStatus(true));
};
const logOut = () => {
teardownProfileSession();
dispatch(updateLoggedInStatus(false));
- setHasSession('');
+ clearLocalHasSession();
};
const useLoggedInQuery = location => {
useEffect(
() => {
if (location?.query?.loggedIn === 'true') {
- setHasSession('true');
+ setLocalHasSession('true');
dispatch(updateLoggedInStatus(true));
}
if (location?.query?.loggedIn === 'false') {
teardownProfileSession();
dispatch(updateLoggedInStatus(false));
- setHasSession('');
+ clearLocalHasSession();
}
// having the pollTimeout present triggers some api calls to be made locally and in codespaces
@@ -43,5 +57,5 @@ export const useMockedLogin = () => {
);
};
- return { logIn, logOut, useLoggedInQuery };
+ return { logIn, logOut, useLoggedInQuery, loggedIn };
};
diff --git a/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/in-progress-forms/22-1990.js b/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/in-progress-forms/22-1990.js
index 3ba89f4f564f..ed27973d8926 100644
--- a/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/in-progress-forms/22-1990.js
+++ b/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/in-progress-forms/22-1990.js
@@ -18,7 +18,7 @@ const response = {
street: USER.MAILING_ADDRESS.ADDRESS_LINE1,
city: USER.MAILING_ADDRESS.CITY,
state: USER.MAILING_ADDRESS.STATE_CODE,
- country: USER.MAILING_ADDRESS.COUNTRY_CODE_ISO2,
+ country: USER.MAILING_ADDRESS.COUNTRY_CODE_ISO3,
postalCode: USER.MAILING_ADDRESS.ZIP_CODE,
isMilitary: false,
},
diff --git a/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/in-progress-forms/26-1880.js b/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/in-progress-forms/26-1880.js
index 51d26fb2e097..e9f49c9e0356 100644
--- a/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/in-progress-forms/26-1880.js
+++ b/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/in-progress-forms/26-1880.js
@@ -8,7 +8,7 @@ const response = {
street: USER.MAILING_ADDRESS.ADDRESS_LINE1,
city: USER.MAILING_ADDRESS.CITY,
state: USER.MAILING_ADDRESS.STATE_CODE,
- country: USER.MAILING_ADDRESS.COUNTRY_CODE_ISO2,
+ country: USER.MAILING_ADDRESS.COUNTRY_CODE_ISO3,
postalCode: USER.MAILING_ADDRESS.ZIP_CODE,
},
contactPhone: `${USER.HOME_PHONE.AREA_CODE}${USER.HOME_PHONE.PHONE_NUMBER}`,
diff --git a/src/applications/_mock-form-ae-design-patterns/mocks/server.js b/src/applications/_mock-form-ae-design-patterns/mocks/server.js
index 673b05df5270..d2f138bfe2bc 100644
--- a/src/applications/_mock-form-ae-design-patterns/mocks/server.js
+++ b/src/applications/_mock-form-ae-design-patterns/mocks/server.js
@@ -47,8 +47,8 @@ const responses = {
res.json(
generateFeatureToggles({
aedpVADX: true,
- coeAccess: true,
- profileUseExperimental: true,
+ coeAccess: false,
+ profileUseExperimental: false,
}),
),
secondsOfDelay,
diff --git a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/ProfileInformationEditView.jsx b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/ProfileInformationEditView.jsx
index 88b5281d5a73..edfa1bc9a2c4 100644
--- a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/ProfileInformationEditView.jsx
+++ b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/ProfileInformationEditView.jsx
@@ -346,7 +346,7 @@ export class ProfileInformationEditView extends Component {
diff --git a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/shared/components/WIP.jsx b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/shared/components/WIP.jsx
index 3ed42a296e1c..daec3c6743c6 100644
--- a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/shared/components/WIP.jsx
+++ b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/shared/components/WIP.jsx
@@ -1,7 +1,7 @@
import React from 'react';
export const WIP = () => (
-
+
diff --git a/src/applications/_mock-form-ae-design-patterns/shared/components/VADXPlugin.jsx b/src/applications/_mock-form-ae-design-patterns/shared/components/VADXPlugin.jsx
index 4340475aac92..6fd29e4105bb 100644
--- a/src/applications/_mock-form-ae-design-patterns/shared/components/VADXPlugin.jsx
+++ b/src/applications/_mock-form-ae-design-patterns/shared/components/VADXPlugin.jsx
@@ -4,13 +4,42 @@ import { Link } from 'react-router';
export const VADXPlugin = () => {
return (
-
VADX Plugin
-
- Pattern 1
-
-
- Pattern 2
-
+
VADX Plugin Example
+
+
Pattern 1
+
+ -
+ Task Green
+
+ -
+
+ Task Yellow
+
+
+ -
+
+ Task Purple
+
+
+
+
+
+
Pattern 2
+
+ -
+ Task Orange
+
+ -
+ Task Gray
+
+ -
+ Task Blue
+
+ -
+ Post Study
+
+
+
);
};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/context/vadx.js b/src/applications/_mock-form-ae-design-patterns/vadx/context/vadx.js
index ddf30b708062..98bffcc69624 100644
--- a/src/applications/_mock-form-ae-design-patterns/vadx/context/vadx.js
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/context/vadx.js
@@ -7,7 +7,7 @@ import React, {
} from 'react';
import PropTypes from 'prop-types';
-import { debounce, isEqual } from 'lodash';
+import { debounce } from 'lodash';
import { useDispatch, useSelector } from 'react-redux';
import { useMockedLogin } from '../../hooks/useMockedLogin';
import { vadxPreferencesStorage } from '../utils/StorageAdapter';
@@ -22,10 +22,9 @@ import { setVadxToggles } from '../actions/toggles';
export const VADXContext = createContext(null);
/**
- * @param {Object} props
+ * @component VADXProvider
* @param {React.ReactNode} props.children
* @returns {React.ReactNode}
- * @component
*/
export const VADXProvider = ({ children }) => {
const [preferences, setPreferences] = useState({});
@@ -40,7 +39,7 @@ export const VADXProvider = ({ children }) => {
);
// mock login functions
- const { logIn, logOut } = useMockedLogin();
+ const { logIn, logOut, loggedIn } = useMockedLogin();
useEffect(
() => {
@@ -120,10 +119,14 @@ export const VADXProvider = ({ children }) => {
// update local toggles
const updateLocalToggles = useCallback(
- toggles => {
- setSyncedData({ ...preferences, localToggles: toggles });
+ async toggles => {
+ await setSyncedData({
+ ...preferences,
+ localToggles: { ...toggles },
+ });
+ dispatch(setVadxToggles(toggles));
},
- [preferences, setSyncedData],
+ [preferences, setSyncedData, dispatch],
);
// clear local toggles
@@ -142,30 +145,12 @@ export const VADXProvider = ({ children }) => {
const togglesState = useSelector(state => state?.featureToggles);
- const localTogglesAreEmpty = useMemo(
- () => isEqual(preferences?.localToggles, {}),
- [preferences?.localToggles],
- );
-
- // check if the local toggles are not empty and not equal to the toggles state
- const customLocalToggles =
- !localTogglesAreEmpty && !isEqual(preferences?.localToggles, togglesState);
-
- // if the custom local toggles are true, then update the redux state for the toggles
- useEffect(
- () => {
- if (customLocalToggles) {
- dispatch(setVadxToggles(preferences?.localToggles));
- }
- },
- [customLocalToggles, preferences?.localToggles, dispatch],
- );
-
return (
{
+ useEffect(
+ () => {
+ const handleKeyPress = event => {
+ // Check for Ctrl/Cmd + Shift + /
+ if (
+ (event.ctrlKey || event.metaKey) &&
+ event.shiftKey &&
+ event.key === '\\'
+ ) {
+ event.preventDefault();
+ setShowVADX(!showVADX);
+ }
+ };
+
+ document.addEventListener('keydown', handleKeyPress);
+ return () => document.removeEventListener('keydown', handleKeyPress);
+ },
+ [showVADX, setShowVADX],
+ );
+
return (
setShowVADX(!showVADX)} type="button">
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/Panel.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/Panel.jsx
index f5c6b45c66aa..f55bacd241bc 100644
--- a/src/applications/_mock-form-ae-design-patterns/vadx/panel/Panel.jsx
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/Panel.jsx
@@ -1,9 +1,9 @@
-import React, { useContext } from 'react';
+import React, { useCallback, useContext } from 'react';
import { PluginContext } from '../context/plugin';
import { VADXContext } from '../context/vadx';
-import Tabs from './Tabs';
+import Tabs from './tabs/Tabs';
import { FloatingButton } from './FloatingButton';
const VADXContainer = () => {
@@ -12,9 +12,12 @@ const VADXContainer = () => {
const showVADX = !!preferences?.showVADX;
- const handleShowVADX = () => {
- updateShowVADX(!showVADX);
- };
+ const handleShowVADX = useCallback(
+ () => {
+ updateShowVADX(!showVADX);
+ },
+ [showVADX, updateShowVADX],
+ );
return (
<>
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/FormTab.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/FormTab.jsx
deleted file mode 100644
index abb146c88791..000000000000
--- a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/FormTab.jsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-import { Link, withRouter } from 'react-router';
-
-const getFormInfo = router => {
- const childRoutes = router?.routes.map?.(route => {
- if (route.childRoutes && route.childRoutes.length > 0) {
- return route.childRoutes?.[0];
- }
- return null;
- });
-
- // we are assuming the first route with config is the same for all routes
- const firstRouteWithConfig = childRoutes.find(route => {
- return route?.formConfig;
- }, {});
-
- const pageListFormatted = firstRouteWithConfig?.pageList?.map(page => {
- return {
- path: page?.path,
- title: page?.title,
- pageKey: page?.pageKey,
- isActive: router?.location?.pathname === page?.path,
- };
- });
-
- return {
- pageList: pageListFormatted,
- formConfig: firstRouteWithConfig?.formConfig,
- };
-};
-
-const FormTabBase = props => {
- const formInfo = getFormInfo(props.router);
-
- return (
-
- {formInfo?.pageList ? (
- formInfo.pageList.map(page => (
- -
-
- {page.title || page.pageKey || page.path}
-
-
- ))
- ) : (
- - No form system pages found
- )}
-
- );
-};
-
-export const FormTab = withRouter(FormTabBase);
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/OtherTab.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/OtherTab.jsx
deleted file mode 100644
index dc303f130005..000000000000
--- a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/OtherTab.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-
-import { useFocusedElement } from '../../hooks/useFocusedElement';
-import { HeadingHierarchyInspector } from '../HeadingHierarchyInspector';
-
-export const OtherTab = () => {
- const { displayString, onMouseEnter, onMouseLeave } = useFocusedElement();
- return (
-
-
-
- {displayString || 'No element focused'}
-
-
-
-
- );
-};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/Tabs.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/Tabs.jsx
similarity index 91%
rename from src/applications/_mock-form-ae-design-patterns/vadx/panel/Tabs.jsx
rename to src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/Tabs.jsx
index 681ea5106bac..a5bf58709ea0 100644
--- a/src/applications/_mock-form-ae-design-patterns/vadx/panel/Tabs.jsx
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/Tabs.jsx
@@ -1,10 +1,10 @@
import React, { useContext } from 'react';
import { VaButton } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import styled from 'styled-components';
-import { TogglesTab } from './tabs/TogglesTab';
-import { FormTab } from './tabs/FormTab';
-import { OtherTab } from './tabs/OtherTab';
-import { VADXContext } from '../context/vadx';
+import { TogglesTab } from './toggles/TogglesTab';
+import { FormTab } from './form/FormTab';
+import { OtherTab } from './other/OtherTab';
+import { VADXContext } from '../../context/vadx';
const VADXPanelDiv = styled.div`
background-color: var(--vads-color-white);
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/TogglesTab.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/TogglesTab.jsx
deleted file mode 100644
index adcd9a240747..000000000000
--- a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/TogglesTab.jsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import {
- VaButton,
- VaCheckbox,
- VaSearchInput,
-} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
-import React, { useContext } from 'react';
-import environment from '~/platform/utilities/environment';
-import LoadingButton from 'platform/site-wide/loading-button/LoadingButton';
-import { VADXContext } from '../../context/vadx';
-
-export const TogglesTab = () => {
- const {
- preferences,
- updateDevLoading,
- updateLocalToggles,
- updateClearLocalToggles,
- debouncedSetSearchQuery,
- togglesLoading,
- togglesState,
- } = useContext(VADXContext);
-
- const { searchQuery, isDevLoading } = preferences;
-
- if (togglesLoading) {
- return Loading...
;
- }
-
- const fetchDevToggles = async () => {
- try {
- updateDevLoading(true);
-
- const response = await fetch(
- 'https://staging-api.va.gov/v0/feature_toggles',
- );
- const {
- data: { features },
- } = await response.json();
-
- const formattedToggles = features
- .filter(toggle => toggle.name.includes('_'))
- .reduce((acc, toggle) => {
- acc[toggle.name] = toggle.value;
- return acc;
- }, {});
-
- updateLocalToggles(formattedToggles);
-
- updateDevLoading(false);
- } catch (error) {
- // eslint-disable-next-line no-console
- console.error('Error fetching dev toggles:', error);
- }
- };
-
- const filteredToggles = Object.keys(togglesState).filter(toggle => {
- const toggleName = toggle?.toLowerCase?.() || '';
- const searchQueryLower = searchQuery?.toLowerCase?.() || '';
- return toggleName.includes(searchQueryLower) && toggle !== 'loading';
- });
-
- return (
- <>
- debouncedSetSearchQuery(e.target.value)}
- onSubmit={e => debouncedSetSearchQuery(e.target.value)}
- small
- value={searchQuery}
- />
-
- {
- updateClearLocalToggles();
- window.location.reload();
- }}
- text="Reset Toggles"
- primary
- />
-
- {isDevLoading ? (
-
- ) : (
-
- )}
-
- {filteredToggles?.length} toggles
-
-
-
- {Object.keys(togglesState).length < 2 && (
-
-
-
- No toggles found.
-
-
- Is your api running at {environment.API_URL}?
-
-
- )}
-
- {filteredToggles.map(toggle => {
- return (
-
- {
- const updatedToggles = {
- ...togglesState,
- [toggle]: e.target.checked,
- };
- updateLocalToggles(updatedToggles);
- }}
- />
-
- );
- })}
- >
- );
-};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/ChapterAnalyzer.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/ChapterAnalyzer.jsx
new file mode 100644
index 000000000000..8bab909996b5
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/ChapterAnalyzer.jsx
@@ -0,0 +1,139 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link } from 'react-router';
+import { useSelector } from 'react-redux';
+
+const isReactComponent = component => {
+ return (
+ component &&
+ (typeof component === 'function' ||
+ (component.$$typeof &&
+ component.$$typeof.toString().includes('Symbol(react')))
+ );
+};
+
+const WarningBadge = ({ children }) => (
+
+ {children}
+
+);
+
+const PageAnalysis = ({ page, pageName, urlPrefix }) => {
+ const formData = useSelector(state => state?.form?.data || {});
+ const hasCustomPage = isReactComponent(page?.CustomPage);
+ const hasCustomPageReview = isReactComponent(page?.CustomPageReview);
+ const hasUiSchema = page?.uiSchema && Object.keys(page?.uiSchema).length > 0;
+ const showWarning = (hasCustomPage || hasCustomPageReview) && hasUiSchema;
+
+ const depends = page?.depends;
+ const hasDepends = !!depends;
+ const isVisible = !!(
+ depends &&
+ typeof depends === 'function' &&
+ depends(formData)
+ );
+
+ const path = `${urlPrefix}${page.path}`;
+
+ return (
+
+
+
+
+ {page.title ? page.title : `${pageName}: no title`}
+
+
+
+ {hasDepends &&
+ (isVisible ? (
+
+ ) : (
+
+ ))}
+ {(hasCustomPage || hasCustomPageReview || showWarning) && (
+ <>
+ {hasCustomPage && (
+
+ [CustomPage
+ {hasCustomPageReview && '+Review'}]
+
+ )}
+
+ {showWarning && (
+ Warning: uiSchema may be ignored
+ )}
+ >
+ )}
+
+
+ );
+};
+
+const ChapterAnalyzer = ({ formConfig, urlPrefix }) => {
+ if (!formConfig?.chapters) {
+ return (
+
+ No chapters or form config found
+
+ );
+ }
+
+ return (
+
+
Form Structure
+
+ {Object.entries(formConfig.chapters).map(([chapterKey, chapter]) => (
+
+
+ {chapter?.title || chapterKey}
+
+
+ {chapter?.pages &&
+ Object.entries(chapter.pages).map(([pageKey, page]) => (
+
+ ))}
+
+
+ ))}
+
+
+ );
+};
+
+ChapterAnalyzer.propTypes = {
+ formConfig: PropTypes.shape({
+ chapters: PropTypes.object.isRequired,
+ }),
+ urlPrefix: PropTypes.string,
+};
+
+PageAnalysis.propTypes = {
+ page: PropTypes.shape({
+ title: PropTypes.string,
+ path: PropTypes.string,
+ depends: PropTypes.func,
+ CustomPage: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
+ CustomPageReview: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
+ uiSchema: PropTypes.object,
+ }).isRequired,
+ pageName: PropTypes.string.isRequired,
+ urlPrefix: PropTypes.string,
+};
+
+WarningBadge.propTypes = {
+ children: PropTypes.node.isRequired,
+};
+
+export default ChapterAnalyzer;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/ChapterAnalyzer.unit.spec.js b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/ChapterAnalyzer.unit.spec.js
new file mode 100644
index 000000000000..b1152846df5f
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/ChapterAnalyzer.unit.spec.js
@@ -0,0 +1,197 @@
+import React from 'react';
+import { expect } from 'chai';
+import { renderWithStoreAndRouter } from 'platform/testing/unit/react-testing-library-helpers';
+import ChapterAnalyzer from './ChapterAnalyzer';
+
+const initialState = {
+ form: {
+ data: {
+ someField: 'value',
+ },
+ },
+};
+
+const path = '/form/name-birth';
+
+const reducers = {
+ form: (state = initialState) => state,
+};
+
+describe('ChapterAnalyzer', () => {
+ describe('when no chapters exist on formConfig', () => {
+ it('should display no chapters found message', () => {
+ const { getByText } = renderWithStoreAndRouter(
+ ,
+ { initialState, path, history: null, reducers },
+ );
+
+ expect(getByText('No chapters or form config found')).to.exist;
+ });
+ });
+
+ describe('with valid form config', () => {
+ let formConfig;
+
+ beforeEach(() => {
+ formConfig = {
+ chapters: {
+ personalInfo: {
+ title: 'Personal Information',
+ pages: {
+ nameAndBirth: {
+ title: 'Name and Birth Date',
+ path: 'name-birth',
+ depends: () => true,
+ },
+ contactInfo: {
+ title: 'Contact Information',
+ path: 'contact',
+ CustomPage: () => null,
+ uiSchema: { 'ui:title': 'Contact' },
+ },
+ },
+ },
+ },
+ };
+ });
+
+ it('should render chapter titles', () => {
+ const { getByText } = renderWithStoreAndRouter(
+ ,
+ { initialState, path, history: null, reducers },
+ );
+
+ expect(getByText('Personal Information')).to.exist;
+ });
+
+ it('should render page links', () => {
+ const { getByText } = renderWithStoreAndRouter(
+ ,
+ { initialState, path, history: null, reducers },
+ );
+
+ const pageTitleLink = getByText('Name and Birth Date').closest('a');
+
+ expect(pageTitleLink, 'Link element exists').to.not.be.null;
+ expect(pageTitleLink, 'Link is an anchor tag').to.have.property(
+ 'tagName',
+ 'A',
+ );
+ });
+
+ it('should show warning badge when CustomPage and uiSchema exist together', () => {
+ const { getByText } = renderWithStoreAndRouter(
+ ,
+ { initialState, path, history: null, reducers },
+ );
+
+ expect(getByText('Warning: uiSchema may be ignored')).to.exist;
+ });
+
+ it('should show visibility indicator for pages with depends function', () => {
+ const { container } = renderWithStoreAndRouter(
+ ,
+ { initialState, path, history: null, reducers },
+ );
+
+ // Check for visibility icon
+ // have to use container.querySelector because
+ // visibility icon is web component
+ const visibilityIcon = container.querySelector(
+ 'va-icon[icon="visibility"]',
+ );
+ expect(visibilityIcon).to.exist;
+ });
+
+ it('should show visibility_off indicator for pages with depends function that return false', () => {
+ formConfig.chapters.personalInfo.pages.nameAndBirth.depends = () => false;
+
+ const { container } = renderWithStoreAndRouter(
+ ,
+ { initialState, path, history: null, reducers },
+ );
+
+ const visibilityOffIcon = container.querySelector(
+ 'va-icon[icon="visibility_off"]',
+ );
+ expect(visibilityOffIcon).to.exist;
+ });
+
+ it('should handle pages without titles', () => {
+ const configWithoutTitle = {
+ chapters: {
+ chapter1: {
+ pages: {
+ page1: {
+ path: '/path',
+ },
+ },
+ },
+ },
+ };
+
+ const { getByText } = renderWithStoreAndRouter(
+ ,
+ { initialState, path, history: null, reducers },
+ );
+
+ expect(getByText('page1: no title')).to.exist;
+ });
+
+ it('should show warning badge when CustomPage and uiSchema exist together', () => {
+ const { getByText } = renderWithStoreAndRouter(
+ ,
+ { initialState, path, history: null, reducers },
+ );
+
+ expect(getByText('Warning: uiSchema may be ignored')).to.exist;
+ });
+ });
+
+ describe('CustomPage indicators', () => {
+ it('should show CustomPage indicator', () => {
+ const config = {
+ chapters: {
+ chapter1: {
+ pages: {
+ page1: {
+ title: 'Test Page',
+ CustomPage: () => null,
+ },
+ },
+ },
+ },
+ };
+
+ const { getByText } = renderWithStoreAndRouter(
+ ,
+ { initialState, path, history: null, reducers },
+ );
+
+ expect(getByText('[CustomPage]')).to.exist;
+ });
+
+ it('should show CustomPage+Review indicator when both exist', () => {
+ const config = {
+ chapters: {
+ chapter1: {
+ pages: {
+ page1: {
+ title: 'Test Page',
+ CustomPage: () => null,
+ CustomPageReview: () => null,
+ },
+ },
+ },
+ },
+ };
+
+ const { getByText } = renderWithStoreAndRouter(
+ ,
+ { initialState, path, history: null, reducers },
+ );
+
+ expect(getByText('[CustomPage+Review]')).to.exist;
+ });
+ });
+});
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormDataViewer.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormDataViewer.jsx
new file mode 100644
index 000000000000..936d32102c96
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormDataViewer.jsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+/**
+ * @component FormDataViewer
+ * @description Displays form data values in a read-only format.
+ * Skips objects where all nested values are undefined.
+ *
+ * @param {Object} props
+ * @param {Object} props.data - The form data object to display
+ */
+export const FormDataViewer = ({ data }) => {
+ if (!data || typeof data !== 'object') {
+ return No data available
;
+ }
+
+ // Recursively check if an object has any defined values
+ const hasDefinedValues = obj => {
+ if (!obj || typeof obj !== 'object') {
+ return obj !== undefined;
+ }
+
+ return Object.values(obj).some(value => {
+ if (value && typeof value === 'object') {
+ return hasDefinedValues(value);
+ }
+ return value !== undefined;
+ });
+ };
+
+ const renderValue = (key, value) => {
+ // Skip undefined values
+ if (value === undefined) {
+ return null;
+ }
+
+ // Skip objects with no defined values
+ if (value && typeof value === 'object' && !hasDefinedValues(value)) {
+ return null;
+ }
+
+ switch (typeof value) {
+ case 'boolean':
+ return (
+
+
+ {key}:
+
+ {value ? 'Yes' : 'No'}
+
+ );
+
+ case 'string':
+ case 'number':
+ if (!value && value !== 0) return null;
+ return (
+
+
+ {key}:
+
+ {value}
+
+ );
+
+ case 'object':
+ if (value === null) {
+ return null;
+ }
+
+ // Only render object if it has defined values
+ if (!hasDefinedValues(value)) {
+ return null;
+ }
+
+ return (
+
+ );
+
+ default:
+ return null;
+ }
+ };
+
+ // If no defined values exist at all, show empty message
+ if (!hasDefinedValues(data)) {
+ return (
+
+
+ No form data entered yet
+
+
+ );
+ }
+
+ return (
+
+ {Object.entries(data).map(([key, value]) => {
+ const rendered = renderValue(key, value);
+ return rendered ?
{rendered}
: null;
+ })}
+
+ );
+};
+
+FormDataViewer.propTypes = {
+ data: PropTypes.object,
+};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormTab.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormTab.jsx
new file mode 100644
index 000000000000..9830ee426e71
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormTab.jsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import { Link, withRouter } from 'react-router';
+import { useSelector } from 'react-redux';
+import ChapterAnalyzer from './ChapterAnalyzer';
+import { FormDataViewer } from './FormDataViewer';
+
+const getFormInfo = router => {
+ const childRoutes = router?.routes.map?.(route => {
+ if (route.childRoutes && route.childRoutes.length > 0) {
+ return route.childRoutes?.[0];
+ }
+ return null;
+ });
+
+ // we are assuming the first route with config is the same for all routes
+ const firstRouteWithConfig = childRoutes.find(route => {
+ return route?.formConfig;
+ }, {});
+
+ const pageListFormatted = firstRouteWithConfig?.pageList?.map(page => {
+ return {
+ path: page?.path,
+ title: page?.title,
+ pageKey: page?.pageKey,
+ isActive: router?.location?.pathname === page?.path,
+ };
+ });
+
+ return {
+ pageList: pageListFormatted,
+ formConfig: firstRouteWithConfig?.formConfig,
+ };
+};
+
+const FormTabBase = props => {
+ const formInfo = getFormInfo(props.router);
+ const specialPages = formInfo?.pageList?.filter(
+ page =>
+ page.path?.includes('/introduction') ||
+ page.path?.includes('/review-and-submit'),
+ );
+
+ const formData = useSelector(state => state?.form?.data);
+
+ return (
+
+
+ {specialPages?.length > 0
+ ? specialPages.map((page, index) => (
+
+ {page.title || page.pageKey || page.path}
+
+ ))
+ : null}
+
+
+
+
+ );
+};
+
+export const FormTab = withRouter(FormTabBase);
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/ActiveElement.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/ActiveElement.jsx
new file mode 100644
index 000000000000..afee1eb40a8f
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/ActiveElement.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { useFocusedElement } from '../../../hooks/useFocusedElement';
+
+export const ActiveElement = () => {
+ const { displayString, onMouseEnter, onMouseLeave } = useFocusedElement();
+ return (
+
+
Active element:
+
+
+
+ {displayString || 'No element focused'}
+
+
+
+ );
+};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/HeadingHierarchyInspector.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/HeadingHierarchyInspector.jsx
similarity index 91%
rename from src/applications/_mock-form-ae-design-patterns/vadx/panel/HeadingHierarchyInspector.jsx
rename to src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/HeadingHierarchyInspector.jsx
index d9590c2acd49..b9b2cd795ef5 100644
--- a/src/applications/_mock-form-ae-design-patterns/vadx/panel/HeadingHierarchyInspector.jsx
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/HeadingHierarchyInspector.jsx
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { withRouter } from 'react-router';
import PropTypes from 'prop-types';
-import HeadingHierarchyAnalyzer from '../utils/HeadingHierarchyAnalyzer';
+import HeadingHierarchyAnalyzer from '../../../utils/HeadingHierarchyAnalyzer';
const PreXs = styled.pre`
font-size: 0.75rem;
@@ -51,10 +51,11 @@ const HeadingHierarchyInspectorBase = ({ location }) => {
) : (
-
+
+ Heading Hierarchy
{analysis?.issues?.length > 0 ? (
-
- Heading Issues Found ({analysis.issues.length})
+
+ Issues Found ({analysis.issues.length})
) : (
@@ -94,9 +95,6 @@ const HeadingHierarchyInspectorBase = ({ location }) => {
)}
-
- Heading Hierarchy
-
{analysis &&
new HeadingHierarchyAnalyzer().generateTreeText(
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/LoggedInState.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/LoggedInState.jsx
new file mode 100644
index 000000000000..c18ec30fe326
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/LoggedInState.jsx
@@ -0,0 +1,19 @@
+import React, { useContext } from 'react';
+import { VADXContext } from '../../../context/vadx';
+
+export const LoggedInState = () => {
+ const { logIn, logOut, loggedIn } = useContext(VADXContext);
+
+ return (
+
+
+ Logged In Status: {loggedIn ? 'true' : 'false'}
+
+
+
+ );
+};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/MemoryUsage.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/MemoryUsage.jsx
new file mode 100644
index 000000000000..29534cbd0ad8
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/MemoryUsage.jsx
@@ -0,0 +1,36 @@
+import React, { useState, useEffect } from 'react';
+
+export const MemoryUsage = () => {
+ const [memoryUsage, setMemoryUsage] = useState(null);
+
+ useEffect(() => {
+ // Function to get browser tab's memory usage
+ const updateMemoryUsage = () => {
+ if (performance && performance.memory) {
+ const used = Math.round(
+ performance.memory.usedJSHeapSize / 1024 / 1024,
+ );
+ const total = Math.round(
+ performance.memory.totalJSHeapSize / 1024 / 1024,
+ );
+ setMemoryUsage({ used, total });
+ }
+ };
+
+ updateMemoryUsage();
+
+ const interval = setInterval(updateMemoryUsage, 2000);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ return memoryUsage ? (
+
+
+ Memory Usage: {memoryUsage.used}
+ MB / {memoryUsage.total}
+ MB
+
+
+ ) : null;
+};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/OtherTab.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/OtherTab.jsx
new file mode 100644
index 000000000000..f1cf24d1cc2d
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/other/OtherTab.jsx
@@ -0,0 +1,17 @@
+import React from 'react';
+
+import { HeadingHierarchyInspector } from './HeadingHierarchyInspector';
+import { MemoryUsage } from './MemoryUsage';
+import { ActiveElement } from './ActiveElement';
+import { LoggedInState } from './LoggedInState';
+
+export const OtherTab = () => {
+ return (
+
+ );
+};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/toggles/TogglesTab.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/toggles/TogglesTab.jsx
new file mode 100644
index 000000000000..a6b2e96b269e
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/toggles/TogglesTab.jsx
@@ -0,0 +1,180 @@
+import {
+ VaButton,
+ VaCheckbox,
+ VaSearchInput,
+} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
+import { snakeCase } from 'lodash';
+import React, { useCallback, useContext, useEffect, useMemo } from 'react';
+import FEATURE_FLAG_NAMES from 'platform/utilities/feature-toggles/featureFlagNames';
+import { useDispatch } from 'react-redux';
+import { connectFeatureToggle } from 'platform/utilities/feature-toggles';
+import { TOGGLE_VALUES_RESET } from 'platform/site-wide/feature-toggles/actionTypes';
+import { VADXContext } from '../../../context/vadx';
+
+/**
+ * @component TogglesTab
+ *
+ * @description A tab component that manages and displays feature toggles
+ * This component provides functionality to:
+ * - Search through feature toggles
+ * - Reset toggles to their default values
+ * - Fetch toggles from the staging environment
+ * - Toggle individual features on/off
+ *
+ * @requires VADXContext - Context providing toggle state management and other VADX utilities
+ *
+ * @requires Redux - For global state management of feature toggles
+ *
+ * @note
+ * - Only displays toggles with underscore naming convention
+ * - Excludes toggles containing 'vadx' in their names
+ * - Maintains both camelCase and snake_case versions of toggle names for compatibility within state
+ *
+ */
+export const TogglesTab = () => {
+ const dispatch = useDispatch();
+ const {
+ preferences,
+ updateDevLoading,
+ updateLocalToggles,
+ updateClearLocalToggles,
+ debouncedSetSearchQuery,
+ togglesLoading,
+ togglesState,
+ } = useContext(VADXContext);
+
+ const handleResetToggles = useCallback(
+ () => {
+ updateDevLoading(true);
+ updateClearLocalToggles();
+ dispatch({ type: TOGGLE_VALUES_RESET });
+ dispatch(connectFeatureToggle);
+ updateDevLoading(false);
+ },
+ [dispatch, updateDevLoading, updateClearLocalToggles],
+ );
+
+ const { searchQuery, isDevLoading } = preferences;
+
+ useEffect(() => {
+ if (preferences?.localToggles) {
+ updateLocalToggles(preferences.localToggles);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const fetchDevToggles = useCallback(
+ async () => {
+ try {
+ updateDevLoading(true);
+
+ const response = await fetch(
+ 'https://staging-api.va.gov/v0/feature_toggles',
+ );
+ const {
+ data: { features },
+ } = await response.json();
+
+ const formattedToggles = features
+ .filter(toggle => {
+ return toggle.name.includes('_') && !toggle.name.includes('vadx');
+ })
+ .reduce(
+ (acc, toggle) => {
+ acc[toggle.name] = toggle.value;
+ return acc;
+ },
+ {
+ [FEATURE_FLAG_NAMES.aedpVADX]: true,
+ },
+ );
+
+ updateLocalToggles(formattedToggles);
+
+ updateDevLoading(false);
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('Error fetching dev toggles:', error);
+ }
+ },
+ [updateDevLoading, updateLocalToggles],
+ );
+
+ const filteredToggles = useMemo(
+ () => {
+ return Object.keys(togglesState).filter(toggle => {
+ const toggleName = toggle.toLowerCase();
+ const searchQueryLower = searchQuery?.toLowerCase() || '';
+
+ return (
+ toggle.includes('_') &&
+ toggleName.includes(searchQueryLower) &&
+ !toggleName.includes('vadx') &&
+ toggle !== 'loading'
+ );
+ });
+ },
+ [togglesState, searchQuery],
+ );
+
+ if (togglesLoading || isDevLoading) {
+ return Loading...
;
+ }
+
+ return (
+ <>
+ debouncedSetSearchQuery(e.target.value)}
+ onSubmit={e => debouncedSetSearchQuery(e.target.value)}
+ small
+ value={searchQuery}
+ />
+
+
+
+
+
+
+ {filteredToggles?.length} toggles
+
+
+
+ {Object.keys(togglesState).length < 2 && (
+
+
+
+ No toggles found.
+
+
+ )}
+
+ {filteredToggles.map(toggle => {
+ const snakeCaseToggle = snakeCase(toggle);
+ return (
+
+ {
+ const updatedToggles = {
+ ...togglesState,
+ [toggle]: e.target.checked,
+ [snakeCaseToggle]: e.target.checked,
+ };
+ updateLocalToggles(updatedToggles);
+ }}
+ />
+
+ );
+ })}
+ >
+ );
+};
diff --git a/src/platform/site-wide/feature-toggles/actionTypes.js b/src/platform/site-wide/feature-toggles/actionTypes.js
index ec3011c3b06b..acfe63493426 100644
--- a/src/platform/site-wide/feature-toggles/actionTypes.js
+++ b/src/platform/site-wide/feature-toggles/actionTypes.js
@@ -1,3 +1,4 @@
export const FETCH_TOGGLE_VALUES_STARTED = 'FETCH_TOGGLE_VALUES_STARTED';
export const FETCH_TOGGLE_VALUES_SUCCEEDED = 'FETCH_TOGGLE_VALUES_SUCCEEDED';
export const TOGGLE_VALUES_SET = 'TOGGLE_VALUES_SET';
+export const TOGGLE_VALUES_RESET = 'TOGGLE_VALUES_RESET';
diff --git a/src/platform/site-wide/feature-toggles/reducers/index.js b/src/platform/site-wide/feature-toggles/reducers/index.js
index b9042eeead10..bebd4ecf8724 100644
--- a/src/platform/site-wide/feature-toggles/reducers/index.js
+++ b/src/platform/site-wide/feature-toggles/reducers/index.js
@@ -2,6 +2,7 @@ import {
TOGGLE_VALUES_SET,
FETCH_TOGGLE_VALUES_STARTED,
FETCH_TOGGLE_VALUES_SUCCEEDED,
+ TOGGLE_VALUES_RESET,
} from '../actionTypes';
const INITIAL_STATE = {};
@@ -24,6 +25,8 @@ export const FeatureToggleReducer = (state = INITIAL_STATE, action) => {
...state,
...action.newToggleValues,
};
+ case TOGGLE_VALUES_RESET:
+ return INITIAL_STATE;
default:
return state;
}