From eb2e1ee027b9003d6471b7cde66933505bbc4829 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=A9o=20Banno-Cloutier?=
 <leo.banno-cloutier@savoirfairelinux.com>
Date: Tue, 20 Jun 2023 14:00:50 -0400
Subject: [PATCH] jams-react-client: add PolicyDataContext

Change-Id: I315a0b4c71661e1b7c9fd4e3f9261e4e7ad68078
---
 .dockerignore                                 |   1 +
 .../src/views/Blueprint/Blueprint.js          |   3 +
 .../Blueprint/EditBlueprintConfiguration.js   | 259 ++----------------
 .../Blueprint/EditBlueprintPermissions.js     | 238 +++-------------
 .../src/views/Blueprint/EditBlueprintUi.js    | 180 ++----------
 .../src/views/Blueprint/PolicyDataContext.js  |  61 +++++
 .../src/views/Blueprint/parsePolicyData.js    | 101 +++++++
 .../views/Blueprint/policyData.constants.js   |  39 +++
 .../src/views/Blueprint/updatePolicyData.js   | 168 ++++++++++++
 9 files changed, 452 insertions(+), 598 deletions(-)
 create mode 100644 jams-react-client/src/views/Blueprint/PolicyDataContext.js
 create mode 100644 jams-react-client/src/views/Blueprint/parsePolicyData.js
 create mode 100644 jams-react-client/src/views/Blueprint/policyData.constants.js
 create mode 100644 jams-react-client/src/views/Blueprint/updatePolicyData.js

diff --git a/.dockerignore b/.dockerignore
index e4c71e6e..56e5249f 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -4,5 +4,6 @@
 .dockerignore
 
 **/node_modules
+**/target
 
 jams
diff --git a/jams-react-client/src/views/Blueprint/Blueprint.js b/jams-react-client/src/views/Blueprint/Blueprint.js
index 72283bca..ecf1a1fc 100644
--- a/jams-react-client/src/views/Blueprint/Blueprint.js
+++ b/jams-react-client/src/views/Blueprint/Blueprint.js
@@ -10,6 +10,7 @@ import Box from "@material-ui/core/Box";
 import EditBlueprintPermissions from "./EditBlueprintPermissions";
 import EditBlueprintConfiguration from "./EditBlueprintConfiguration";
 import EditBlueprintUi from "./EditBlueprintUi";
+import { PolicyDataContextProvider } from "./PolicyDataContext";
 
 import { infoColor } from "assets/jss/material-dashboard-react.js";
 
@@ -78,6 +79,7 @@ export default function Blueprint({ blueprintName }) {
         </Tabs>
       </AppBar>
 
+      <PolicyDataContextProvider blueprintName={blueprintName}>
         <TabPanel value={openedTab} index={0}>
           <EditBlueprintPermissions blueprintName={blueprintName} />
         </TabPanel>
@@ -87,6 +89,7 @@ export default function Blueprint({ blueprintName }) {
         <TabPanel value={openedTab} index={2}>
           <EditBlueprintUi blueprintName={blueprintName} />
         </TabPanel>
+      </PolicyDataContextProvider>
     </div>
   );
 }
diff --git a/jams-react-client/src/views/Blueprint/EditBlueprintConfiguration.js b/jams-react-client/src/views/Blueprint/EditBlueprintConfiguration.js
index 4192bcfd..7017a89f 100644
--- a/jams-react-client/src/views/Blueprint/EditBlueprintConfiguration.js
+++ b/jams-react-client/src/views/Blueprint/EditBlueprintConfiguration.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from "react";
+import React, { useContext, useState } from "react";
 import clsx from "clsx";
 
 // @material-ui/core components
@@ -41,6 +41,7 @@ import { hexToRgb, blackColor } from "assets/jss/material-dashboard-react.js";
 import BlueprintSnackbar from "components/Snackbar/BlueprintSnackbar";
 import CustomPopupState from "components/CustomPopupState/CustomPopupState";
 
