This is an automated email from the ASF dual-hosted git repository.

fimugdha pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/master by this push:
     new a93c43f6b RANGER-5685 : Follow-on UI side review changes for 
RANGER-5628 (#1127)
a93c43f6b is described below

commit a93c43f6b084874cab0ea70fa86509fb77e2a014
Author: Mugdha Varadkar <[email protected]>
AuthorDate: Wed Aug 5 17:39:03 2026 +0530

    RANGER-5685 : Follow-on UI side review changes for RANGER-5628 (#1127)
---
 .../src/components/CommonComponents.jsx            |   6 +-
 .../react-webapp/src/components/Editable.jsx       |  75 ++++----
 .../hooks/usePolicyPermissionConditionContext.js   |  88 +++++++++
 .../src/hooks/usePruneStaleConditions.js           | 148 +++++++--------
 .../react-webapp/src/utils/policyConditionUtils.js | 198 ++++++++++++++++++---
 .../views/PolicyListing/AddUpdatePolicyForm.jsx    |  16 +-
 .../EditNewPolicyForm/EditPolicyForm.jsx           |   1 +
 .../AccessRulesStep.jsx                            |  11 +-
 .../PolicyDetailsStep.jsx                          |  10 +-
 .../PolicyPermissionItemVertical.jsx               |  69 +++----
 .../views/PolicyListing/PolicyConditionsComp.jsx   |  29 +--
 .../views/PolicyListing/PolicyPermissionItem.jsx   | 115 ++++--------
 12 files changed, 479 insertions(+), 287 deletions(-)

diff --git 
a/security-admin/src/main/webapp/react-webapp/src/components/CommonComponents.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/components/CommonComponents.jsx
index 64aed3515..b771668f0 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/components/CommonComponents.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/components/CommonComponents.jsx
@@ -499,10 +499,6 @@ export const scrollToError = (selector) => {
 };
 
 export const selectInputCustomStyles = {
-  multiValue: (base) => ({
-    ...base,
-    maxWidth: "100%"
-  }),
   option: (base) => ({
     ...base,
     textOverflow: "unset",
@@ -523,7 +519,7 @@ export const selectInputCustomStyles = {
   })
 };
 
-/** Multi-value select styles that wrap chips (e.g. action-matches in 
permission rows). */
+// Multi-value select styles that wrap chips (e.g. action-matches in 
permission rows).
 export const selectInputWrappingCustomStyles = {
   ...selectInputCustomStyles,
   control: (base) => ({
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/components/Editable.jsx 
b/security-admin/src/main/webapp/react-webapp/src/components/Editable.jsx
index f05c790d5..807764972 100644
--- a/security-admin/src/main/webapp/react-webapp/src/components/Editable.jsx
+++ b/security-admin/src/main/webapp/react-webapp/src/components/Editable.jsx
@@ -59,8 +59,7 @@ const TYPE_CUSTOM = "custom";
  * Callers may pass selectProps to merge or override (PolicyPermissionItem 
does).
  */
 const CONDITION_POPOVER_SELECT_PROPS = {
-  menuPortalTarget:
-    typeof document !== "undefined" ? document.body : undefined,
+  menuPortalTarget: typeof document !== "undefined" ? document.body : 
undefined,
   menuPosition: "fixed",
   menuPlacement: "auto",
   menuShouldScrollIntoView: false
@@ -243,15 +242,9 @@ const ConditionRow = ({
           return mOp;
         }
       });
-      setCondSelect(
-        !isEmpty(e) ? (e?.length > 1 ? filterVal : e) : null
-      );
+      setCondSelect(!isEmpty(e) ? (e?.length > 1 ? filterVal : e) : null);
       tagAccessData(
-        !isEmpty(e)
-          ? e?.length > 1
-            ? filterVal[0].value
-            : e[0].value
-          : null,
+        !isEmpty(e) ? (e?.length > 1 ? filterVal[0].value : e[0].value) : null,
         name
       );
     };
@@ -321,9 +314,7 @@ const ConditionRow = ({
                 isInvalid={validExpression.state}
               />
               {validExpression.state && (
-                <div className="text-danger">
-                  {validExpression.errorMSG}
-                </div>
+                <div className="text-danger">{validExpression.errorMSG}</div>
               )}
             </Col>
           </Row>
@@ -529,6 +520,23 @@ const Editable = (props) => {
     };
   }, []);
 
+  useEffect(() => {
+    if (!initialLoad.current) {
+      onChange(editableValue);
+      dispatch({
+        type: "SET_VALUE",
+        value: editableValue,
+        show: false,
+        target: null
+      });
+    } else {
+      initialLoad.current = false;
+    }
+    type === TYPE_CUSTOM
+      ? (selectValRef.current = { ...editableValue })
+      : (selectValRef.current = editableValue);
+  }, [editableValue]);
+
   const displayValue = () => {
     let val = "--";
     let selectVal = value;
@@ -759,23 +767,6 @@ const Editable = (props) => {
     return val;
   };
 
-  useEffect(() => {
-    if (!initialLoad.current) {
-      onChange(editableValue);
-      dispatch({
-        type: "SET_VALUE",
-        value: editableValue,
-        show: false,
-        target: null
-      });
-    } else {
-      initialLoad.current = false;
-    }
-    type === TYPE_CUSTOM
-      ? (selectValRef.current = { ...editableValue })
-      : (selectValRef.current = editableValue);
-  }, [editableValue]);
-
   const handleApply = () => {
     let errors, uiHintVal;
     if (selectValRef?.current) {
@@ -795,7 +786,7 @@ const Editable = (props) => {
             actionFilterContext?.selectedAccessTypes?.length > 0
           ) {
             const current = selectValRef.current[conditionObj.name];
-            
+
             if (Array.isArray(current)) {
               const { prunedSelection } = getAllowedActionMatchesForCondition({
                 conditionName: conditionObj.name,
@@ -805,7 +796,7 @@ const Editable = (props) => {
                 uiHintAttb: uiHintVal,
                 currentSelection: current
               });
-              
+
               if (prunedSelection && prunedSelection.length > 0) {
                 selectValRef.current[conditionObj.name] = prunedSelection;
               } else {
@@ -842,6 +833,16 @@ const Editable = (props) => {
     }
   };
 
+  const handleClick = (e) => {
+    setValidated({ state: false, errorMSG: "" });
+    let display = !show;
+    dispatch({
+      type: "SET_POPOVER",
+      show: display,
+      target: e.target
+    });
+  };
+
   const handleClose = () => {
     setValidated({ state: false, errorMSG: "" });
     dispatch({
@@ -906,16 +907,6 @@ const Editable = (props) => {
     </Popover>
   );
 
-  const handleClick = (e) => {
-    setValidated({ state: false, errorMSG: "" });
-    let display = !show;
-    dispatch({
-      type: "SET_POPOVER",
-      show: display,
-      target: e.target
-    });
-  };
-
   return (
     <div ref={popoverRef}>
       <OverlayTrigger
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/hooks/usePolicyPermissionConditionContext.js
 
b/security-admin/src/main/webapp/react-webapp/src/hooks/usePolicyPermissionConditionContext.js
new file mode 100644
index 000000000..c91925eb4
--- /dev/null
+++ 
b/security-admin/src/main/webapp/react-webapp/src/hooks/usePolicyPermissionConditionContext.js
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useMemo } from "react";
+import { groupBy } from "lodash";
+import { policyConditionUpdatedJSON } from "Utils/XAUtils";
+import {
+  buildActionReqsMapFromConditionDef,
+  getResourceSelectionSignature,
+  getSelectedLeafResourceTypes
+} from "Utils/policyConditionUtils";
+import { usePruneStaleConditions } from "Hooks/usePruneStaleConditions";
+
+/**
+ * Shared condition/action-matches setup for permission row components.
+ * Memoizes condition defs, action requirements, and leaf resource types,
+ * then runs background action-matches prune sync.
+ */
+export const usePolicyPermissionConditionContext = ({
+  serviceCompDetails,
+  formValues,
+  attrName,
+  form,
+  enableResourcePruneDefer = false
+}) => {
+  const conditionDefVal = useMemo(
+    () => policyConditionUpdatedJSON(serviceCompDetails?.policyConditions),
+    [serviceCompDetails?.policyConditions]
+  );
+
+  const actionReqsMap = useMemo(
+    () => buildActionReqsMapFromConditionDef(conditionDefVal),
+    [conditionDefVal]
+  );
+
+  const grpResourcesKeys = useMemo(() => {
+    const { resources = [] } = serviceCompDetails || {};
+    const grpResources = groupBy(resources, "level");
+    let keys = [];
+    for (const resourceKey in grpResources) {
+      keys.push(+resourceKey);
+    }
+    return keys.sort((a, b) => a - b);
+  }, [serviceCompDetails?.resources]);
+
+  const resourceSelectionSignature = getResourceSelectionSignature(
+    grpResourcesKeys,
+    formValues
+  );
+
+  const leafResourceTypes = useMemo(() => {
+    return getSelectedLeafResourceTypes(serviceCompDetails, formValues);
+  }, [serviceCompDetails, resourceSelectionSignature]);
+
+  usePruneStaleConditions({
+    formValues,
+    attrName,
+    form,
+    leafResourceTypes,
+    serviceCompDetails,
+    conditionDefVal,
+    actionReqsMap,
+    enableResourcePruneDefer
+  });
+
+  return {
+    conditionDefVal,
+    actionReqsMap,
+    leafResourceTypes,
+    grpResourcesKeys
+  };
+};
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/hooks/usePruneStaleConditions.js
 
b/security-admin/src/main/webapp/react-webapp/src/hooks/usePruneStaleConditions.js
index 6cb1cb0ed..9d14e4d15 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/hooks/usePruneStaleConditions.js
+++ 
b/security-admin/src/main/webapp/react-webapp/src/hooks/usePruneStaleConditions.js
@@ -23,15 +23,18 @@
  * or other per-row conditions.
  */
 
-import { useEffect } from "react";
+import { useEffect, useMemo } from "react";
 import { isArray, find, isEqual } from "lodash";
 import {
   getSelectedAccessTypesForRow,
   getAllowedActionMatchesForCondition,
-  isPerRowCondition,
+  shouldDeferActionMatchesPrune,
+  isActionMatcherEnabled,
   parseConditionUiHint
 } from "Utils/policyConditionUtils";
 
+const ACTION_MATCHES = "action-matches";
+
 /**
  * Stable dependency string: only permission accesses and per-row condition 
values.
  * Avoids re-running when unrelated form fields (user/group names, etc.) 
change.
@@ -44,7 +47,7 @@ const serializePruneDeps = (formValues, attrName) => {
   return JSON.stringify(
     items.map((item, index) => ({
       accesses: getSelectedAccessTypesForRow(formValues, attrName, index),
-      actionMatches: item?.conditions?.["action-matches"] ?? null
+      actionMatches: item?.conditions?.[ACTION_MATCHES] ?? null
     }))
   );
 };
@@ -56,98 +59,103 @@ export const usePruneStaleConditions = ({
   leafResourceTypes,
   serviceCompDetails,
   conditionDefVal,
-  actionReqsMap
+  actionReqsMap,
+  // When true, skip resource-driven prune while multi-resource rows are empty 
or mid-edit.
+  enableResourcePruneDefer = false
 }) => {
+  const actionMatcherEnabled = isActionMatcherEnabled(
+    serviceCompDetails?.options
+  );
   const serializedPruneDeps = serializePruneDeps(formValues, attrName);
+  const serializedLeafTypes = leafResourceTypes
+    ? [...leafResourceTypes].sort().join(",")
+    : "";
+
+  const actionMatchesUiHint = useMemo(() => {
+    const actionMatchesDef = find(conditionDefVal, { name: ACTION_MATCHES });
+    return actionMatchesDef
+      ? parseConditionUiHint(actionMatchesDef.uiHint)
+      : null;
+  }, [conditionDefVal]);
 
   useEffect(() => {
+    if (!actionMatcherEnabled) {
+      return;
+    }
+
     const items = formValues?.[attrName];
     if (!items || !isArray(items)) {
       return;
     }
 
-    let hasChanges = false;
-    const newItems = [...items];
+    const deferResourcePrune =
+      enableResourcePruneDefer &&
+      shouldDeferActionMatchesPrune(serviceCompDetails, formValues);
 
-    newItems.forEach((item, index) => {
+    const baseActionFilterContext = {
+      leafResourceTypes,
+      accessTypeDefs: serviceCompDetails?.accessTypes
+    };
+
+    const changedRows = [];
+
+    items.forEach((item, index) => {
       if (!item?.conditions) {
         return;
       }
 
-      const accesses = getSelectedAccessTypesForRow(formValues, attrName, 
index);
+      const accesses = getSelectedAccessTypesForRow(
+        formValues,
+        attrName,
+        index
+      );
+
+      // Always sync when permissions are cleared — defer must not block this.
       if (accesses.length === 0) {
-        const actionMatches = item.conditions?.["action-matches"];
+        const actionMatches = item.conditions[ACTION_MATCHES];
         if (Array.isArray(actionMatches) && actionMatches.length > 0) {
           const newConditions = { ...item.conditions };
-          delete newConditions["action-matches"];
-          newItems[index] = { ...item, conditions: newConditions };
-          hasChanges = true;
+          delete newConditions[ACTION_MATCHES];
+          changedRows.push({ index, conditions: newConditions });
         }
         return;
       }
 
-      const actionFilterContext = {
-        selectedAccessTypes: accesses,
-        leafResourceTypes,
-        accessTypeDefs: serviceCompDetails?.accessTypes
-      };
-
-      let itemChanged = false;
-      const newConditions = { ...item.conditions };
-
-      for (const conditionName in newConditions) {
-        if (!isPerRowCondition(conditionName)) {
-          continue;
-        }
-
-        const conditionDef = find(conditionDefVal, { name: conditionName });
-        if (!conditionDef) {
-          continue;
-        }
+      // Defer only resource-driven prune while a multi-resource row is empty 
or mid-edit.
+      if (deferResourcePrune) {
+        return;
+      }
 
-        const uiHintVal = parseConditionUiHint(conditionDef.uiHint);
-
-        if (
-          uiHintVal?.isMultiValue &&
-          Array.isArray(newConditions[conditionName])
-        ) {
-          const current = newConditions[conditionName];
-          const { prunedSelection } = getAllowedActionMatchesForCondition({
-            conditionName,
-            actionFilterContext,
-            actionReqsMap,
-            servicedefName: serviceCompDetails?.name,
-            uiHintAttb: uiHintVal,
-            currentSelection: current
-          });
-
-          if (!isEqual(current, prunedSelection)) {
-            if (prunedSelection && prunedSelection.length > 0) {
-              newConditions[conditionName] = prunedSelection;
-            } else {
-              delete newConditions[conditionName];
-            }
-            itemChanged = true;
-            hasChanges = true;
-          }
-        }
+      const current = item.conditions[ACTION_MATCHES];
+      if (!Array.isArray(current) || current.length === 0) {
+        return;
       }
 
-      if (itemChanged) {
-        newItems[index] = { ...item, conditions: newConditions };
+      const { prunedSelection } = getAllowedActionMatchesForCondition({
+        conditionName: ACTION_MATCHES,
+        actionFilterContext: {
+          ...baseActionFilterContext,
+          selectedAccessTypes: accesses
+        },
+        actionReqsMap,
+        servicedefName: serviceCompDetails?.name,
+        uiHintAttb: actionMatchesUiHint,
+        currentSelection: current
+      });
+
+      if (!isEqual(current, prunedSelection)) {
+        const newConditions = { ...item.conditions };
+        if (prunedSelection && prunedSelection.length > 0) {
+          newConditions[ACTION_MATCHES] = prunedSelection;
+        } else {
+          delete newConditions[ACTION_MATCHES];
+        }
+        changedRows.push({ index, conditions: newConditions });
       }
     });
 
-    if (hasChanges) {
-      form.change(attrName, newItems);
-    }
-  }, [
-    serializedPruneDeps,
-    leafResourceTypes,
-    actionReqsMap,
-    attrName,
-    form,
-    conditionDefVal,
-    serviceCompDetails
-  ]);
+    changedRows.forEach(({ index, conditions }) => {
+      form.change(`${attrName}[${index}].conditions`, conditions);
+    });
+  }, [serializedPruneDeps, serializedLeafTypes, attrName]);
 };
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/utils/policyConditionUtils.js 
b/security-admin/src/main/webapp/react-webapp/src/utils/policyConditionUtils.js
index 3397769c3..e61259a51 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/utils/policyConditionUtils.js
+++ 
b/security-admin/src/main/webapp/react-webapp/src/utils/policyConditionUtils.js
@@ -39,7 +39,7 @@
  * (ozone.json keyed by volume | bucket | key) and the row's selected access 
types.
  */
 
-import { find, isEqual, isArray } from "lodash";
+import { isEmpty, isEqual, isArray, find } from "lodash";
 import { actionRequirementsRegistry } from "./actionRequirements/registry";
 
 /**
@@ -188,17 +188,27 @@ export const getSelectedAccessTypesForRow = (formValues, 
attrName, index) => {
  */
 export const isPerRowCondition = (name) => name === "action-matches";
 
+export const isActionMatcherEnabled = (serviceDefOptions) => {
+  const value = serviceDefOptions?.enableActionMatcherInPoliciesCondition;
+  return value === "true" || value === true;
+};
+
 /**
  * Safely parses a condition definition uiHint JSON string.
  * @returns {object|null} Parsed uiHint object, or null if missing or invalid.
  */
 export const parseConditionUiHint = (uiHint) => {
-  if (uiHint == null || uiHint === "") {
+  if (isEmpty(uiHint)) {
     return null;
   }
+
   try {
     const parsed = JSON.parse(uiHint);
-    return parsed && typeof parsed === "object" ? parsed : null;
+    return parsed !== null &&
+      typeof parsed === "object" &&
+      !Array.isArray(parsed)
+      ? parsed
+      : null;
   } catch (e) {
     return null;
   }
@@ -231,6 +241,135 @@ export const sortPolicyConditions = (conditions) => {
   });
 };
 
+const getResourceLevels = (resources) => {
+  if (!Array.isArray(resources)) {
+    return [];
+  }
+  return [
+    ...new Set(resources.map((r) => r?.level).filter(Number.isFinite))
+  ].sort((a, b) => a - b);
+};
+
+const getResourceBlocks = (formValues) => {
+  if (Array.isArray(formValues?.additionalResources)) {
+    return formValues.additionalResources;
+  }
+  return [formValues];
+};
+
+const hasResourcePathValue = (block, level) => {
+  const valueField = block?.[`value-${level}`];
+  if (Array.isArray(valueField)) {
+    return valueField.length > 0;
+  }
+  return valueField != null && valueField !== "";
+};
+
+/**
+ * A resource block is complete when the deepest selected level has a path 
value
+ * or an explicit "None" (e.g. all keys under a bucket). Auto-populated child
+ * levels without user input stay incomplete until resolved.
+ */
+const isResourceBlockComplete = (block, levels) => {
+  if (!block || typeof block !== "object") {
+    return false;
+  }
+
+  let deepestLevel = null;
+  for (const level of levels) {
+    const sel = block[`resourceName-${level}`];
+    if (sel && sel.name) {
+      deepestLevel = level;
+    }
+  }
+
+  if (deepestLevel == null) {
+    return false;
+  }
+
+  const deepestSel = block[`resourceName-${deepestLevel}`];
+  if (deepestSel?.value === NONE_RESOURCE_VALUE) {
+    return true;
+  }
+
+  return hasResourcePathValue(block, deepestLevel);
+};
+
+/** Defer resource-driven background prune while a multi-resource row is empty 
or mid-edit.
+ *  Does not block clearing action-matches when row permissions are removed. */
+export const shouldDeferActionMatchesPrune = (
+  serviceCompDetails,
+  formValues
+) => {
+  if (!Array.isArray(formValues?.additionalResources)) {
+    return false;
+  }
+
+  const levels = getResourceLevels(serviceCompDetails?.resources);
+  if (levels.length === 0) {
+    return false;
+  }
+
+  return formValues.additionalResources.some((block) => {
+    if (!block || typeof block !== "object") {
+      return false;
+    }
+
+    const hasSelection = levels.some((level) => {
+      const sel = block[`resourceName-${level}`];
+      return sel && sel.name;
+    });
+
+    // Empty "Add Resource" row
+    if (!hasSelection) {
+      return true;
+    }
+
+    // Row started but deepest level not committed yet (e.g. key not rendered)
+    return !isResourceBlockComplete(block, levels);
+  });
+};
+
+const resourceSelectionPart = (sel) => {
+  if (!sel) {
+    return "";
+  }
+  return `${sel.name ?? ""}:${sel.value ?? ""}`;
+};
+
+/**
+ * Stable string signature of resource selection fields only.
+ * Multi-resource: additionalResources blocks only.
+ * Single-resource: root formValues resourceName-* fields.
+ *
+ * @param {number[]} resourceLevels - Sorted resource level keys 
(grpResourcesKeys).
+ * @param {object} formValues - Policy form state.
+ * @returns {string} Signature used to memoize leafResourceTypes.
+ */
+export const getResourceSelectionSignature = (resourceLevels, formValues) => {
+  if (!isArray(resourceLevels) || resourceLevels.length === 0) {
+    return "";
+  }
+
+  if (isArray(formValues?.additionalResources)) {
+    return formValues.additionalResources
+      .map((block) =>
+        resourceLevels
+          .map((level) =>
+            resourceSelectionPart(block?.[`resourceName-${level}`])
+          )
+          .join("|")
+      )
+      .join(";");
+  }
+
+  return resourceLevels
+    .map((level) =>
+      resourceSelectionPart(formValues?.[`resourceName-${level}`])
+    )
+    .join("|");
+};
+
 /**
  * Determines the deepest (leaf) resource types currently selected in the 
policy form.
  * For Ozone, this might return {"key"} or {"bucket"} depending on which 
resource
@@ -254,13 +393,9 @@ export const getSelectedLeafResourceTypes = (
     return result;
   }
 
-  const levels = [
-    ...new Set(resources.map((r) => r?.level).filter(Number.isFinite))
-  ].sort((a, b) => a - b);
+  const levels = getResourceLevels(resources);
 
-  const blocks = Array.isArray(formValues?.additionalResources)
-    ? formValues.additionalResources
-    : [formValues];
+  const blocks = getResourceBlocks(formValues);
 
   for (const block of blocks) {
     if (!block) {
@@ -723,35 +858,42 @@ export const getAllowedActionMatchesForCondition = ({
 };
 
 /**
- * Pre-builds the action requirements map from the condition definitions.
- * Scans each condition's uiHint for an `actionRequirementsFile` reference
- * (which points to a JSON file in the actionRequirements registry, e.g. 
"ozone")
- * or an inline `actionRequirements` object.
+ * Pre-builds action requirements for the action-matches condition.
+ * Only per-row conditions (today: action-matches) may declare
+ * actionRequirementsFile (registry) or inline actionRequirements in uiHint.
  *
- * This is memoized in PolicyPermissionItem and Editable so we don't re-parse
- * uiHint JSON on every render.
+ * Memoized in PolicyPermissionItem and Editable so uiHint JSON is not
+ * re-parsed on every render.
  *
  * @param {Array<{name: string, uiHint: string}>} conditionDefVal - Condition 
definitions from service def.
- * @returns {object} Map of condition name → action requirements object.
+ * @returns {object} Map with at most one entry: { "action-matches": 
requirementsObject }.
  */
 export const buildActionReqsMapFromConditionDef = (conditionDefVal) => {
   const map = {};
-  if (!Array.isArray(conditionDefVal) || conditionDefVal.length === 0) {
+
+  if (!isArray(conditionDefVal) || isEmpty(conditionDefVal)) {
     return map;
   }
 
-  conditionDefVal.forEach((m) => {
-    const uiHintAttb = parseConditionUiHint(m.uiHint);
+  const actionMatchesDef = find(conditionDefVal, (def) =>
+    isPerRowCondition(def?.name)
+  );
 
-    if (uiHintAttb) {
-      const fileToLoad = uiHintAttb?.actionRequirementsFile;
-      if (fileToLoad && actionRequirementsRegistry[fileToLoad]) {
-        map[m.name] = actionRequirementsRegistry[fileToLoad];
-      } else if (uiHintAttb?.actionRequirements) {
-        map[m.name] = uiHintAttb.actionRequirements;
-      }
-    }
-  });
+  if (!actionMatchesDef) {
+    return map;
+  }
+
+  const uiHintAttb = parseConditionUiHint(actionMatchesDef.uiHint);
+  if (!uiHintAttb) {
+    return map;
+  }
+
+  const fileToLoad = uiHintAttb.actionRequirementsFile;
+  if (fileToLoad && actionRequirementsRegistry[fileToLoad]) {
+    map[actionMatchesDef.name] = actionRequirementsRegistry[fileToLoad];
+  } else if (uiHintAttb.actionRequirements) {
+    map[actionMatchesDef.name] = uiHintAttb.actionRequirements;
+  }
 
   return map;
 };
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
index 5c0da101e..2c6f2a7b2 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
@@ -66,7 +66,6 @@ import {
 } from "react-router-dom";
 import PolicyValidityPeriodComp from "./PolicyValidityPeriodComp";
 import PolicyConditionsComp from "./PolicyConditionsComp";
-import { isPerRowCondition } from "Utils/policyConditionUtils";
 import moment from "moment";
 import {
   InfoIcon,
@@ -1362,15 +1361,17 @@ export default function AddUpdatePolicyForm() {
                                       name="conditions"
                                       render={({ input }) => (
                                         <PolicyConditionsComp
-                                          
policyConditionDetails={policyConditionUpdatedJSON(
-                                            serviceCompDetails.policyConditions
-                                          )}
-                                          inputVal={input}
+                                          // Visibility & Control
                                           showModal={showModal}
+                                          modalHeader="Policy Conditions"
                                           handleCloseModal={
                                             policyConditionState
                                           }
-                                          modalHeader="Policy Conditions"
+                                          // Data & Value Binding
+                                          inputVal={input}
+                                          
policyConditionDetails={policyConditionUpdatedJSON(
+                                            serviceCompDetails.policyConditions
+                                          )}
                                         />
                                       )}
                                     />
@@ -1394,9 +1395,6 @@ export default function AddUpdatePolicyForm() {
                                 !isEmpty(values.conditions) ? (
                                   Object.keys(values.conditions).map(
                                     (keyName) => {
-                                      if (isPerRowCondition(keyName)) {
-                                        return null;
-                                      }
                                       if (
                                         values.conditions[keyName] != "" &&
                                         values.conditions[keyName] != null
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/EditNewPolicyForm/EditPolicyForm.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/EditNewPolicyForm/EditPolicyForm.jsx
index f5fa0c9ea..d3de2c8d2 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/EditNewPolicyForm/EditPolicyForm.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/EditNewPolicyForm/EditPolicyForm.jsx
@@ -841,6 +841,7 @@ export default function EditPolicyForm() {
                       values={values}
                       form={form}
                       selectedServiceComponentDef={serviceCompDef}
+                      enableResourcePruneDefer
                     />
                   </Accordion.Body>
                 </Accordion.Item>
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/AccessRulesStep.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/AccessRulesStep.jsx
index 686ca47ac..448077256 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/AccessRulesStep.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/AccessRulesStep.jsx
@@ -83,7 +83,11 @@ const isItemEmpty = (item) => {
   return isEmpty;
 };
 
-const AccessRulesStep = ({ values, selectedServiceComponentDef }) => {
+const AccessRulesStep = ({
+  values,
+  selectedServiceComponentDef,
+  enableResourcePruneDefer = false
+}) => {
   const isDenyAllElseEnabled = values?.isDenyAllElse === true;
   const enableDenyAndExceptions =
     selectedServiceComponentDef?.options?.enableDenyAndExceptionsInPolicies ===
@@ -185,6 +189,7 @@ const AccessRulesStep = ({ values, 
selectedServiceComponentDef }) => {
                         formValues={values}
                         attrName="policyItems"
                         msg="Allow rules grants access to resources when the 
specific criteria are met."
+                        enableResourcePruneDefer={enableResourcePruneDefer}
                       />
                     </Accordion.Body>
                   </Accordion.Item>
@@ -201,6 +206,7 @@ const AccessRulesStep = ({ values, 
selectedServiceComponentDef }) => {
                         formValues={values}
                         attrName="allowExceptions"
                         msg="Exclude from Allow Rules grants access to 
resources when the specific criteria are met."
+                        enableResourcePruneDefer={enableResourcePruneDefer}
                       />
                     </Accordion.Body>
                   </Accordion.Item>
@@ -283,6 +289,7 @@ const AccessRulesStep = ({ values, 
selectedServiceComponentDef }) => {
                         attrName="denyPolicyItems"
                         msg="Deny rules explicitly block access to resources 
when the specific
             criteria are met."
+                        enableResourcePruneDefer={enableResourcePruneDefer}
                       />
                     </Accordion.Body>
                   </Accordion.Item>
@@ -300,6 +307,7 @@ const AccessRulesStep = ({ values, 
selectedServiceComponentDef }) => {
                         attrName="denyExceptions"
                         msg="Exclude from Deny Rules explicitly block access 
to resources when the specific
             criteria are met."
+                        enableResourcePruneDefer={enableResourcePruneDefer}
                       />
                     </Accordion.Body>
                   </Accordion.Item>
@@ -317,6 +325,7 @@ const AccessRulesStep = ({ values, 
selectedServiceComponentDef }) => {
                 formValues={values}
                 attrName={item.attrName}
                 msg={item.msg}
+                enableResourcePruneDefer={enableResourcePruneDefer}
               />
             ))}
           </>
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyDetailsStep.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyDetailsStep.jsx
index 36e97981b..58ec8906d 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyDetailsStep.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyDetailsStep.jsx
@@ -459,6 +459,12 @@ const PolicyDetailsStep = ({ values, form, 
selectedServiceComponentDef }) => {
                         {/* Policy Conditions Modal */}
                         {showConditionsModal && (
                           <PolicyConditionsComp
+                            // Visibility & Control
+                            showModal={showConditionsModal}
+                            handleCloseModal={() =>
+                              setShowConditionsModal(false)
+                            }
+                            // Data & Value Binding
                             policyConditionDetails={policyConditionUpdatedJSON(
                               selectedServiceComponentDef.policyConditions
                             )}
@@ -468,10 +474,6 @@ const PolicyDetailsStep = ({ values, form, 
selectedServiceComponentDef }) => {
                                 form.change("conditions", newValue);
                               }
                             }}
-                            showModal={showConditionsModal}
-                            handleCloseModal={() =>
-                              setShowConditionsModal(false)
-                            }
                           />
                         )}
                       </Col>
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyPermissionItemVertical.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyPermissionItemVertical.jsx
index f961df92a..0ac3fd996 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyPermissionItemVertical.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyPermissionItemVertical.jsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import React, { useMemo, useState } from "react";
+import React, { useState } from "react";
 import { FieldArray } from "react-final-form-arrays";
 import {
   Row,
@@ -31,7 +31,6 @@ import { Field, useForm } from "react-final-form";
 import { RangerPolicyType } from "Utils/XAEnums";
 import { find, isEmpty, isArray, toUpper, map } from "lodash";
 import PolicyConditionsComp from "../../PolicyConditionsComp";
-import { policyConditionUpdatedJSON } from "Utils/XAUtils";
 import UsersGroupsAndRolesSelectionComponent from 
"./UsersGroupsAndRolesSelectionComponent";
 import TagBasePermissionItem from "../../TagBasePermissionItem";
 import {
@@ -39,17 +38,14 @@ import {
   getPolicyConditionDisplayLbl
 } from "Utils/XAUtils";
 import { CustomTooltip } from "Components/CommonComponents";
-import {
-  getSelectedAccessTypesForRow,
-  getSelectedLeafResourceTypes,
-  buildActionReqsMapFromConditionDef
-} from "Utils/policyConditionUtils";
-import { usePruneStaleConditions } from 
"../../../../hooks/usePruneStaleConditions";
+import { getSelectedAccessTypesForRow } from "Utils/policyConditionUtils";
+import { usePolicyPermissionConditionContext } from 
"Hooks/usePolicyPermissionConditionContext";
 
 export default function PolicyPermissionItemVertical({
   attrName,
   serviceCompDetails,
-  formValues
+  formValues,
+  enableResourcePruneDefer = false
 }) {
   const [showConditionsModalIndex, setShowConditionsModalIndex] =
     useState(null);
@@ -57,30 +53,14 @@ export default function PolicyPermissionItemVertical({
     useState(null);
   const form = useForm(); // Get form instance
 
-  const conditionDefVal = useMemo(
-    () => policyConditionUpdatedJSON(serviceCompDetails?.policyConditions),
-    [serviceCompDetails?.policyConditions]
-  );
-
-  const actionReqsMap = useMemo(
-    () => buildActionReqsMapFromConditionDef(conditionDefVal),
-    [conditionDefVal]
-  );
-
-  const leafResourceTypes = useMemo(
-    () => getSelectedLeafResourceTypes(serviceCompDetails, formValues),
-    [serviceCompDetails, formValues]
-  );
-
-  usePruneStaleConditions({
-    formValues,
-    attrName,
-    form,
-    leafResourceTypes,
-    serviceCompDetails,
-    conditionDefVal,
-    actionReqsMap
-  });
+  const { conditionDefVal, actionReqsMap, leafResourceTypes } =
+    usePolicyPermissionConditionContext({
+      serviceCompDetails,
+      formValues,
+      attrName,
+      form,
+      enableResourcePruneDefer
+    });
 
   const accessTypeOptions = getAccessTypesByResource(
     formValues,
@@ -553,6 +533,14 @@ export default function PolicyPermissionItemVertical({
                           )}
                           {showConditionsModalIndex === index && (
                             <PolicyConditionsComp
+                              // Visibility & Control
+                              showModal={true}
+                              modalHeader="Rule Conditions"
+                              handleCloseModal={() =>
+                                setShowConditionsModalIndex(null)
+                              }
+                              // Data & Value Binding
+                              scope="policyItem"
                               policyConditionDetails={conditionDefVal}
                               inputVal={{
                                 value: fields.value[index]?.conditions || {},
@@ -560,23 +548,20 @@ export default function PolicyPermissionItemVertical({
                                   form.change(`${name}.conditions`, newValue);
                                 }
                               }}
-                              showModal={true}
-                              handleCloseModal={() =>
-                                setShowConditionsModalIndex(null)
-                              }
-                              modalHeader="Rule Conditions"
-                              scope="policyItem"
+                              // Service & Actions Metadata
                               servicedefName={serviceCompDetails?.name}
+                              serviceDefOptions={serviceCompDetails?.options}
                               actionReqsMap={actionReqsMap}
+                              // Filter Context Evaluation
                               actionFilterContext={{
+                                leafResourceTypes,
+                                accessTypeDefs: 
serviceCompDetails?.accessTypes,
                                 selectedAccessTypes:
                                   getSelectedAccessTypesForRow(
                                     formValues,
                                     attrName,
                                     index
-                                  ),
-                                leafResourceTypes,
-                                accessTypeDefs: serviceCompDetails?.accessTypes
+                                  )
                               }}
                             />
                           )}
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyConditionsComp.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyConditionsComp.jsx
index 60ed5e3e6..34b65c886 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyConditionsComp.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyConditionsComp.jsx
@@ -30,6 +30,7 @@ import {
   trimInputValue
 } from "Components/CommonComponents";
 import {
+  isActionMatcherEnabled,
   isPerRowCondition,
   getCleanConditions,
   parseConditionUiHint,
@@ -109,22 +110,31 @@ const pruneActionMatchesOnSubmit = ({
 
 export default function PolicyConditionsComp(props) {
   const {
-    policyConditionDetails,
-    inputVal,
+    // Visibility & Control
     showModal,
-    handleCloseModal,
     modalHeader,
+    handleCloseModal,
+
+    // Data & Value Binding
+    inputVal,
+    policyConditionDetails,
     scope = POLICY_SCOPE,
+
+    // Service & Actions Metadata
     servicedefName,
-    actionFilterContext,
-    actionReqsMap: actionReqsMapProp
+    serviceDefOptions,
+    actionReqsMap: actionReqsMapProp,
+
+    // Filter Context Evaluation
+    actionFilterContext
   } = props;
 
   const isPolicyItemScope = scope === POLICY_ITEM_SCOPE;
 
+  const hasActionMatcherEnabled = isActionMatcherEnabled(serviceDefOptions);
+
   const conditionDefVal = useMemo(
-    () =>
-      Array.isArray(policyConditionDetails) ? policyConditionDetails : [],
+    () => (Array.isArray(policyConditionDetails) ? policyConditionDetails : 
[]),
     [policyConditionDetails]
   );
 
@@ -231,6 +241,7 @@ export default function PolicyConditionsComp(props) {
 
   const renderMultiValueField = (m, uiHintAttb) => {
     const isActionMatches =
+      hasActionMatcherEnabled &&
       isPolicyItemScope &&
       isPerRowCondition(m.name) &&
       Array.isArray(uiHintAttb?.options);
@@ -281,9 +292,7 @@ export default function PolicyConditionsComp(props) {
             value={ipRangeVal(input.value)}
             onChange={(e) => handleChange(e, input)}
             styles={selectInputCustomStyles}
-            formatCreateLabel={(inputValue) =>
-              `Create "${inputValue.trim()}"`
-            }
+            formatCreateLabel={(inputValue) => `Create "${inputValue.trim()}"`}
             onCreateOption={(inputValue) => {
               const trimmedValue = inputValue.trim();
               if (trimmedValue) {
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyPermissionItem.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyPermissionItem.jsx
index 60a48dff1..351925128 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyPermissionItem.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyPermissionItem.jsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import React, { useMemo, useRef, useState } from "react";
+import React, { useRef, useState } from "react";
 import { Table, Button, Form } from "react-bootstrap";
 import { FieldArray } from "react-final-form-arrays";
 import { Col } from "react-bootstrap";
@@ -25,7 +25,6 @@ import { Field, useForm } from "react-final-form";
 import AsyncSelect from "react-select/async";
 import {
   find,
-  groupBy,
   isEmpty,
   isArray,
   map,
@@ -43,19 +42,19 @@ import {
   dragEnter,
   drop,
   dragOver,
-  policyConditionUpdatedJSON,
   getPolicyConditionDisplayLbl,
   safeJsonParse
 } from "Utils/XAUtils";
-import { selectInputCustomStyles, ConfirmationClearIndicator } from 
"Components/CommonComponents";
+import {
+  selectInputCustomStyles,
+  ConfirmationClearIndicator
+} from "Components/CommonComponents";
 import PolicyConditionsComp from "./PolicyConditionsComp";
 import {
-  getSelectedLeafResourceTypes,
   getSelectedAccessTypesForRow,
-  buildActionReqsMapFromConditionDef,
   getCleanConditions
 } from "Utils/policyConditionUtils";
-import { usePruneStaleConditions } from "../../hooks/usePruneStaleConditions";
+import { usePolicyPermissionConditionContext } from 
"Hooks/usePolicyPermissionConditionContext";
 
 const noneOptions = {
   label: "None",
@@ -87,6 +86,8 @@ export default function PolicyPermissionItem(props) {
 
   const permList = ["Select Roles", "Select Groups", "Select Users"];
 
+  const form = useForm();
+
   if (serviceCompDetails?.policyConditions?.length > 0) {
     permList.push("Rule Conditions");
   }
@@ -123,66 +124,17 @@ export default function PolicyPermissionItem(props) {
     });
   };
 
-  const grpResourcesKeys = useMemo(() => {
-    const { resources = [] } = serviceCompDetails || {};
-    const grpResources = groupBy(resources, "level");
-    let grpResourcesKeys = [];
-    for (const resourceKey in grpResources) {
-      grpResourcesKeys.push(+resourceKey);
-    }
-    grpResourcesKeys = grpResourcesKeys.sort((a, b) => a - b);
-    return grpResourcesKeys;
-  }, [serviceCompDetails?.resources]);
-
-  // Narrow dependency: only recompute leafResourceTypes when resource 
selection
-  // fields change, not on every keystroke in user/group/permission fields.
-  const resourceSelectionPart = (sel) => {
-    if (!sel) {
-      return "";
-    }
-    return `${sel.name ?? ""}:${sel.value ?? ""}`;
-  };
-
-  const resourceSelectionSignature = Array.isArray(
-    formValues?.additionalResources
-  )
-    ? formValues.additionalResources
-        .map((b) =>
-          grpResourcesKeys
-            .map((level) => 
resourceSelectionPart(b?.[`resourceName-${level}`]))
-            .join("|")
-        )
-        .join(";")
-    : grpResourcesKeys
-        .map((level) =>
-          resourceSelectionPart(formValues?.[`resourceName-${level}`])
-        )
-        .join("|");
-
-  const leafResourceTypes = useMemo(() => {
-    return getSelectedLeafResourceTypes(serviceCompDetails, formValues);
-  }, [serviceCompDetails, resourceSelectionSignature]);
-
-  const conditionDefVal = useMemo(
-    () => policyConditionUpdatedJSON(serviceCompDetails.policyConditions),
-    [serviceCompDetails.policyConditions]
-  );
-
-  const form = useForm();
-
-  const actionReqsMap = useMemo(
-    () => buildActionReqsMapFromConditionDef(conditionDefVal),
-    [conditionDefVal]
-  );
-
-  usePruneStaleConditions({
+  const {
+    conditionDefVal,
+    actionReqsMap,
+    leafResourceTypes,
+    grpResourcesKeys
+  } = usePolicyPermissionConditionContext({
+    serviceCompDetails,
     formValues,
     attrName,
     form,
-    leafResourceTypes,
-    serviceCompDetails,
-    conditionDefVal,
-    actionReqsMap
+    enableResourcePruneDefer: true
   });
 
   const getAccessTypeOptions = () => {
@@ -414,8 +366,8 @@ export default function PolicyPermissionItem(props) {
             >
               <span className="line-clamp line-clamp-5 text-start">
                 {`${getPolicyConditionDisplayLbl(conditionObj.label)}: ${
-                isArray(selectVal[property]) ? ipRangVal : selectVal[property]
-              }`}
+                  isArray(selectVal[property]) ? ipRangVal : 
selectVal[property]
+                }`}
               </span>
             </span>
           </div>
@@ -592,33 +544,40 @@ export default function PolicyPermissionItem(props) {
                                       <>
                                         {showConditionsModal && (
                                           <PolicyConditionsComp
-                                            policyConditionDetails={
-                                              conditionDefVal
-                                            }
-                                            inputVal={input}
+                                            // Visibility & Control
                                             showModal={true}
+                                            modalHeader="Rule Conditions"
                                             handleCloseModal={() =>
                                               setActiveConditionRow(null)
                                             }
-                                            modalHeader="Rule Conditions"
+                                            // Data & Value Binding
                                             scope="policyItem"
+                                            inputVal={input}
+                                            policyConditionDetails={
+                                              conditionDefVal
+                                            }
+                                            // Service & Actions Metadata
                                             servicedefName={
                                               serviceCompDetails?.name
                                             }
+                                            serviceDefOptions={
+                                              serviceCompDetails?.options
+                                            }
                                             actionReqsMap={actionReqsMap}
+                                            // Filter Context Evaluation
                                             actionFilterContext={{
+                                              leafResourceTypes,
+                                              accessTypeDefs:
+                                                
serviceCompDetails?.accessTypes,
                                               selectedAccessTypes:
                                                 getSelectedAccessTypesForRow(
                                                   formValues,
                                                   attrName,
                                                   index
-                                                ),
-                                              leafResourceTypes,
-                                              accessTypeDefs:
-                                                serviceCompDetails?.accessTypes
+                                                )
                                             }}
                                           />
-                                      )}
+                                        )}
                                         <div className="table-editable">
                                           {hasValue ? (
                                             <h6>
@@ -633,6 +592,8 @@ export default function PolicyPermissionItem(props) {
                                                 onClick={() =>
                                                   setActiveConditionRow(name)
                                                 }
+                                                
data-js="customPolicyConditions"
+                                                
data-cy="customPolicyConditions"
                                               >
                                                 <i className="fa-fw fa 
fa-pencil"></i>
                                               </Button>
@@ -650,6 +611,8 @@ export default function PolicyPermissionItem(props) {
                                                 onClick={() =>
                                                   setActiveConditionRow(name)
                                                 }
+                                                
data-js="customPolicyConditions"
+                                                
data-cy="customPolicyConditions"
                                               >
                                                 <i className="fa-fw fa 
fa-plus"></i>
                                               </Button>

Reply via email to