+import { PolicyDataContext } from "./PolicyDataContext";
 
 const styles = (theme) => ({
   ...dashboardStyle,
@@ -158,214 +159,30 @@ const StyledRadio = (props) => {
 
 export default function EditBlueprintConfiguration(props) {
   const classes = useStyles();
-
-  const [videoEnabled, setVideoEnabled] = useState(true);
-  const [publicInCalls, setPublicInCalls] = useState(true);
-  const [autoAnswer, setAutoAnswer] = useState(false);
-  const [peerDiscovery, setPeerDiscovery] = useState(true);
-  const [rendezVous, setRendezVous] = useState(false);
-
-  const [selectedTurnOption, setSelectedTurnOption] = useState("defaultTurn");
-  const [selectedDHTProxyOption, setSelectedDHTProxyOption] = useState(
-    "defaultDHTProxy"
+  const { policyData, updatePolicyData, snackbar, setSnackbar } = useContext(
+    PolicyDataContext
   );
-  const [upnpEnabled, setUpnpEnabled] = useState(true);
-  const [turnEnabled, setTurnEnabled] = useState(true);
-  const [turnServer, setTurnServer] = useState("turn.jami.net");
-  const [turnServerUserName, setTurnServerUserName] = useState("ring");
-  const [turnServerPassword, setTurnServerPassword] = useState("ring");
-  const [proxyEnabled, setProxyEnabled] = useState(false);
-  const [proxyServer, setProxyServer] = useState("dhtproxy.jami.net");
-  const [dhtProxyListUrl, setDhtProxyListUrl] = useState("");
-
-  const [turnPassowrdVisible, setTurnPasswordVisible] = useState(false);
-
-  const [uiCustomization, setUiCustomization] = useState("");
-
-  const [open, setOpen] = useState(false);
-  const [message, setMessage] = useState(false);
-  const [severity, setSeverity] = useState("success");
-
-  const [getRequestDone, setGetRequestDone] = useState(false);
-
-  useEffect(() => {
-    axios(
-      configApiCall(
-        api_path_blueprints + "?name=" + props.blueprintName,
-        "GET",
-        null,
-        null
-      )
-    )
-      .then((response) => {
-        let policyData = JSON.parse(response.data.policyData);
-        setVideoEnabled(policyData["videoEnabled"]);
-        setPublicInCalls(policyData["publicInCalls"]);
-        setAutoAnswer(policyData["autoAnswer"]);
-        setPeerDiscovery(policyData["peerDiscovery"]);
-        setRendezVous(policyData["rendezVous"]);
-        setUiCustomization(policyData["uiCustomization"]);
-
-        setUpnpEnabled(policyData["upnpEnabled"]);
-        if (policyData["turnEnabled"] === true) {
-          setSelectedTurnOption("customTurn");
-          setTurnEnabled(policyData["turnEnabled"]);
-          setTurnServer(policyData["turnServer"]);
-          setTurnServerUserName(policyData["turnServerUserName"]);
-          setTurnServerPassword(policyData["turnServerPassword"]);
-        } else if (policyData["turnEnabled"] === false) {
-          setSelectedTurnOption("disabledTurn");
-          setTurnEnabled(false);
-        } else {
-          setSelectedTurnOption("defaultTurn");
-        }
-        if (policyData["proxyEnabled"] === true) {
-          setSelectedDHTProxyOption("customDHTProxy");
-          setProxyEnabled(policyData["proxyEnabled"]);
-          setProxyServer(policyData["proxyServer"]);
-          setDhtProxyListUrl(policyData["dhtProxyListUrl"]);
-        } else if (policyData["proxyEnabled"] === false) {
-          setSelectedDHTProxyOption("disabledDHTProxy");
-          setTurnEnabled(false);
-        } else {
-          setSelectedDHTProxyOption("defaultDHTProxy");
-        }
-      })
-      .catch((error) => {
-        console.log(
-          "Error fetching blueprint permissions : " +
-            props.username +
-            " " +
-            error
-        );
-      })
-      .finally(() => {
-        setGetRequestDone(true);
-      });
-  }, [props.blueprintName, props.username]);
 
-  const handleUpdateConfiguration = (field, value, selectedOptions = []) => {
-    // if the get request is not done yet, don't update the configuration
-    if (!getRequestDone) {
-      return;
-    }
+  const {
+    upnpEnabled,
 
-    let data = {
-      videoEnabled: videoEnabled,
-      publicInCalls: publicInCalls,
-      autoAnswer: autoAnswer,
-      peerDiscovery: peerDiscovery,
-      accountDiscovery: peerDiscovery,
-      accountPublish: peerDiscovery,
-      rendezVous: rendezVous,
-      upnpEnabled: upnpEnabled,
-      turnEnabled: turnEnabled,
-      turnServer: turnServer,
-      turnServerUserName: turnServerUserName,
-      turnServerPassword: turnServerPassword,
-      proxyEnabled: proxyEnabled,
-      proxyServer: proxyServer,
-      dhtProxyListUrl: dhtProxyListUrl,
-      uiCustomization: uiCustomization,
-    };
+    selectedTurnOption,
+    turnServer,
+    turnServerUserName,
 
-    data[field] = value;
+    selectedDHTProxyOption,
+    proxyServer,
+    dhtProxyListUrl,
+  } = policyData;
 
-    selectedOptions.forEach((selectedOption) => {
-      if (selectedOption === "defaultTurn") {
-        delete data.turnEnabled;
-        delete data.turnServer;
-        delete data.turnServerUserName;
-        delete data.turnServerPassword;
-      }
-
-      if (selectedOption === "defaultDHTProxy") {
-        delete data.proxyEnabled;
-        delete data.proxyServer;
-        delete data.dhtProxyListUrl;
-      }
-
-      if (selectedOption === "customTurn") {
-        data.turnEnabled = true;
-      }
-      if (selectedOption === "customDHTProxy") {
-        data.proxyEnabled = true;
-      }
-
-      if (selectedOption === "disabledTurn") {
-        data.turnEnabled = false;
-      }
-      if (selectedOption === "disabledDHTProxy") {
-        data.proxyEnabled = false;
-      }
-    });
-
-    axios(
-      configApiCall(
-        api_path_blueprints + "?name=" + props.blueprintName,
-        "PUT",
-        data,
-        null
-      )
-    )
-      .then((response) => {
-        setOpen(false);
-        setSeverity("success");
-        setOpen(true);
-        setMessage(
-          i18next.t(
-            "updated_blueprint_configuration_successfully",
-            "Blueprint configuration successfully updated."
-          )
-        );
-      })
-      .catch((error) => {
-        setOpen(false);
-        setSeverity("error");
-        setOpen(true);
-        setMessage(
-          i18next.t(
-            "error_updating_blueprint_configuration",
-            "Error occurred while updating blueprint configuration."
-          ) +
-            error +
-            "!"
-        );
-      });
-  };
+  const [turnPasswordVisible, setTurnPasswordVisible] = useState(false);
 
   const handleTurnChangedOption = (event) => {
-    setSelectedTurnOption(event.target.value);
-    if (event.target.value === "customTurn") {
-      setTurnEnabled(true);
-      handleUpdateConfiguration("turnEnabled", true, [
-        event.target.value,
-        selectedDHTProxyOption,
-      ]);
-    } else {
-      setTurnEnabled(false);
-      handleUpdateConfiguration("turnEnabled", false, [
-        event.target.value,
-        selectedDHTProxyOption,
-      ]);
-    }
+    updatePolicyData("selectedTurnOption", event.target.value);
   };
 
   const handleDHTProxyChangedOption = (event) => {
-    setSelectedDHTProxyOption(event.target.value);
-    if (event.target.value === "customDHTProxy") {
-      setProxyEnabled(true);
-      handleUpdateConfiguration("proxyEnabled", true, [
-        selectedTurnOption,
-        event.target.value,
-      ]);
-    } else {
-      setProxyEnabled(false);
-      handleUpdateConfiguration("proxyEnabled", false, [
-        selectedTurnOption,
-        event.target.value,
-      ]);
-    }
+    updatePolicyData("selectedDHTProxyOption", event.target.value);
   };
 
   const handleMouseDownPassword = () => {
@@ -410,12 +227,7 @@ export default function EditBlueprintConfiguration(props) {
                           checked={upnpEnabled}
                           color="primary"
                           onChange={(e) => {
-                            setUpnpEnabled(e.target.checked);
-                            handleUpdateConfiguration(
-                              "upnpEnabled",
-                              e.target.checked,
-                              [selectedTurnOption, selectedDHTProxyOption]
-                            );
+                            updatePolicyData("upnpEnabled", e.target.checked);
                           }}
                           name="upnpEnabled"
                           inputProps={{ "aria-label": "secondary checkbox" }}
@@ -482,12 +294,7 @@ export default function EditBlueprintConfiguration(props) {
                                 </InputAdornment>
                               }
                               onChange={(e) => {
-                                setTurnServer(e.target.value);
-                                handleUpdateConfiguration(
-                                  "turnServer",
-                                  e.target.value,
-                                  ["customTurn", selectedDHTProxyOption]
-                                );
+                                updatePolicyData("turnServer", e.target.value);
                               }}
                             />
                           </FormControl>
@@ -517,11 +324,9 @@ export default function EditBlueprintConfiguration(props) {
                                 </InputAdornment>
                               }
                               onChange={(e) => {
-                                setTurnServerUserName(e.target.value);
-                                handleUpdateConfiguration(
+                                updatePolicyData(
                                   "turnServerUserName",
-                                  e.target.value,
-                                  ["customTurn", selectedDHTProxyOption]
+                                  e.target.value
                                 );
                               }}
                             />
@@ -544,10 +349,9 @@ export default function EditBlueprintConfiguration(props) {
                               )}
                             </InputLabel>
                             <Input
-                              disabled={!turnEnabled}
                               id="turnServerPassword"
                               placeholder="****"
-                              type={turnPassowrdVisible ? "text" : "password"}
+                              type={turnPasswordVisible ? "text" : "password"}
                               startAdornment={
                                 <InputAdornment position="start">
                                   <VpnKeyOutlinedIcon />
@@ -559,7 +363,7 @@ export default function EditBlueprintConfiguration(props) {
                                   onMouseDown={handleMouseDownPassword}
                                   onMouseUp={handleMouseUpPassword}
                                 >
-                                  {turnPassowrdVisible ? (
+                                  {turnPasswordVisible ? (
                                     <VisibilityIcon />
                                   ) : (
                                     <VisibilityOffIcon />
@@ -567,11 +371,9 @@ export default function EditBlueprintConfiguration(props) {
                                 </IconButton>
                               }
                               onChange={(e) => {
-                                setTurnServerPassword(e.target.value);
-                                handleUpdateConfiguration(
+                                updatePolicyData(
                                   "turnServerPassword",
-                                  e.target.value,
-                                  ["customTurn", selectedDHTProxyOption]
+                                  e.target.value
                                 );
                               }}
                             />
@@ -647,12 +449,7 @@ export default function EditBlueprintConfiguration(props) {
                                 </InputAdornment>
                               }
                               onChange={(e) => {
-                                setProxyServer(e.target.value);
-                                handleUpdateConfiguration(
-                                  "proxyServer",
-                                  e.target.value,
-                                  [selectedTurnOption, "customDHTProxy"]
-                                );
+                                updatePolicyData("proxyServer", e.target.value);
                               }}
                             />
                           </FormControl>
@@ -682,11 +479,9 @@ export default function EditBlueprintConfiguration(props) {
                                 </InputAdornment>
                               }
                               onChange={(e) => {
-                                setDhtProxyListUrl(e.target.value);
-                                handleUpdateConfiguration(
+                                updatePolicyData(
                                   "dhtProxyListUrl",
-                                  e.target.value,
-                                  [selectedTurnOption, "customDHTProxy"]
+                                  e.target.value
                                 );
                               }}
                             />
diff --git a/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.js b/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.js
index 4d2b9c12..3afd34ac 100644
--- a/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.js
+++ b/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from "react";
+import React, { useContext, useEffect, useState } from "react";
 import { Link, useHistory } from "react-router-dom";
 import axios from "axios";
 import i18next from "i18next";
@@ -41,10 +41,9 @@ import auth from "../../auth";
 import configApiCall from "../../api";
 import {
   api_path_get_user_directory_search,
-  api_path_get_ns_name_from_addr,
-  api_path_get_user_profile,
 } from "../../globalUrls";
 
+import { PolicyDataContext } from "./PolicyDataContext";
 
 const styles = {
   ...dashboardStyle,
@@ -103,39 +102,26 @@ const styles = {
 
 const useStyles = makeStyles(styles);
 
-
 export default function EditBlueprintPermissions(props) {
   const classes = useStyles();
   const history = useHistory();
 
+  const { policyData, updatePolicyData, snackbar, setSnackbar } = useContext(
+    PolicyDataContext
+  );
 
   const [openDrawer, setOpenDrawer] = useState(false);
   const [users, setUsers] = useState([]);
-  const [blueprintModerators, setBlueprintModerators] = useState([]);
-
-  const [videoEnabled, setVideoEnabled] = useState(true);
-  const [publicInCalls, setPublicInCalls] = useState(true);
-  const [autoAnswer, setAutoAnswer] = useState(false);
-  const [peerDiscovery, setPeerDiscovery] = useState(true);
-  const [rendezVous, setRendezVous] = useState(false);
-  const [defaultModerators, setDefaultModerators] = useState("");
-
-  const [upnpEnabled, setUpnpEnabled] = useState(true);
-  const [turnEnabled, setTurnEnabled] = useState(true);
-  const [turnServer, setTurnServer] = useState("turn.jami.net");
-  const [turnServerUserName, setTurnServerUserName] = useState("ring");
-  const [turnServerPassword, setTurnServerPassword] = useState("ring");
-  const [proxyEnabled, setProxyEnabled] = useState(false);
-  const [proxyServer, setProxyServer] = useState("dhtproxy.jami.net");
-  const [dhtProxyListUrl, setDhtProxyListUrl] = useState("");
-
-  const [allowLookup, setAllowLookup] = useState(true);
-
-  const [uiCustomization, setUiCustomization] = useState("");
 
-  const [open, setOpen] = useState(false);
-  const [message, setMessage] = useState(false);
-  const [severity, setSeverity] = useState("success");
+  const {
+    videoEnabled,
+    publicInCalls,
+    autoAnswer,
+    peerDiscovery,
+    rendezVous,
+    blueprintModerators,
+    allowLookup,
+  } = policyData;
 
   const searchUsers = (value) => {
     axios(
@@ -147,14 +133,16 @@ export default function EditBlueprintPermissions(props) {
       )
     )
       .then((response) => {
-        let profiles = [];
+        if (response.status === 204) {
+          setUsers([]);
+          return;
+        }
+
         const profilesResults = response.data.profiles;
-        profilesResults.forEach((profile) => {
-          let existingUser = false;
-          users.forEach((user) => {
-            if (profile.username === user.username) existingUser = true;
-          });
-          if (!existingUser) profiles.push(profile);
+        const usernames = users.map((user) => user.username);
+
+        const profiles = profilesResults.filter((profile) => {
+          return !usernames.includes(profile.username);
         });
         setUsers(profiles);
       })
@@ -169,167 +157,17 @@ export default function EditBlueprintPermissions(props) {
 
   useEffect(() => {
     searchUsers();
-
-    axios(
-      configApiCall(
-        api_path_blueprints + "?name=" + props.blueprintName,
-        "GET",
-        null,
-        null
-      )
-    )
-      .then((response) => {
-        let policyData = JSON.parse(response.data.policyData);
-
-        setVideoEnabled(policyData["videoEnabled"]);
-        setPublicInCalls(policyData["publicInCalls"]);
-        setAutoAnswer(policyData["autoAnswer"]);
-        setPeerDiscovery(policyData["peerDiscovery"]);
-        setRendezVous(policyData["rendezVous"]);
-        setDefaultModerators(policyData["defaultModerators"]);
-
-        setUpnpEnabled(policyData["upnpEnabled"]);
-        setTurnEnabled(policyData["turnEnabled"]);
-        setTurnServer(policyData["turnServer"]);
-        setTurnServerUserName(policyData["turnServerUserName"]);
-        setTurnServerPassword(policyData["turnServerPassword"]);
-        setProxyEnabled(policyData["proxyEnabled"]);
-        setProxyServer(policyData["proxyServer"]);
-        setDhtProxyListUrl();
-        setAllowLookup(policyData["allowLookup"]);
-
-        setUiCustomization(policyData["uiCustomization"]);
-
-        policyData["defaultModerators"].split("/").forEach((id) => {
-          if (id !== "undefined" && id !== "") {
-            axios(
-              configApiCall(api_path_get_ns_name_from_addr + id),
-              null,
-              null
-            ).then((usernameResponse) => {
-              let username = usernameResponse.data.name;
-              axios(
-                configApiCall(api_path_get_user_profile + username),
-                null,
-                null
-              ).then((userProfileResponse) => {
-                let userProfiles = blueprintModerators;
-                userProfileResponse.data["id"] = id;
-                userProfiles.push(userProfileResponse.data);
-                setBlueprintModerators(userProfiles);
-                // This state update is added to refresh the list of moderators
-                setOpenDrawer(true);
-                setOpenDrawer(false);
-              });
-            });
-          }
-        });
-      })
-      .catch((error) => {
-        console.log(
-          "Error fetching blueprint permissions : " +
-            props.blueprintName +
-            " " +
-            error
-        );
-      });
-  }, [props.blueprintName]);
-
-  const handleUpdatePermissions = (field, value) => {
-    let data = {
-      videoEnabled: videoEnabled,
-      publicInCalls: publicInCalls,
-      autoAnswer: autoAnswer,
-      peerDiscovery: peerDiscovery,
-      accountDiscovery: peerDiscovery,
-      accountPublish: peerDiscovery,
-      rendezVous: rendezVous,
-      defaultModerators: defaultModerators,
-      upnpEnabled: upnpEnabled,
-      turnEnabled: turnEnabled,
-      turnServer: turnServer,
-      turnServerUserName: turnServerUserName,
-      turnServerPassword: turnServerPassword,
-      proxyEnabled: proxyEnabled,
-      proxyServer: proxyServer,
-      dhtProxyListUrl: dhtProxyListUrl,
-      allowLookup: allowLookup,
-      uiCustomization: uiCustomization,
-    };
-
-    if (field === "peerDiscovery") {
-      data.peerDiscovery = value;
-      data.accountDiscovery = value;
-      data.accountPublish = value;
-    } else {
-      data[field] = value;
-    }
-
-    if (turnEnabled === undefined) {
-      delete data.turnEnabled;
-      delete data.turnServer;
-      delete data.turnServerUserName;
-      delete data.turnServerPassword;
-    }
-
-    if (proxyEnabled === undefined) {
-      delete data.proxyEnabled;
-      delete data.proxyServer;
-      delete data.dhtProxyListUrl;
-    }
-
-    axios(
-      configApiCall(
-        api_path_blueprints + "?name=" + props.blueprintName,
-        "PUT",
-        data,
-        null
-      )
-    )
-      .then((response) => {
-        setOpen(false);
-        setSeverity("success");
-        setOpen(true);
-        setMessage(
-          i18next.t(
-            "updated_blueprint_permissions_successfully",
-            "Blueprint permissions successfully updated."
-          )
-        );
-      })
-      .catch((error) => {
-        setOpen(false);
-        setSeverity("error");
-        setOpen(true);
-        setMessage(
-          i18next.t(
-            "error_updating_blueprint_permissions",
-            "Error occurred while updating blueprint permissions."
-          ) +
-            error +
-            "!"
-        );
-      });
-  };
+  }, []);
 
   const addModeratorToBlueprint = (user) => {
-    handleUpdatePermissions(
-      "defaultModerators",
-      defaultModerators + user.id + "/"
-    );
-    setDefaultModerators(defaultModerators + user.id + "/");
-    let newBlueprintModerators = blueprintModerators;
-    newBlueprintModerators.push(user);
-    setBlueprintModerators(newBlueprintModerators);
+    updatePolicyData("blueprintModerators", [...blueprintModerators, user]);
   };
 
   const removeModeratorFromBlueprint = (user) => {
-    let newDefaultModerators = defaultModerators.replace(user.id + "/", "");
-    handleUpdatePermissions("defaultModerators", newDefaultModerators);
-    setDefaultModerators(newDefaultModerators);
-    let newBlueprintModerators = blueprintModerators;
-    newBlueprintModerators.splice(newBlueprintModerators.indexOf(user), 1);
-    setBlueprintModerators(newBlueprintModerators);
+    const newBlueprintModerators = blueprintModerators.filter(
+      (moderator) => moderator.id !== user.id
+    );
+    updatePolicyData("blueprintModerators", newBlueprintModerators);
   };
 
   return (
@@ -377,8 +215,7 @@ export default function EditBlueprintPermissions(props) {
                                 checked={videoEnabled}
                                 color="primary"
                                 onChange={(e) => {
-                                  setVideoEnabled(e.target.checked);
-                                  handleUpdatePermissions(
+                                  updatePolicyData(
                                     "videoEnabled",
                                     e.target.checked
                                   );
@@ -405,8 +242,7 @@ export default function EditBlueprintPermissions(props) {
                                 checked={publicInCalls}
                                 color="primary"
                                 onChange={(e) => {
-                                  setPublicInCalls(e.target.checked);
-                                  handleUpdatePermissions(
+                                  updatePolicyData(
                                     "publicInCalls",
                                     e.target.checked
                                   );
@@ -433,8 +269,7 @@ export default function EditBlueprintPermissions(props) {
                                 checked={autoAnswer}
                                 color="primary"
                                 onChange={(e) => {
-                                  setAutoAnswer(e.target.checked);
-                                  handleUpdatePermissions(
+                                  updatePolicyData(
                                     "autoAnswer",
                                     e.target.checked
                                   );
@@ -466,8 +301,7 @@ export default function EditBlueprintPermissions(props) {
                                 checked={peerDiscovery}
                                 color="primary"
                                 onChange={(e) => {
-                                  setPeerDiscovery(e.target.checked);
-                                  handleUpdatePermissions(
+                                  updatePolicyData(
                                     "peerDiscovery",
                                     e.target.checked
                                   );
@@ -502,8 +336,7 @@ export default function EditBlueprintPermissions(props) {
                                 checked={allowLookup}
                                 color="primary"
                                 onChange={(e) => {
-                                  setAllowLookup(e.target.checked);
-                                  handleUpdatePermissions(
+                                  updatePolicyData(
                                     "allowLookup",
                                     e.target.checked
                                   );
@@ -542,8 +375,7 @@ export default function EditBlueprintPermissions(props) {
                   checked={rendezVous}
                   color="primary"
                   onChange={(e) => {
-                    setRendezVous(e.target.checked);
-                    handleUpdatePermissions("rendezVous", e.target.checked);
+                    updatePolicyData("rendezVous", e.target.checked);
                   }}
                   name="rendezVous"
                   inputProps={{ "aria-label": "secondary checkbox" }}
diff --git a/jams-react-client/src/views/Blueprint/EditBlueprintUi.js b/jams-react-client/src/views/Blueprint/EditBlueprintUi.js
index 2822a1ea..024b348c 100644
--- a/jams-react-client/src/views/Blueprint/EditBlueprintUi.js
+++ b/jams-react-client/src/views/Blueprint/EditBlueprintUi.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from "react";
+import React, { useContext, useState } from "react";
 
 import { makeStyles } from "@material-ui/core/styles";
 import Grid from "@material-ui/core/Grid";
@@ -14,28 +14,16 @@ import GridContainer from "components/Grid/GridContainer.js";
 
 import { hexToRgb, blackColor } from "assets/jss/material-dashboard-react.js";
 
-import { api_path_blueprints, api_path_get_image, api_path_post_image } from "../../globalUrls";
+import { api_path_get_image, api_path_post_image } from "../../globalUrls";
 
 import dashboardStyle from "assets/jss/material-dashboard-react/views/dashboardStyle.js";
 
 import i18next from "i18next";
+
 import CustomUiPreview from "components/CustomUiPreview/CustomUiPreview";
 import EditBlueprintUiForm from "./EditBlueprintUiForm";
-import configApiCall from "api";
-import axios from "axios";
-
-const DEFAULT_UI_CUSTOMIZATION = {
-  hasTitle: true,
-  customTitle: "",
-  hasDescription: true,
-  customDescription: "",
-  hasTips: false,
-  hasBackgroundCustom: false,
-  customBackgroundColor: "",
-  customBackgroundImageUrl: "",
-  hasLogo: true,
-  customLogoImageUrl: "",
-};
+import { DEFAULT_UI_CUSTOMIZATION } from "./policyData.constants";
+import { PolicyDataContext } from "./PolicyDataContext";
 
 const styles = {
   ...dashboardStyle,
@@ -105,89 +93,23 @@ const useStyles = makeStyles(styles);
 export default function EditBlueprintUi({ blueprintName }) {
   const classes = useStyles();
 
-  const [policyData, setPolicyData] = useState({
-    uiCustomization: DEFAULT_UI_CUSTOMIZATION,
-  });
-  let { uiCustomization } = policyData;
+  const { policyData, updatePolicyData, snackbar, setSnackbar } = useContext(
+    PolicyDataContext
+  );
+  const { uiCustomization } = policyData;
 
   const setUiCustomization = (ui) => {
-    setPolicyData({ ...policyData, uiCustomization: ui });
+    updatePolicyData("uiCustomization", ui);
   };
 
   const [oldUiCustomization, setOldUiCustomization] = useState(uiCustomization);
   const [opacity, setOpacity] = useState(0);
 
-  const [open, setOpen] = useState(false);
-  const [message, setMessage] = useState(false);
-  const [severity, setSeverity] = useState("success");
-
-  const [bgFile, setBgFile] = useState(null);
-  const [logoFile, setLogoFile] = useState(null);
-
-  useEffect(() => {
-    axios(
-      configApiCall(
-        api_path_blueprints + "?name=" + blueprintName,
-        "GET",
-        null,
-        null
-      )
-    )
-      .then((response) => {
-        let policyDataResponse = JSON.parse(response.data.policyData);
-        policyDataResponse.uiCustomization = JSON.parse(
-          policyDataResponse.uiCustomization
-        );
-
-        setPolicyData(policyDataResponse);
-
-        const { uiCustomization: uiCustomizationResponse } = policyDataResponse;
-
-        if (uiCustomizationResponse) {
-          if (
-            uiCustomizationResponse.customBackgroundImageUrl &&
-            uiCustomizationResponse.hasBackgroundCustom
-          ) {
-            //Get the name of the file from the url
-            let urlParts = uiCustomizationResponse.customBackgroundImageUrl.split(
-              "/"
-            );
-            if (urlParts.length) {
-              setBgFile(urlParts[urlParts.length - 1]);
-            }
-          }
-          if (
-            uiCustomizationResponse.customLogoImageUrl &&
-            uiCustomizationResponse.hasLogo
-          ) {
-            //Get the name of the file from the url
-            let urlParts = uiCustomizationResponse.customLogoImageUrl.split(
-              "/"
-            );
-            if (urlParts.length) {
-              setLogoFile(urlParts[urlParts.length - 1]);
-            }
-          }
-
-          // refresh the preview
-          setOldUiCustomization(uiCustomizationResponse);
-          console.log("Found ui customization in the policy data");
-        } else {
-          setUiCustomization(DEFAULT_UI_CUSTOMIZATION);
-          console.log("Did not find ui customization in the policy data");
-        }
-      })
-      .catch((error) => {
-        setUiCustomization(DEFAULT_UI_CUSTOMIZATION);
-
-        console.log(
-          `Error fetching blueprint permissions : ${blueprintName} ${error}`
-        );
-      });
-  }, [blueprintName]);
-
   const handleUpdateUi = (field, value) => {
     const newUiCustomization = { ...uiCustomization, [field]: value };
+    const newUi = newUiCustomization.isCustomizationEnabled
+      ? newUiCustomization
+      : DEFAULT_UI_CUSTOMIZATION;
 
     if (field === "title" || field === "description" || field === "backgroundColor") {
       // Don't fade in for those fields
@@ -197,44 +119,12 @@ export default function EditBlueprintUi({ blueprintName }) {
       // then the old preview is updated, and the preview on top fades out
       setOpacity(1);
       setTimeout(() => {
-        setOldUiCustomization(newUiCustomization);
+        setOldUiCustomization(newUi);
         setOpacity(0);
       }, 250);
     }
 
-    axios(
-      configApiCall(
-        api_path_blueprints + "?name=" + blueprintName,
-        "PUT",
-        { ...policyData, uiCustomization: JSON.stringify(newUiCustomization) },
-        null
-      )
-    )
-      .then(() => {
-        setOpen(false);
-        setSeverity("success");
-        setOpen(true);
-        setMessage(
-          i18next.t(
-            "updated_blueprint_permissions_successfully",
-            "Blueprint permissions successfully updated."
-          )
-        );
-      })
-      .catch((error) => {
-        console.error(error);
-        setOpen(false);
-        setSeverity("error");
-        setOpen(true);
-        setMessage(
-          i18next.t(
-            "error_updating_blueprint_permissions",
-            "Error occurred while updating blueprint permissions."
-          ) +
-            error +
-            "!"
-        );
-      });
+    updatePolicyData("uiCustomization", newUiCustomization);
   };
 
   const handleImgDrop = (acceptedFiles, imgType) => {
@@ -260,7 +150,6 @@ export default function EditBlueprintUi({ blueprintName }) {
             backgroundUrl: newUrl,
             backgroundColor: "",
           });
-          setBgFile(acceptedFiles[0].name);
           handleUpdateUi("backgroundUrl", newUrl);
         } else if (imgType === "logo") {
           let newUrl =
@@ -273,7 +162,6 @@ export default function EditBlueprintUi({ blueprintName }) {
             ...uiCustomization,
             logoUrl: newUrl,
           });
-          setLogoFile(acceptedFiles[0].name);
           handleUpdateUi("logoUrl", newUrl);
         }
       })
@@ -282,34 +170,6 @@ export default function EditBlueprintUi({ blueprintName }) {
       });
   };
 
-  const handleDeleteLogo = (e) => {
-    setLogoFile(null);
-    setUiCustomization({
-      ...uiCustomization,
-      customLogoImageUrl: null,
-    });
-    e.stopPropagation();
-  };
-
-  const handleDeleteBg = (e) => {
-    setBgFile(null);
-    setUiCustomization({
-      ...uiCustomization,
-      customBackgroundImageUrl: null,
-    });
-    e.preventDefault();
-    e.stopPropagation();
-  };
-
-  const handleColorChange = (color) => {
-    setUiCustomization({
-      ...uiCustomization,
-      customBackgroundColor: color.hex,
-      customBackgroundImageUrl: null,
-    });
-    setBgFile(null);
-  };
-
   return (
     <div>
       <BlueprintSnackbar snackbar={snackbar} setSnackbar={setSnackbar} />
@@ -339,7 +199,7 @@ export default function EditBlueprintUi({ blueprintName }) {
                       >
                         <CustomUiPreview
                           opacity={opacity}
-                          uiCustomization={policyData.uiCustomization}
+                          uiCustomization={uiCustomization}
                         />
                         <CustomUiPreview
                           isOldPreview
@@ -349,16 +209,10 @@ export default function EditBlueprintUi({ blueprintName }) {
                       </Grid>
                       <Grid item xs={12} sm={12} md={12}>
                         <EditBlueprintUiForm
-                          uiCustomization={policyData.uiCustomization}
+                          uiCustomization={uiCustomization}
                           setUiCustomization={setUiCustomization}
                           handleUpdateUi={handleUpdateUi}
                           handleImgDrop={handleImgDrop}
-                          setLogoFile={setLogoFile}
-                          handleDeleteLogo={handleDeleteLogo}
-                          bgFile={bgFile}
-                          setBgFile={setBgFile}
-                          handleDeleteBg={handleDeleteBg}
-                          handleColorChange={handleColorChange}
                         />
                       </Grid>
                     </Grid>
diff --git a/jams-react-client/src/views/Blueprint/PolicyDataContext.js b/jams-react-client/src/views/Blueprint/PolicyDataContext.js
new file mode 100644
index 00000000..131db22a
--- /dev/null
+++ b/jams-react-client/src/views/Blueprint/PolicyDataContext.js
@@ -0,0 +1,61 @@
+import React, { createContext, useEffect, useState } from "react";
+
+import axios from "axios";
+
+import configApiCall from "../../api";
+import { api_path_blueprints } from "../../globalUrls";
+import { parsePolicyData } from "./parsePolicyData";
+import { _updatePolicyData } from "./updatePolicyData";
+import { DEFAULT_POLICY_DATA } from "./policyData.constants";
+
+export const PolicyDataContext = createContext(null);
+
+export const PolicyDataContextProvider = ({ blueprintName, children }) => {
+  const [policyData, setPolicyData] = useState(DEFAULT_POLICY_DATA);
+  const [snackbar, setSnackbar] = useState({
+    open: false,
+    severity: "success",
+    message: "",
+  });
+
+  useEffect(() => {
+    axios(
+      configApiCall(
+        api_path_blueprints + "?name=" + blueprintName,
+        "GET",
+        null,
+        null
+      )
+    )
+      .then((response) => {
+        console.log("GET", response.data.policyData);
+        return parsePolicyData(response.data.policyData);
+      })
+      .then((policyData) => {
+        console.log("PARSED", policyData);
+        setPolicyData(policyData);
+      })
+      .catch((error) => {
+        console.log("Error fetching blueprint permissions : " + error);
+      });
+  }, [blueprintName]);
+
+  const updatePolicyData = (field, value) => {
+    _updatePolicyData(
+      blueprintName,
+      policyData,
+      setPolicyData,
+      field,
+      value,
+      setSnackbar
+    );
+  };
+
+  return (
+    <PolicyDataContext.Provider
+      value={{ policyData, updatePolicyData, snackbar, setSnackbar }}
+    >
+      {children}
+    </PolicyDataContext.Provider>
+  );
+};
diff --git a/jams-react-client/src/views/Blueprint/parsePolicyData.js b/jams-react-client/src/views/Blueprint/parsePolicyData.js
new file mode 100644
index 00000000..f5da5f55
--- /dev/null
+++ b/jams-react-client/src/views/Blueprint/parsePolicyData.js
@@ -0,0 +1,101 @@
+import axios from "axios";
+
+import configApiCall from "../../api";
+import {
+  api_path_get_ns_name_from_addr,
+  api_path_get_user_profile,
+} from "../../globalUrls";
+import { DEFAULT_UI_CUSTOMIZATION } from "./policyData.constants";
+
+const getModerators = async (defaultModerators) => {
+  const moderators = [];
+  const ids = defaultModerators.split("/").filter((id) => id !== "");
+
+  for (const id of ids) {
+    const usernameResponse = await axios(
+      configApiCall(api_path_get_ns_name_from_addr + id, "GET", null, null)
+    );
+    const username = usernameResponse.data.name;
+
+    const userProfileResponse = await axios(
+      configApiCall(api_path_get_user_profile + username, "GET", null, null)
+    );
+    userProfileResponse.data["id"] = id;
+    moderators.push(userProfileResponse.data);
+  }
+
+  return moderators;
+};
+
+const setConfigurationSettings = (policyData) => {
+  const { turnEnabled, proxyEnabled } = policyData;
+
+  if (turnEnabled === true) {
+    policyData.selectedTurnOption = "customTurn";
+  } else if (turnEnabled === false) {
+    policyData.selectedTurnOption = "disabledTurn";
+  } else {
+    policyData.selectedTurnOption = "defaultTurn";
+  }
+  delete policyData.turnEnabled;
+
+  if (proxyEnabled === true) {
+    policyData.selectedDHTProxyOption = "customDHTProxy";
+  } else if (proxyEnabled === false) {
+    policyData.selectedDHTProxyOption = "disabledDHTProxy";
+  } else {
+    policyData.selectedDHTProxyOption = "defaultDHTProxy";
+  }
+  delete policyData.proxyEnabled;
+
+  return policyData;
+};
+
+const setCustomizationSettings = (policyData) => {
+  if (!policyData.uiCustomization) {
+    policyData.uiCustomization = DEFAULT_UI_CUSTOMIZATION;
+    return policyData;
+  }
+
+  const result = JSON.parse(policyData.uiCustomization);
+  const ui = { isCustomizationEnabled: true };
+
+  ui.hasTitle = result.title !== "";
+  ui.title = result.title;
+  ui.hasDescription = result.description !== "";
+  ui.description = result.description;
+  ui.hasTips = result.hasTips;
+
+  if (result.backgroundType === "default") {
+    ui.hasBackground = false;
+  } else if (result.backgroundType === "color") {
+    ui.hasBackground = true;
+    ui.backgroundColor = result.backgroundColorOrUrl;
+  } else if (result.backgroundType === "image") {
+    ui.hasBackground = true;
+    ui.backgroundUrl = result.backgroundColorOrUrl;
+  }
+
+  // Get the name of the file from the url
+  ui.hasLogo = result.logoUrl !== "";
+  if (ui.hasLogo) {
+    ui.logoUrl = result.logoUrl;
+  }
+
+  policyData.uiCustomization = ui;
+  return policyData;
+};
+
+export const parsePolicyData = async (data) => {
+  let policyData = JSON.parse(data);
+
+  policyData.blueprintModerators = await getModerators(
+    policyData.defaultModerators
+  );
+  delete policyData.defaultModerators;
+
+  policyData = setConfigurationSettings(policyData);
+  policyData = setCustomizationSettings(policyData);
+
+  return policyData;
+};
diff --git a/jams-react-client/src/views/Blueprint/policyData.constants.js b/jams-react-client/src/views/Blueprint/policyData.constants.js
new file mode 100644
index 00000000..967baebb
--- /dev/null
+++ b/jams-react-client/src/views/Blueprint/policyData.constants.js
@@ -0,0 +1,39 @@
+export const DEFAULT_UI_CUSTOMIZATION = {
+  isCustomizationEnabled: false,
+  hasTitle: true,
+  title: "",
+  hasDescription: true,
+  description: "",
+  hasTips: false,
+
+  hasBackground: false,
+  backgroundColor: "",
+  backgroundUrl: "",
+
+  hasLogo: true,
+  logoUrl: "",
+};
+
+export const DEFAULT_POLICY_DATA = {
+  videoEnabled: true,
+  publicInCalls: false,
+  autoAnswer: false,
+  peerDiscovery: true,
+  allowLookup: true,
+
+  rendezVous: false,
+  blueprintModerators: [],
+
+  upnpEnabled: true,
+
+  selectedTurnOption: "defaultTurn",
+  turnServer: "turn.jami.net",
+  turnServerUserName: "ring",
+  turnServerPassword: "ring",
+
+  selectedDHTProxyOption: "defaultDHTProxy",
+  proxyServer: "dhtproxy.jami.net",
+  dhtProxyListUrl: "",
+
+  uiCustomization: DEFAULT_UI_CUSTOMIZATION,
+};
diff --git a/jams-react-client/src/views/Blueprint/updatePolicyData.js b/jams-react-client/src/views/Blueprint/updatePolicyData.js
new file mode 100644
index 00000000..d5eafb0f
--- /dev/null
+++ b/jams-react-client/src/views/Blueprint/updatePolicyData.js
@@ -0,0 +1,168 @@
+import axios from "axios";
+import i18next from "i18next";
+
+import configApiCall from "api";
+import { api_path_blueprints } from "globalUrls";
+import { debounce } from "lodash";
+
+const updatePerms = (data, field, value) => {
+  data.defaultModerators = data.blueprintModerators
+    .map((moderator) => moderator.id)
+    .join("/");
+  delete data.blueprintModerators;
+
+  if (field === "peerDiscovery") {
+    data.accountDiscovery = value;
+    data.accountPublish = value;
+  }
+
+  return data;
+};
+
+const updateConfig = (data) => {
+  const { selectedTurnOption, selectedDHTProxyOption } = data;
+  // if undefined, it means we want the default values
+  if (selectedTurnOption !== "customTurn") {
+    delete data.turnServer;
+    delete data.turnServerUserName;
+    delete data.turnServerPassword;
+  }
+
+  if (selectedDHTProxyOption !== "customDHTProxy") {
+    delete data.proxyServer;
+    delete data.dhtProxyListUrl;
+  }
+
+  if (selectedTurnOption === "customTurn") {
+    data.turnEnabled = true;
+  } else if (selectedTurnOption === "disabledTurn") {
+    data.turnEnabled = false;
+  }
+  delete data.selectedTurnOption;
+
+  if (selectedDHTProxyOption === "customDHTProxy") {
+    data.proxyEnabled = true;
+  } else if (selectedDHTProxyOption === "disabledDHTProxy") {
+    data.proxyEnabled = false;
+  }
+  delete data.selectedDHTProxyOption;
+
+  return data;
+};
+
+const updateUiCustomization = (data) => {
+  if (data.uiCustomization.isCustomizationEnabled === false) {
+    return data;
+  }
+
+  const {
+    hasTitle,
+    title,
+    hasDescription,
+    description,
+    hasTips,
+
+    hasBackground,
+    backgroundColor,
+    backgroundUrl,
+
+    hasLogo,
+    logoUrl,
+  } = data.uiCustomization;
+  const ui = {};
+
+  if (hasTitle === false) {
+    ui.title = "";
+  } else if (title !== "") {
+    ui.title = title;
+  }
+
+  if (hasDescription === false) {
+    ui.description = "";
+  } else if (description !== "") {
+    ui.description = description;
+  }
+
+  ui.areTipsEnabled = hasTips;
+
+  if (hasBackground === false) {
+    ui.backgroundType = "default";
+  } else if (backgroundUrl !== "") {
+    ui.backgroundType = "image";
+    ui.backgroundColorOrUrl = backgroundUrl;
+  } else if (backgroundColor !== "") {
+    ui.backgroundType = "color";
+    ui.backgroundColorOrUrl = backgroundColor;
+  } else {
+    ui.backgroundType = "default";
+  }
+
+  if (hasLogo === false) {
+    ui.logoUrl = "";
+  } else if (logoUrl !== "") {
+    ui.logoUrl = logoUrl;
+  }
+
+  console.log("updateUiCustomization", ui);
+  data.uiCustomization = JSON.stringify(ui);
+  return data;
+};
+
+const sendPutRequest = (blueprintName, data, setSnackbar) => {
+  console.log("PUT", data);
+
+  axios(
+    configApiCall(
+      api_path_blueprints + "?name=" + blueprintName,
+      "PUT",
+      data,
+      null
+    )
+  )
+    .then((response) => {
+      const message = i18next.t(
+        "updated_blueprint_permissions_successfully",
+        "Blueprint permissions successfully updated."
+      );
+      setSnackbar({
+        open: true,
+        severity: "success",
+        message,
+      });
+    })
+    .catch((error) => {
+      const msg = i18next.t(
+        "error_updating_blueprint_permissions",
+        "Error occurred while updating blueprint permissions."
+      );
+      setSnackbar({
+        open: true,
+        severity: "error",
+        message: `${msg} ${error}!`,
+      });
+    });
+};
+
+const debouncedSendPutRequest = debounce(sendPutRequest, 200, {
+  trailing: true,
+});
+
+export const _updatePolicyData = (
+  blueprintName,
+  policyData,
+  setPolicyData,
+  field,
+  value,
+  setSnackbar
+) => {
+  setPolicyData((state) => ({ ...state, [field]: value }));
+
+  let data = { ...policyData, [field]: value };
+  data = updatePerms(data, field, value);
+  data = updateConfig(data);
+  data = updateUiCustomization(data);
+
+  console.log("updatePolicyData", { field, value });
+
+  debouncedSendPutRequest(blueprintName, data, setSnackbar);
+};
-- 
GitLab