From 705cd9da2d67b1c686f8d51d181df1a997a34bb6 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Thu, 4 Feb 2021 08:03:40 +0100
Subject: [PATCH 01/46] Added Safari registration

---
 .env.development                    |  6 +++---
 package.json                        |  2 +-
 src/components/devices/AddDevice.js | 19 ++++++++++++++++---
 src/utils/user-agent.js             | 14 ++++++++++++--
 4 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/.env.development b/.env.development
index b22fa355..5bfa60f8 100644
--- a/.env.development
+++ b/.env.development
@@ -1,4 +1,4 @@
-REACT_APP_BASE_URL=https://localhost:8080
-REACT_APP_OAUTH_REDIRECT_URL=https://localhost:3000/redirect
-REACT_APP_OAUTH_LOGOUT_URL=https://localhost:3000/logout
+REACT_APP_BASE_URL=https://192.168.0.168:8080
+REACT_APP_OAUTH_REDIRECT_URL=https://192.168.0.168:3000/redirect
+REACT_APP_OAUTH_LOGOUT_URL=https://192.168.0.168:3000/logout
 REACT_APP_NODE_TLS_REJECT_UNAUTHORIZED=0
diff --git a/package.json b/package.json
index da99fe68..ef99f575 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
   "scripts": {
     "build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/",
     "watch-css": "npm run build-css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive",
-    "start-js": "react-scripts start",
+    "start-js": "HOST=0.0.0.0 react-scripts start",
     "start": "HTTPS=true npm-run-all -p watch-css start-js",
     "build-js": "react-scripts build",
     "build": "npm-run-all build-css build-js",
diff --git a/src/components/devices/AddDevice.js b/src/components/devices/AddDevice.js
index 07253f2f..5107cd26 100644
--- a/src/components/devices/AddDevice.js
+++ b/src/components/devices/AddDevice.js
@@ -6,7 +6,8 @@ import {Form, Modal, Button, Label, Message, Segment} from 'semantic-ui-react';
 import * as deviceActions from 'actions/devices';
 import * as showSnackBarActionCreators from '../../common/actions/Snackbar';
 import ServiceWorkerRegistration from './ServiceWorkerRegistration';
-import getClientInformation from '../../utils/user-agent';
+import SafariRegistration from './SafariRegistration';
+import {getClientInformation, isSafari} from '../../utils/user-agent';
 import './AddDevice.scss';
 
 const clientInformation = getClientInformation();
@@ -14,12 +15,23 @@ const clientInformation = getClientInformation();
 // Full list
 // const deviceTypesEnum = ['BROWSER', 'ANDROID', 'IOS', 'WINDOWS', 'LINUX', 'MAC', 'MAIL'];
 // Current supported list
-const deviceTypesEnum = ['BROWSER', 'WINDOWS', 'MAIL'];
+const deviceTypesEnum = ['BROWSER', 'APP', 'MAIL'];
+const deviceSubTypesEnum = [
+  'SAFARI',
+  'OTHER',
+  'IOS',
+  'ANDROID',
+  'WINDOWS',
+  'LINUX',
+  'MAC',
+  'PRIMARY',
+];
 
 const AddDevice = ({createDevice, showSnackbar, loading}) => {
   const [deviceName, setDeviceName] = useState(clientInformation);
   const [deviceInfo, setDeviceInfo] = useState(navigator.userAgent);
   const [deviceType, setDeviceType] = useState('BROWSER');
+  const [deviceSubType, setDeviceSubType] = useState(isSafari() ? 'SAFARI' : 'OTHER');
   const [deviceToken, setDeviceToken] = useState('');
 
   const [modalOpen, setModalOpen] = useState(false);
@@ -29,7 +41,8 @@ const AddDevice = ({createDevice, showSnackbar, loading}) => {
   const resetFormValues = () => {
     setDeviceName(clientInformation); // initialize with platform name, let user edit for easy remembering
     setDeviceInfo(navigator.userAgent);
-    setDeviceType('BROWSER'); // Browser only for now.
+    setDeviceType('BROWSER');
+    setDeviceSubType(isSafari() ? 'SAFARI' : 'OTHER');
     setDeviceToken('');
     setModalOpen(false);
   };
diff --git a/src/utils/user-agent.js b/src/utils/user-agent.js
index 5ec02a08..7e19ce3f 100644
--- a/src/utils/user-agent.js
+++ b/src/utils/user-agent.js
@@ -1,7 +1,7 @@
 const os = ['Win', 'Linux', 'Mac', 'Android', 'iPhone']; // add OS values
 const br = ['Chrome', 'Firefox', 'MSIE', 'Edge', 'Safari', 'Opera']; // add browser values
 
-function getClientInformation() {
+export function getClientInformation() {
   return (
     (os.find(v => navigator.platform.indexOf(v) >= 0) || 'Other') +
     ' ' +
@@ -9,4 +9,14 @@ function getClientInformation() {
   );
 }
 
-export default getClientInformation;
+export function isSafari() {
+  return (
+    navigator.vendor &&
+    navigator.vendor.indexOf('Apple') > -1 &&
+    navigator.userAgent &&
+    navigator.userAgent.indexOf('CriOS') === -1 &&
+    navigator.userAgent.indexOf('FxiOS') === -1
+  );
+}
+
+//export default getClientInformation;
-- 
GitLab


From fe0636ed272c5a24d237d8032a0cc8df82cb80d5 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Thu, 4 Feb 2021 08:04:14 +0100
Subject: [PATCH 02/46] Added Safari registration

---
 src/components/devices/SafariRegistration.js | 103 +++++++++++++++++++
 1 file changed, 103 insertions(+)
 create mode 100644 src/components/devices/SafariRegistration.js

diff --git a/src/components/devices/SafariRegistration.js b/src/components/devices/SafariRegistration.js
new file mode 100644
index 00000000..8a310b47
--- /dev/null
+++ b/src/components/devices/SafariRegistration.js
@@ -0,0 +1,103 @@
+import React, {useState, useEffect} from 'react';
+import {Form, Message, Checkbox, Popup} from 'semantic-ui-react';
+
+function SafariRegistration({onSubscription, onLoadStatus}) {
+  const [status, setStatus] = useState([]);
+  const [errormsg, setErrormsg] = useState([]);
+  const [isEnabled, setIsEnabled] = useState(false);
+
+  useEffect(() => {
+    // Ensure that the user can receive Safari Push Notifications.
+    if ('safari' in window && 'pushNotification' in window.safari) {
+      var permissionData = window.safari.pushNotification.permission(
+        process.env.REACT_APP_APPLE_WEBSITEPUSHID
+      );
+      if (permissionData.permission === 'granted') {
+        setIsEnabled(true);
+        onLoadStatus(true);
+        onSubscription(permissionData.deviceToken);
+        setStatus(prevState => [...prevState, 'Registration is already active.']);
+      } else if (permissionData.permission === 'denied') {
+        onLoadStatus(false);
+        setErrormsg(prevState => [
+          ...prevState,
+          'Permission for notifications on this site is denied. Check your preferences.',
+        ]);
+      } else onLoadStatus(false);
+    } else {
+      setErrormsg(prevState => [...prevState, 'Not Safari or no push support in this version !']);
+      onLoadStatus(false);
+    }
+  }, [onLoadStatus, onSubscription]);
+
+  function checkRemotePermission(permissionData) {
+    if (permissionData.permission === 'default') {
+      window.safari.pushNotification.requestPermission(
+        process.env.REACT_APP_APPLE_WEBSERVICEURL, // The web service URL.
+        process.env.REACT_APP_APPLE_WEBSITEPUSHID, // The Website Push ID.
+        {anonid: 'anonid-123456-anonid'}, // Data that you choose to send to your server to help you identify the user.
+        checkRemotePermission // The callback function.
+      );
+    } else if (permissionData.permission === 'denied') {
+      // The user said no.
+      setErrormsg(prevState => [
+        ...prevState,
+        'Permission for notifications on this site is denied. Check your preferences.',
+      ]);
+    } else if (permissionData.permission === 'granted') {
+      // The web service URL is a valid push provider, and the user said yes.
+      // permissionData.deviceToken is available to use.
+      setIsEnabled(true);
+      setStatus(prevState => [...prevState, 'Registration completed.']);
+      onSubscription(permissionData.deviceToken);
+    }
+  }
+
+  function registerSafari() {
+    // console.log('Service worker is active.');
+    // setStatus(prevState => [...prevState, 'Service worker is active.']);
+    // setErrormsg(prevState => [...prevState, 'No notification permission granted!']);
+    checkRemotePermission(window.safari.pushNotification.permission('web.ch.cern.notifications'));
+  }
+
+  function unRegisterSafari() {
+    setErrormsg(prevState => [
+      ...prevState,
+      'To remove registration, open Safari Preferences / Notifications, and Remove CERN Notification.',
+    ]);
+  }
+
+  function toggleRegistration() {
+    if (isEnabled) unRegisterSafari();
+    else registerSafari();
+  }
+
+  return (
+    <Form.Field>
+      <label>Register for Push Notifications</label>
+      <Popup
+        trigger={<Checkbox checked={isEnabled} onChange={toggleRegistration} toggle />}
+        content="Enable Push Notifications"
+        position="right center"
+      />
+      {status && status.length > 0 && (
+        <Message size="tiny">
+          <Message.Header>Status</Message.Header>
+          {status.map((str, i) => (
+            <div key={i}>{str} </div>
+          ))}
+        </Message>
+      )}
+      {errormsg && errormsg.length > 0 && (
+        <Message negative size="tiny">
+          <Message.Header>Error</Message.Header>
+          {errormsg.map((str, i) => (
+            <div key={i}>{str} </div>
+          ))}
+        </Message>
+      )}
+    </Form.Field>
+  );
+}
+
+export default SafariRegistration;
-- 
GitLab


From fa6e4bb6cb009839474aa17454246c2ddc2c0b53 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Thu, 4 Feb 2021 16:47:34 +0100
Subject: [PATCH 03/46] Added uuid for device and safari ref

---
 .env.development                             |  6 ++---
 package-lock.json                            | 28 +++++++++++++++++---
 package.json                                 |  3 ++-
 src/components/devices/AddDevice.js          |  2 ++
 src/components/devices/SafariRegistration.js |  5 ++--
 5 files changed, 34 insertions(+), 10 deletions(-)

diff --git a/.env.development b/.env.development
index 5bfa60f8..b22fa355 100644
--- a/.env.development
+++ b/.env.development
@@ -1,4 +1,4 @@
-REACT_APP_BASE_URL=https://192.168.0.168:8080
-REACT_APP_OAUTH_REDIRECT_URL=https://192.168.0.168:3000/redirect
-REACT_APP_OAUTH_LOGOUT_URL=https://192.168.0.168:3000/logout
+REACT_APP_BASE_URL=https://localhost:8080
+REACT_APP_OAUTH_REDIRECT_URL=https://localhost:3000/redirect
+REACT_APP_OAUTH_LOGOUT_URL=https://localhost:3000/logout
 REACT_APP_NODE_TLS_REJECT_UNAUTHORIZED=0
diff --git a/package-lock.json b/package-lock.json
index a2cf62c1..27e10c50 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12723,7 +12723,8 @@
     "prettier": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
-      "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q=="
+      "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
+      "dev": true
     },
     "prettier-linter-helpers": {
       "version": "1.0.0",
@@ -13921,6 +13922,11 @@
           "version": "6.5.2",
           "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
           "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+        },
+        "uuid": {
+          "version": "3.4.0",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+          "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
         }
       }
     },
@@ -14975,6 +14981,13 @@
         "faye-websocket": "^0.10.0",
         "uuid": "^3.4.0",
         "websocket-driver": "0.6.5"
+      },
+      "dependencies": {
+        "uuid": {
+          "version": "3.4.0",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+          "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+        }
       }
     },
     "sockjs-client": {
@@ -16393,9 +16406,9 @@
       "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
     },
     "uuid": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
-      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+      "version": "8.3.2",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
     },
     "v8-compile-cache": {
       "version": "2.1.1",
@@ -17039,6 +17052,13 @@
       "requires": {
         "ansi-colors": "^3.0.0",
         "uuid": "^3.3.2"
+      },
+      "dependencies": {
+        "uuid": {
+          "version": "3.4.0",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+          "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+        }
       }
     },
     "webpack-manifest-plugin": {
diff --git a/package.json b/package.json
index ef99f575..01d4134d 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,8 @@
     "redux-thunk": "^2.3.0",
     "register-service-worker": "^1.7.2",
     "semantic-ui-react": "^2.0.0",
-    "serve": "^11.3.0"
+    "serve": "^11.3.0",
+    "uuid": "^8.3.2"
   },
   "scripts": {
     "build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/",
diff --git a/src/components/devices/AddDevice.js b/src/components/devices/AddDevice.js
index 5107cd26..5c88e73e 100644
--- a/src/components/devices/AddDevice.js
+++ b/src/components/devices/AddDevice.js
@@ -32,6 +32,7 @@ const AddDevice = ({createDevice, showSnackbar, loading}) => {
   const [deviceInfo, setDeviceInfo] = useState(navigator.userAgent);
   const [deviceType, setDeviceType] = useState('BROWSER');
   const [deviceSubType, setDeviceSubType] = useState(isSafari() ? 'SAFARI' : 'OTHER');
+  const [deviceUuid, setdeviceUuid] = useState(uuidv4());
   const [deviceToken, setDeviceToken] = useState('');
 
   const [modalOpen, setModalOpen] = useState(false);
@@ -43,6 +44,7 @@ const AddDevice = ({createDevice, showSnackbar, loading}) => {
     setDeviceInfo(navigator.userAgent);
     setDeviceType('BROWSER');
     setDeviceSubType(isSafari() ? 'SAFARI' : 'OTHER');
+    setdeviceUuid(uuidv4());
     setDeviceToken('');
     setModalOpen(false);
   };
diff --git a/src/components/devices/SafariRegistration.js b/src/components/devices/SafariRegistration.js
index 8a310b47..6de7b8e6 100644
--- a/src/components/devices/SafariRegistration.js
+++ b/src/components/devices/SafariRegistration.js
@@ -1,7 +1,7 @@
 import React, {useState, useEffect} from 'react';
 import {Form, Message, Checkbox, Popup} from 'semantic-ui-react';
 
-function SafariRegistration({onSubscription, onLoadStatus}) {
+function SafariRegistration({onSubscription, onLoadStatus, deviceUuid}) {
   const [status, setStatus] = useState([]);
   const [errormsg, setErrormsg] = useState([]);
   const [isEnabled, setIsEnabled] = useState(false);
@@ -35,7 +35,8 @@ function SafariRegistration({onSubscription, onLoadStatus}) {
       window.safari.pushNotification.requestPermission(
         process.env.REACT_APP_APPLE_WEBSERVICEURL, // The web service URL.
         process.env.REACT_APP_APPLE_WEBSITEPUSHID, // The Website Push ID.
-        {anonid: 'anonid-123456-anonid'}, // Data that you choose to send to your server to help you identify the user.
+        // TODO: Encrypt uuid
+        {anonid: deviceUuid}, // Data that you choose to send to your server to help you identify the user.
         checkRemotePermission // The callback function.
       );
     } else if (permissionData.permission === 'denied') {
-- 
GitLab


From 5b86d3d63e562fc01b6101d3b371aec115c67710 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Fri, 5 Feb 2021 16:49:36 +0100
Subject: [PATCH 04/46] Changed notification list and notiifcation display

---
 package.json                                  |   1 +
 src/common/pages/MainPage/MainPage.js         |   7 ++
 src/notifications/actions/GetNotifications.js |  15 +++
 .../NotificationsList/NotificationList.js     | 107 +++++++++++-------
 .../reducers/NotificationsList.js             |  10 ++
 5 files changed, 98 insertions(+), 42 deletions(-)

diff --git a/package.json b/package.json
index 01d4134d..a7fdedb3 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
     "connected-react-router": "^6.7.0",
     "cross-env": "^7.0.0",
     "env-cmd": "^10.1.0",
+    "he": "^1.2.0",
     "history": "^4.7.2",
     "js-cookie": "^2.2.0",
     "jwt-decode": "^2.2.0",
diff --git a/src/common/pages/MainPage/MainPage.js b/src/common/pages/MainPage/MainPage.js
index e64bdbb7..cd1b2f62 100644
--- a/src/common/pages/MainPage/MainPage.js
+++ b/src/common/pages/MainPage/MainPage.js
@@ -12,6 +12,7 @@ import CERNToolBar from '../../components/CERNToolBar/CERNToolBar';
 import EditChannelPage from '../../../channels/pages/EditChannelPage/EditChannelPage';
 import ChannelGlobalPreferences from '../../../channels/pages/ChannelGlobalPreferences/ChannelGlobalPreferences';
 import DevicesGlobal from 'common/pages/Devices/DevicesGlobal';
+import NotificationPage from 'common/pages/Notification/NotificationPage';
 
 const MainPage = props => {
   const {snackbars, isAuthenticated} = props;
@@ -48,6 +49,12 @@ const MainPage = props => {
               <Route path="/main/channels/:channelId" component={EditChannelPage} key={4} />
               <Route path="/main/preferences" component={ChannelGlobalPreferences} key={5} />
               <Route path="/main/devices" component={DevicesGlobal} key={6} />
+
+              <Route
+                path="/main/notification/:notificationId"
+                component={NotificationPage}
+                key={7}
+              />
             </Switch>
           </div>
         </div>
diff --git a/src/notifications/actions/GetNotifications.js b/src/notifications/actions/GetNotifications.js
index cb36ca60..9ce6b8e0 100644
--- a/src/notifications/actions/GetNotifications.js
+++ b/src/notifications/actions/GetNotifications.js
@@ -7,6 +7,11 @@ export const GET_NOTIFICATIONS = 'GET_NOTIFICATIONS';
 export const GET_NOTIFICATIONS_SUCCESS = 'GET_NOTIFICATIONS_SUCCESS';
 export const GET_NOTIFICATIONS_FAILURE = 'GET_NOTIFICATIONS_FAILURE';
 
+// Get notification (from ID)
+export const GET_NOTIFICATION = 'GET_NOTIFICATION';
+export const GET_NOTIFICATION_SUCCESS = 'GET_NOTIFICATION_SUCCESS';
+export const GET_NOTIFICATION_FAILURE = 'GET_NOTIFICATION_FAILURE';
+
 export const SET_GET_NOTIFICATIONS_QUERY = 'SET_GET_NOTIFICATIONS_QUERY';
 
 export const getNotifications = (channelId, query) => ({
@@ -32,3 +37,13 @@ export const setGetNotificationsQuery = query => {
     payload: query,
   };
 };
+
+export const getNotification = notificationId => ({
+  [RSAA]: {
+    endpoint: `${process.env.REACT_APP_BASE_URL}/notifications/notification/${notificationId}`,
+    method: 'GET',
+    credentials: 'include',
+    headers: withAuth({'Content-Type': 'application/json'}),
+    types: [GET_NOTIFICATION, GET_NOTIFICATION_SUCCESS, GET_NOTIFICATION_FAILURE],
+  },
+});
diff --git a/src/notifications/components/NotificationsList/NotificationList.js b/src/notifications/components/NotificationsList/NotificationList.js
index fd8ddd05..ad2fb2b7 100644
--- a/src/notifications/components/NotificationsList/NotificationList.js
+++ b/src/notifications/components/NotificationsList/NotificationList.js
@@ -1,10 +1,12 @@
-import React, {useState, Fragment, useEffect} from 'react';
-import {Accordion, Icon, Visibility} from 'semantic-ui-react';
+import React, {useEffect} from 'react';
+import {Icon, Feed} from 'semantic-ui-react';
 import {connect} from 'react-redux';
 import * as getNotificationsActionCreators from 'notifications/actions/GetNotifications';
 import * as getPublicNotificationsActionCreators from 'notifications/actions/GetPublicNotifications';
 import {bindActionCreators} from 'redux';
 import {useParams} from 'react-router-dom';
+import * as he from 'he';
+import './NotificationList.scss';
 
 const NotificationsList = props => {
   const {
@@ -15,7 +17,7 @@ const NotificationsList = props => {
     isAuthenticated,
     getPublicNotifications,
   } = props;
-  const [activeIndex, setActiveIndex] = useState(-1);
+  //const [activeIndex, setActiveIndex] = useState(-1);
   const {channelId} = useParams();
 
   useEffect(() => {
@@ -23,54 +25,75 @@ const NotificationsList = props => {
     else getPublicNotifications(channelId, getNotificationsQuery);
   }, [getNotifications, channelId, getNotificationsQuery, getPublicNotifications, isAuthenticated]);
 
-  const handleClick = (e, titleProps) => {
-    const {index} = titleProps;
-    const newIndex = activeIndex === index ? -1 : index;
+  // const handleClick = (e, titleProps) => {
+  //   const { index } = titleProps;
+  //   const newIndex = activeIndex === index ? -1 : index;
+  //   setActiveIndex(newIndex);
+  // };
 
-    setActiveIndex(newIndex);
+  const bodyPreview = body => {
+    const preview = body.replace(/(<([^>]+)>)/gi, '');
+    // Using he.decode to decode html special chars
+    return preview && he.decode(preview).substring(0, 100) + '...';
   };
+
+  const feedIcon = priority => {
+    if (priority === 'IMPORTANT') return 'warning circle';
+    else return 'info circle';
+  };
+
   if (notifications.length === 0) {
     return <p>The are no notifications in this channel</p>;
   }
 
   return (
-    <Accordion style={{width: '100%'}} styled>
-      <Visibility
-        onBottomVisible={() => {
-          setGetNotificationsQuery({
-            ...getNotificationsQuery,
-            skip: getNotificationsQuery.skip + getNotificationsQuery.take,
-          });
-        }}
-      >
+    <>
+      <Feed>
         {notifications.map((notification, index) => (
-          // eslint-disable-next-line react/jsx-fragments
-          <Fragment>
-            <Accordion.Title active={activeIndex === index} index={index} onClick={handleClick}>
-              <Icon name="dropdown" />
-              {notification.subject}
-              <div style={{float: 'right'}}>{new Date(notification.sentAt).toLocaleString()}</div>
-              {activeIndex !== index && (
-                <p
-                  style={{
-                    overflow: 'hidden',
-                    textOverflow: 'ellipsis',
-                    display: '-webkit-box',
-                    WebkitLineClamp: 3,
-                    WebkitBoxOrient: 'vertical',
-                    maxHeight: '150px',
-                  }}
-                  dangerouslySetInnerHTML={{__html: notification.body}}
-                />
-              )}
-            </Accordion.Title>
-            <Accordion.Content active={activeIndex === index}>
-              <div dangerouslySetInnerHTML={{__html: notification.body}} />
-            </Accordion.Content>
-          </Fragment>
+          <Feed.Event key={index}>
+            <Feed.Label>
+              <Icon name={feedIcon(notification.priority)} />
+            </Feed.Label>
+            <Feed.Content>
+              <Feed.Summary>
+                <a href="" target="_blank" rel="noopener noreferrer">
+                  {notification.summary}
+                </a>
+                <Feed.Date>{new Date(notification.sentAt).toLocaleString()}</Feed.Date>
+              </Feed.Summary>
+              <Feed.Extra text>{bodyPreview(notification.body)}</Feed.Extra>
+            </Feed.Content>
+          </Feed.Event>
         ))}
-      </Visibility>
-    </Accordion>
+      </Feed>
+
+      {/* <Accordion style={{ width: '100%' }} styled>
+        <Visibility
+          onBottomVisible={() => {
+            setGetNotificationsQuery({
+              ...getNotificationsQuery,
+              skip: getNotificationsQuery.skip + getNotificationsQuery.take,
+            });
+          }}
+        >
+          {notifications.map((notification, index) => (
+            // eslint-disable-next-line react/jsx-fragments
+            <Fragment>
+              <Accordion.Title active={activeIndex === index} index={index} onClick={handleClick}>
+                <Icon name="dropdown" />
+                {notification.summary}
+                <div style={{ float: 'right' }}>{new Date(notification.sentAt).toLocaleString()}</div>
+              </Accordion.Title>
+              <Accordion.Content active={activeIndex === index}>
+                <div dangerouslySetInnerHTML={{ __html: notification.body }} />
+                <div><img src={notification.imgUrl} border="0" alt="" /></div>
+                <div><a href={notification.link} target="_blank" rel="noopener noreferrer">{notification.link}</a></div>
+              </Accordion.Content>
+            </Fragment>
+          ))}
+        </Visibility>
+      </Accordion> */}
+    </>
   );
 };
 
diff --git a/src/notifications/reducers/NotificationsList.js b/src/notifications/reducers/NotificationsList.js
index dbe1c4ad..8dbb93ef 100644
--- a/src/notifications/reducers/NotificationsList.js
+++ b/src/notifications/reducers/NotificationsList.js
@@ -3,12 +3,17 @@ import {
   GET_NOTIFICATIONS_SUCCESS,
   GET_NOTIFICATIONS_FAILURE,
   SET_GET_NOTIFICATIONS_QUERY,
+  //  GET_NOTIFICATION,
+  GET_NOTIFICATION_SUCCESS,
+  //  GET_NOTIFICATION_SUCCESS,
+  //  GET_NOTIFICATION_FAILURE,
 } from 'notifications/actions/GetNotifications';
 
 import {CREATE_NOTIFICATION_SUCCESS} from 'notifications/actions/CreateNotification';
 
 const INITIAL_STATE = {
   notifications: [],
+  notification: null,
   getNotificationsQuery: {
     searchText: '',
     skip: 0,
@@ -84,6 +89,7 @@ function processSetGetNotificationsQuery(state, query) {
 
 export default function (state = INITIAL_STATE, action) {
   let error;
+
   switch (action.type) {
     case GET_NOTIFICATIONS:
       return processGetNotifications(state, action.payload);
@@ -99,6 +105,10 @@ export default function (state = INITIAL_STATE, action) {
     case SET_GET_NOTIFICATIONS_QUERY:
       return processSetGetNotificationsQuery(state, action.payload);
 
+    case GET_NOTIFICATION_SUCCESS: {
+      return {...state, notification: action.payload};
+    }
+
     default:
       return state;
   }
-- 
GitLab


From b6acd02341c02a018077981316a680356371ce4b Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Fri, 5 Feb 2021 16:50:37 +0100
Subject: [PATCH 05/46] Changed notification list and notiifcation display

---
 .../pages/Notification/NotificationPage.js    | 36 ++++++++++++++
 .../NotificationDisplay.js                    | 48 +++++++++++++++++++
 .../NotificationsList/NotificationList.scss   | 13 +++++
 3 files changed, 97 insertions(+)
 create mode 100644 src/common/pages/Notification/NotificationPage.js
 create mode 100644 src/notifications/components/NotificationDisplay/NotificationDisplay.js
 create mode 100644 src/notifications/components/NotificationsList/NotificationList.scss

diff --git a/src/common/pages/Notification/NotificationPage.js b/src/common/pages/Notification/NotificationPage.js
new file mode 100644
index 00000000..38fd95f7
--- /dev/null
+++ b/src/common/pages/Notification/NotificationPage.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+import {useParams} from 'react-router-dom';
+import NotificationDisplay from '../../../notifications/components/NotificationDisplay/NotificationDisplay';
+
+function NotificationPage({isAuthenticated}) {
+  const {notificationId} = useParams();
+
+  return (
+    <div>
+      NotificationId = {notificationId}
+      <br />
+      isAuthenticated = {isAuthenticated}
+      {isAuthenticated && <NotificationDisplay notificationId={notificationId} />}
+    </div>
+  );
+}
+
+NotificationPage.propTypes = {
+  isAuthenticated: PropTypes.bool.isRequired,
+};
+
+const mapStateToProps = state => ({
+  isAuthenticated: state.auth.loggedIn,
+});
+
+export default connect(mapStateToProps, dispatch =>
+  bindActionCreators(
+    {
+      //      getPreferences: preferencesActions.getPreferences,
+    },
+    dispatch
+  )
+)(NotificationPage);
diff --git a/src/notifications/components/NotificationDisplay/NotificationDisplay.js b/src/notifications/components/NotificationDisplay/NotificationDisplay.js
new file mode 100644
index 00000000..26abb20a
--- /dev/null
+++ b/src/notifications/components/NotificationDisplay/NotificationDisplay.js
@@ -0,0 +1,48 @@
+import React, {useState, useEffect} from 'react';
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+import {Modal, Button} from 'semantic-ui-react';
+import {useParams} from 'react-router-dom';
+
+import * as showSnackBarActionCreators from '../../../common/actions/Snackbar';
+import * as notificationActions from '../../actions/GetNotifications';
+
+const NotificationDisplay = ({notification, getNotification}) => {
+  const {notificationId} = useParams();
+
+  const [modalOpen, setModalOpen] = useState(true);
+
+  useEffect(() => {
+    getNotification(notificationId);
+  }, [getNotification, notificationId]);
+
+  return (
+    <Modal open={modalOpen}>
+      <Modal.Header>Notification</Modal.Header>
+      <Modal.Content>
+        <Modal.Description>
+          displaying id={notificationId}
+          <br />
+          content = {notification}
+        </Modal.Description>
+      </Modal.Content>
+      <Modal.Actions>
+        <Button onClick={() => setModalOpen(false)}>Close</Button>
+      </Modal.Actions>
+    </Modal>
+  );
+};
+
+export default connect(
+  state => ({
+    notification: state.notification,
+  }),
+  dispatch =>
+    bindActionCreators(
+      {
+        getNotification: notificationActions.getNotification,
+        ...showSnackBarActionCreators,
+      },
+      dispatch
+    )
+)(NotificationDisplay);
diff --git a/src/notifications/components/NotificationsList/NotificationList.scss b/src/notifications/components/NotificationsList/NotificationList.scss
new file mode 100644
index 00000000..63c8e591
--- /dev/null
+++ b/src/notifications/components/NotificationsList/NotificationList.scss
@@ -0,0 +1,13 @@
+.ui.feed .event {
+  .label {
+    margin-top: 10px;
+  }
+
+  .content .extra {
+    margin: 0;
+  }
+
+  .content .extra.text {
+    font-size: small;
+  }
+}
-- 
GitLab


From ac2759042b0b650cf3baae8a2464c9609df2f638 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Mon, 15 Feb 2021 13:58:02 +0100
Subject: [PATCH 06/46] Notification Display v0

---
 .../NotificationDisplay.js                    | 48 ++++++++++++++++---
 .../NotificationsList/NotificationList.js     | 16 ++++---
 .../reducers/NotificationsList.js             |  2 +-
 src/utils/notification-type-icon.js           |  5 ++
 4 files changed, 56 insertions(+), 15 deletions(-)
 create mode 100644 src/utils/notification-type-icon.js

diff --git a/src/notifications/components/NotificationDisplay/NotificationDisplay.js b/src/notifications/components/NotificationDisplay/NotificationDisplay.js
index 26abb20a..617e4fb2 100644
--- a/src/notifications/components/NotificationDisplay/NotificationDisplay.js
+++ b/src/notifications/components/NotificationDisplay/NotificationDisplay.js
@@ -1,10 +1,11 @@
 import React, {useState, useEffect} from 'react';
 import {bindActionCreators} from 'redux';
 import {connect} from 'react-redux';
-import {Modal, Button} from 'semantic-ui-react';
+import {Modal, Button, Item, Image, Icon} from 'semantic-ui-react';
 import {useParams} from 'react-router-dom';
 
-import * as showSnackBarActionCreators from '../../../common/actions/Snackbar';
+import {notificationIcon} from '../../../utils/notification-type-icon';
+//import * as showSnackBarActionCreators from '../../../common/actions/Snackbar';
 import * as notificationActions from '../../actions/GetNotifications';
 
 const NotificationDisplay = ({notification, getNotification}) => {
@@ -21,9 +22,42 @@ const NotificationDisplay = ({notification, getNotification}) => {
       <Modal.Header>Notification</Modal.Header>
       <Modal.Content>
         <Modal.Description>
-          displaying id={notificationId}
-          <br />
-          content = {notification}
+          <Item.Group>
+            <Item>
+              <Item.Image
+                size="tiny"
+                src={
+                  notification && notification.imgUrl
+                    ? notification.imgUrl
+                    : 'https://react.semantic-ui.com/images/wireframe/image.png'
+                }
+              />
+
+              <Item.Content>
+                <Item.Header>{notification.summary}</Item.Header>
+                <Item.Meta>{notification.sentAt}</Item.Meta>
+                <Item.Meta>
+                  <Icon name={notificationIcon(notification.priority)} /> {notification.priority}
+                </Item.Meta>
+
+                <Item.Description>
+                  <div dangerouslySetInnerHTML={{__html: notification.body}} />
+                </Item.Description>
+                {notification && notification.imgUrl && (
+                  <Item.Extra>
+                    <Image src={notification.imgUrl} />
+                  </Item.Extra>
+                )}
+                {notification && notification.link && (
+                  <Item.Extra>
+                    <a href={notification.link} target="_blank" rel="noopener noreferrer">
+                      {notification.link}
+                    </a>
+                  </Item.Extra>
+                )}
+              </Item.Content>
+            </Item>
+          </Item.Group>
         </Modal.Description>
       </Modal.Content>
       <Modal.Actions>
@@ -35,13 +69,13 @@ const NotificationDisplay = ({notification, getNotification}) => {
 
 export default connect(
   state => ({
-    notification: state.notification,
+    notification: state.notifications.notificationsList.notification,
   }),
   dispatch =>
     bindActionCreators(
       {
         getNotification: notificationActions.getNotification,
-        ...showSnackBarActionCreators,
+        //        ...showSnackBarActionCreators,
       },
       dispatch
     )
diff --git a/src/notifications/components/NotificationsList/NotificationList.js b/src/notifications/components/NotificationsList/NotificationList.js
index ad2fb2b7..eace3c16 100644
--- a/src/notifications/components/NotificationsList/NotificationList.js
+++ b/src/notifications/components/NotificationsList/NotificationList.js
@@ -1,11 +1,13 @@
 import React, {useEffect} from 'react';
 import {Icon, Feed} from 'semantic-ui-react';
 import {connect} from 'react-redux';
-import * as getNotificationsActionCreators from 'notifications/actions/GetNotifications';
-import * as getPublicNotificationsActionCreators from 'notifications/actions/GetPublicNotifications';
 import {bindActionCreators} from 'redux';
 import {useParams} from 'react-router-dom';
 import * as he from 'he';
+
+import {notificationIcon} from '../../../utils/notification-type-icon';
+import * as getNotificationsActionCreators from 'notifications/actions/GetNotifications';
+import * as getPublicNotificationsActionCreators from 'notifications/actions/GetPublicNotifications';
 import './NotificationList.scss';
 
 const NotificationsList = props => {
@@ -37,10 +39,10 @@ const NotificationsList = props => {
     return preview && he.decode(preview).substring(0, 100) + '...';
   };
 
-  const feedIcon = priority => {
-    if (priority === 'IMPORTANT') return 'warning circle';
-    else return 'info circle';
-  };
+  // const feedIcon = priority => {
+  //   if (priority === 'IMPORTANT') return 'warning circle';
+  //   else return 'info circle';
+  // };
 
   if (notifications.length === 0) {
     return <p>The are no notifications in this channel</p>;
@@ -52,7 +54,7 @@ const NotificationsList = props => {
         {notifications.map((notification, index) => (
           <Feed.Event key={index}>
             <Feed.Label>
-              <Icon name={feedIcon(notification.priority)} />
+              <Icon name={notificationIcon(notification.priority)} />
             </Feed.Label>
             <Feed.Content>
               <Feed.Summary>
diff --git a/src/notifications/reducers/NotificationsList.js b/src/notifications/reducers/NotificationsList.js
index 8dbb93ef..56d4ff43 100644
--- a/src/notifications/reducers/NotificationsList.js
+++ b/src/notifications/reducers/NotificationsList.js
@@ -13,7 +13,7 @@ import {CREATE_NOTIFICATION_SUCCESS} from 'notifications/actions/CreateNotificat
 
 const INITIAL_STATE = {
   notifications: [],
-  notification: null,
+  notification: '',
   getNotificationsQuery: {
     searchText: '',
     skip: 0,
diff --git a/src/utils/notification-type-icon.js b/src/utils/notification-type-icon.js
new file mode 100644
index 00000000..44e43891
--- /dev/null
+++ b/src/utils/notification-type-icon.js
@@ -0,0 +1,5 @@
+export function notificationIcon(priority) {
+  if (priority === 'CRITICAL') return 'exclamation triangle';
+  else if (priority === 'IMPORTANT') return 'warning circle';
+  else return 'info circle';
+}
-- 
GitLab


From 5256e15ff08114299c288d530973f0b610ce455d Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 16 Feb 2021 09:00:01 +0100
Subject: [PATCH 07/46] Notification Display v0.1

---
 public/images/empty.png                       | Bin 0 -> 7175 bytes
 src/common/colors/colors.scss                 |   1 +
 .../pages/Notification/NotificationPage.js    |  54 ++++++++-----
 .../NotificationDisplay.js                    |  72 ++++++++----------
 .../NotificationDisplay.scss                  |  22 ++++++
 .../NotificationsList/NotificationList.scss   |  13 ----
 ...tificationList.js => NotificationsList.js} |  20 ++---
 .../NotificationsList/NotificationsList.scss  |  24 ++++++
 .../SearchNotificationComponent.js            |   2 +-
 .../NotificationsPage/NotificationsPage.js    |   2 +-
 src/utils/notification-type-icon.js           |  24 ++++--
 11 files changed, 146 insertions(+), 88 deletions(-)
 create mode 100644 public/images/empty.png
 create mode 100644 src/notifications/components/NotificationDisplay/NotificationDisplay.scss
 delete mode 100644 src/notifications/components/NotificationsList/NotificationList.scss
 rename src/notifications/components/NotificationsList/{NotificationList.js => NotificationsList.js} (86%)
 create mode 100644 src/notifications/components/NotificationsList/NotificationsList.scss

diff --git a/public/images/empty.png b/public/images/empty.png
new file mode 100644
index 0000000000000000000000000000000000000000..11ce432e9f182ee39e756ea68065ee0d28e4c0c6
GIT binary patch
literal 7175
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

literal 0
HcmV?d00001

diff --git a/src/common/colors/colors.scss b/src/common/colors/colors.scss
index e0476456..0cc9da9d 100644
--- a/src/common/colors/colors.scss
+++ b/src/common/colors/colors.scss
@@ -1,3 +1,4 @@
 $light-gray: #666;
 $dark-gray: #333;
 $background-lightgrey: #eee;
+$separator-lightgrey: #ccc;
diff --git a/src/common/pages/Notification/NotificationPage.js b/src/common/pages/Notification/NotificationPage.js
index 38fd95f7..6cef5cd1 100644
--- a/src/common/pages/Notification/NotificationPage.js
+++ b/src/common/pages/Notification/NotificationPage.js
@@ -1,20 +1,35 @@
-import React from 'react';
+import React, {useEffect} from 'react';
 import PropTypes from 'prop-types';
 import {bindActionCreators} from 'redux';
 import {connect} from 'react-redux';
 import {useParams} from 'react-router-dom';
+
+import * as notificationActions from '../../../notifications/actions/GetNotifications';
 import NotificationDisplay from '../../../notifications/components/NotificationDisplay/NotificationDisplay';
 
-function NotificationPage({isAuthenticated}) {
+function NotificationPage({isAuthenticated, notification, getNotification}) {
   const {notificationId} = useParams();
 
+  useEffect(() => {
+    getNotification(notificationId);
+  }, [getNotification, notificationId]);
+
+  const onCloseModal = () => {
+    console.log('closed modal, redirecting to channel notifications list');
+    console.log(notification);
+    //history.push(`/main/channels/${channel.id}`);
+  };
+
+  // TODO2: onclose modal -> redirect to channel notification list
   return (
-    <div>
-      NotificationId = {notificationId}
-      <br />
-      isAuthenticated = {isAuthenticated}
-      {isAuthenticated && <NotificationDisplay notificationId={notificationId} />}
-    </div>
+    isAuthenticated &&
+    notification && (
+      <NotificationDisplay
+        notification={notification}
+        modalinitialopenstate={true}
+        onCloseModal={onCloseModal}
+      />
+    )
   );
 }
 
@@ -22,15 +37,16 @@ NotificationPage.propTypes = {
   isAuthenticated: PropTypes.bool.isRequired,
 };
 
-const mapStateToProps = state => ({
-  isAuthenticated: state.auth.loggedIn,
-});
-
-export default connect(mapStateToProps, dispatch =>
-  bindActionCreators(
-    {
-      //      getPreferences: preferencesActions.getPreferences,
-    },
-    dispatch
-  )
+export default connect(
+  state => ({
+    isAuthenticated: state.auth.loggedIn,
+    notification: state.notifications.notificationsList.notification,
+  }),
+  dispatch =>
+    bindActionCreators(
+      {
+        getNotification: notificationActions.getNotification,
+      },
+      dispatch
+    )
 )(NotificationPage);
diff --git a/src/notifications/components/NotificationDisplay/NotificationDisplay.js b/src/notifications/components/NotificationDisplay/NotificationDisplay.js
index 617e4fb2..1ebfeebf 100644
--- a/src/notifications/components/NotificationDisplay/NotificationDisplay.js
+++ b/src/notifications/components/NotificationDisplay/NotificationDisplay.js
@@ -1,24 +1,26 @@
-import React, {useState, useEffect} from 'react';
-import {bindActionCreators} from 'redux';
-import {connect} from 'react-redux';
-import {Modal, Button, Item, Image, Icon} from 'semantic-ui-react';
-import {useParams} from 'react-router-dom';
+import React, {useState} from 'react';
+import {Modal, Button, Item, Image} from 'semantic-ui-react';
 
-import {notificationIcon} from '../../../utils/notification-type-icon';
-//import * as showSnackBarActionCreators from '../../../common/actions/Snackbar';
-import * as notificationActions from '../../actions/GetNotifications';
+import NotificationIcon from '../../../utils/notification-type-icon';
+import './NotificationDisplay.scss';
 
-const NotificationDisplay = ({notification, getNotification}) => {
-  const {notificationId} = useParams();
+const NotificationDisplay = props => {
+  const notification = props.notification;
 
-  const [modalOpen, setModalOpen] = useState(true);
-
-  useEffect(() => {
-    getNotification(notificationId);
-  }, [getNotification, notificationId]);
+  const [modalOpen, setModalOpen] = useState(
+    props.modalinitialopenstate ? props.modalinitialopenstate : false
+  );
 
   return (
-    <Modal open={modalOpen}>
+    <Modal
+      trigger={
+        <Button className="tertiarynohover" onClick={() => setModalOpen(true)}>
+          {notification.summary}
+        </Button>
+      }
+      open={modalOpen}
+      onClose={() => setModalOpen(false)}
+    >
       <Modal.Header>Notification</Modal.Header>
       <Modal.Content>
         <Modal.Description>
@@ -27,19 +29,15 @@ const NotificationDisplay = ({notification, getNotification}) => {
               <Item.Image
                 size="tiny"
                 src={
-                  notification && notification.imgUrl
-                    ? notification.imgUrl
-                    : 'https://react.semantic-ui.com/images/wireframe/image.png'
+                  notification && notification.imgUrl ? notification.imgUrl : '/images/empty.png'
                 }
               />
 
               <Item.Content>
-                <Item.Header>{notification.summary}</Item.Header>
-                <Item.Meta>{notification.sentAt}</Item.Meta>
-                <Item.Meta>
-                  <Icon name={notificationIcon(notification.priority)} /> {notification.priority}
-                </Item.Meta>
-
+                <Item.Header>
+                  <NotificationIcon priority={notification.priority} /> {notification.summary}
+                </Item.Header>
+                <Item.Meta>{new Date(notification.sentAt).toLocaleString()}</Item.Meta>
                 <Item.Description>
                   <div dangerouslySetInnerHTML={{__html: notification.body}} />
                 </Item.Description>
@@ -50,6 +48,7 @@ const NotificationDisplay = ({notification, getNotification}) => {
                 )}
                 {notification && notification.link && (
                   <Item.Extra>
+                    Link:{' '}
                     <a href={notification.link} target="_blank" rel="noopener noreferrer">
                       {notification.link}
                     </a>
@@ -61,22 +60,17 @@ const NotificationDisplay = ({notification, getNotification}) => {
         </Modal.Description>
       </Modal.Content>
       <Modal.Actions>
-        <Button onClick={() => setModalOpen(false)}>Close</Button>
+        <Button
+          onClick={() => {
+            setModalOpen(false);
+            if (props.onCloseModal) props.onCloseModal();
+          }}
+        >
+          Close
+        </Button>
       </Modal.Actions>
     </Modal>
   );
 };
 
-export default connect(
-  state => ({
-    notification: state.notifications.notificationsList.notification,
-  }),
-  dispatch =>
-    bindActionCreators(
-      {
-        getNotification: notificationActions.getNotification,
-        //        ...showSnackBarActionCreators,
-      },
-      dispatch
-    )
-)(NotificationDisplay);
+export default NotificationDisplay;
diff --git a/src/notifications/components/NotificationDisplay/NotificationDisplay.scss b/src/notifications/components/NotificationDisplay/NotificationDisplay.scss
new file mode 100644
index 00000000..efe6b68b
--- /dev/null
+++ b/src/notifications/components/NotificationDisplay/NotificationDisplay.scss
@@ -0,0 +1,22 @@
+.ui.button {
+  &.tertiary {
+    &:not(:hover) {
+      box-shadow: none !important;
+      background: none !important;
+    }
+  }
+}
+
+.ui.button {
+  &.tertiarynohover {
+    box-shadow: none !important;
+    background: none !important;
+    padding-left: 0px;
+  }
+}
+
+.ui.button {
+  &.tertiarynohover:hover {
+    text-decoration: underline;
+  }
+}
diff --git a/src/notifications/components/NotificationsList/NotificationList.scss b/src/notifications/components/NotificationsList/NotificationList.scss
deleted file mode 100644
index 63c8e591..00000000
--- a/src/notifications/components/NotificationsList/NotificationList.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.ui.feed .event {
-  .label {
-    margin-top: 10px;
-  }
-
-  .content .extra {
-    margin: 0;
-  }
-
-  .content .extra.text {
-    font-size: small;
-  }
-}
diff --git a/src/notifications/components/NotificationsList/NotificationList.js b/src/notifications/components/NotificationsList/NotificationsList.js
similarity index 86%
rename from src/notifications/components/NotificationsList/NotificationList.js
rename to src/notifications/components/NotificationsList/NotificationsList.js
index eace3c16..573bf3a9 100644
--- a/src/notifications/components/NotificationsList/NotificationList.js
+++ b/src/notifications/components/NotificationsList/NotificationsList.js
@@ -1,21 +1,22 @@
 import React, {useEffect} from 'react';
-import {Icon, Feed} from 'semantic-ui-react';
+import {Feed} from 'semantic-ui-react';
 import {connect} from 'react-redux';
 import {bindActionCreators} from 'redux';
 import {useParams} from 'react-router-dom';
 import * as he from 'he';
 
-import {notificationIcon} from '../../../utils/notification-type-icon';
+import NotificationDisplay from '../NotificationDisplay/NotificationDisplay';
+import NotificationIcon from '../../../utils/notification-type-icon';
 import * as getNotificationsActionCreators from 'notifications/actions/GetNotifications';
 import * as getPublicNotificationsActionCreators from 'notifications/actions/GetPublicNotifications';
-import './NotificationList.scss';
+import './NotificationsList.scss';
 
 const NotificationsList = props => {
   const {
     notifications,
     getNotifications,
     getNotificationsQuery,
-    setGetNotificationsQuery,
+    //    setGetNotificationsQuery,
     isAuthenticated,
     getPublicNotifications,
   } = props;
@@ -52,16 +53,15 @@ const NotificationsList = props => {
     <>
       <Feed>
         {notifications.map((notification, index) => (
-          <Feed.Event key={index}>
+          <Feed.Event key={index} className="sep">
             <Feed.Label>
-              <Icon name={notificationIcon(notification.priority)} />
+              <NotificationIcon priority={notification.priority} />
             </Feed.Label>
             <Feed.Content>
+              <Feed.Date>{new Date(notification.sentAt).toLocaleString()}</Feed.Date>
               <Feed.Summary>
-                <a href="" target="_blank" rel="noopener noreferrer">
-                  {notification.summary}
-                </a>
-                <Feed.Date>{new Date(notification.sentAt).toLocaleString()}</Feed.Date>
+                {/* {notification.summary} */}
+                <NotificationDisplay notification={notification} />
               </Feed.Summary>
               <Feed.Extra text>{bodyPreview(notification.body)}</Feed.Extra>
             </Feed.Content>
diff --git a/src/notifications/components/NotificationsList/NotificationsList.scss b/src/notifications/components/NotificationsList/NotificationsList.scss
new file mode 100644
index 00000000..3670ff66
--- /dev/null
+++ b/src/notifications/components/NotificationsList/NotificationsList.scss
@@ -0,0 +1,24 @@
+@import '../../../common/colors/colors.scss';
+
+.ui.feed .event {
+  .label {
+    margin-top: 20px;
+  }
+
+  .content .date {
+    margin: 0 0 -10px 0;
+    font-size: 0.8em;
+  }
+
+  .content .extra {
+    margin: 0;
+  }
+
+  .content .extra.text {
+    font-size: small;
+  }
+}
+
+.ui.feed .sep {
+  border-bottom: 1px solid $separator-lightgrey;
+}
diff --git a/src/notifications/components/SearchNotificationComponent/SearchNotificationComponent.js b/src/notifications/components/SearchNotificationComponent/SearchNotificationComponent.js
index 0ae4e6d7..67c9722e 100644
--- a/src/notifications/components/SearchNotificationComponent/SearchNotificationComponent.js
+++ b/src/notifications/components/SearchNotificationComponent/SearchNotificationComponent.js
@@ -4,7 +4,7 @@ import {bindActionCreators} from 'redux';
 import {connect} from 'react-redux';
 import * as getNotificationsActionCreators from '../../actions/GetNotifications';
 
-const WAIT_INTERVAL = 250;
+const WAIT_INTERVAL = 500;
 let timer = null;
 
 const SearchNotificationComponent = ({getNotificationsQuery, setGetNotificationsQuery}) => {
diff --git a/src/notifications/pages/NotificationsPage/NotificationsPage.js b/src/notifications/pages/NotificationsPage/NotificationsPage.js
index 3b5053b0..b8db4c37 100644
--- a/src/notifications/pages/NotificationsPage/NotificationsPage.js
+++ b/src/notifications/pages/NotificationsPage/NotificationsPage.js
@@ -7,7 +7,7 @@ import {Container, Divider, Header, Menu, Segment} from 'semantic-ui-react';
 
 import * as preferencesActions from 'actions/preferences';
 import PreferencesList from 'components/preferences/PreferencesList';
-import NotificationsList from '../../components/NotificationsList/NotificationList';
+import NotificationsList from '../../components/NotificationsList/NotificationsList';
 import SearchNotificationComponent from '../../components/SearchNotificationComponent/SearchNotificationComponent';
 
 import './NotificationsPage.scss';
diff --git a/src/utils/notification-type-icon.js b/src/utils/notification-type-icon.js
index 44e43891..8d523bd3 100644
--- a/src/utils/notification-type-icon.js
+++ b/src/utils/notification-type-icon.js
@@ -1,5 +1,19 @@
-export function notificationIcon(priority) {
-  if (priority === 'CRITICAL') return 'exclamation triangle';
-  else if (priority === 'IMPORTANT') return 'warning circle';
-  else return 'info circle';
-}
+import React from 'react';
+import {Icon, Popup} from 'semantic-ui-react';
+
+const NotificationIcon = props => {
+  if (!props.priority || props.priority === 'LOW') return null;
+
+  let icon = <Icon name="info circle" color="green" />;
+  if (props.priority === 'CRITICAL') icon = <Icon name="exclamation triangle" color="red" />;
+  else if (props.priority === 'IMPORTANT') icon = <Icon name="warning circle" color="orange" />;
+
+  return (
+    <Popup trigger={icon} position="top center">
+      Priority:{' '}
+      {props.priority ? props.priority.charAt(0) + props.priority.substring(1).toLowerCase() : ''}
+    </Popup>
+  );
+};
+
+export default NotificationIcon;
-- 
GitLab


From 9267963b496942277ca575ac2c5e97916395adc8 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 16 Feb 2021 09:03:51 +0100
Subject: [PATCH 08/46] Notification Display v0.1

---
 src/common/pages/Notification/NotificationPage.js    | 12 ++++++------
 .../NotificationDisplay/NotificationDisplay.js       |  6 +++---
 .../NotificationsList/NotificationsList.js           | 12 ++++++------
 3 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/common/pages/Notification/NotificationPage.js b/src/common/pages/Notification/NotificationPage.js
index 6cef5cd1..b6ee6ccd 100644
--- a/src/common/pages/Notification/NotificationPage.js
+++ b/src/common/pages/Notification/NotificationPage.js
@@ -1,14 +1,14 @@
-import React, {useEffect} from 'react';
+import React, { useEffect } from 'react';
 import PropTypes from 'prop-types';
-import {bindActionCreators} from 'redux';
-import {connect} from 'react-redux';
-import {useParams} from 'react-router-dom';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { useParams } from 'react-router-dom';
 
 import * as notificationActions from '../../../notifications/actions/GetNotifications';
 import NotificationDisplay from '../../../notifications/components/NotificationDisplay/NotificationDisplay';
 
-function NotificationPage({isAuthenticated, notification, getNotification}) {
-  const {notificationId} = useParams();
+function NotificationPage({ isAuthenticated, notification, getNotification }) {
+  const { notificationId } = useParams();
 
   useEffect(() => {
     getNotification(notificationId);
diff --git a/src/notifications/components/NotificationDisplay/NotificationDisplay.js b/src/notifications/components/NotificationDisplay/NotificationDisplay.js
index 1ebfeebf..a6224502 100644
--- a/src/notifications/components/NotificationDisplay/NotificationDisplay.js
+++ b/src/notifications/components/NotificationDisplay/NotificationDisplay.js
@@ -1,5 +1,5 @@
-import React, {useState} from 'react';
-import {Modal, Button, Item, Image} from 'semantic-ui-react';
+import React, { useState } from 'react';
+import { Modal, Button, Item, Image } from 'semantic-ui-react';
 
 import NotificationIcon from '../../../utils/notification-type-icon';
 import './NotificationDisplay.scss';
@@ -39,7 +39,7 @@ const NotificationDisplay = props => {
                 </Item.Header>
                 <Item.Meta>{new Date(notification.sentAt).toLocaleString()}</Item.Meta>
                 <Item.Description>
-                  <div dangerouslySetInnerHTML={{__html: notification.body}} />
+                  <div dangerouslySetInnerHTML={{ __html: notification.body }} />
                 </Item.Description>
                 {notification && notification.imgUrl && (
                   <Item.Extra>
diff --git a/src/notifications/components/NotificationsList/NotificationsList.js b/src/notifications/components/NotificationsList/NotificationsList.js
index 573bf3a9..967ca285 100644
--- a/src/notifications/components/NotificationsList/NotificationsList.js
+++ b/src/notifications/components/NotificationsList/NotificationsList.js
@@ -1,8 +1,8 @@
-import React, {useEffect} from 'react';
-import {Feed} from 'semantic-ui-react';
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {useParams} from 'react-router-dom';
+import React, { useEffect } from 'react';
+import { Feed } from 'semantic-ui-react';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import { useParams } from 'react-router-dom';
 import * as he from 'he';
 
 import NotificationDisplay from '../NotificationDisplay/NotificationDisplay';
@@ -21,7 +21,7 @@ const NotificationsList = props => {
     getPublicNotifications,
   } = props;
   //const [activeIndex, setActiveIndex] = useState(-1);
-  const {channelId} = useParams();
+  const { channelId } = useParams();
 
   useEffect(() => {
     if (isAuthenticated) getNotifications(channelId, getNotificationsQuery);
-- 
GitLab


From 9fad28686f3090c8c2a2afa5bdcdfe2aed032c22 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 5 Jan 2021 13:53:11 +0100
Subject: [PATCH 09/46] [#28] Added device management (browser only)

---
 public/service.js                             | 110 ++++++++++++++++++
 src/components/preferences/AddPreferences.css |  17 +++
 2 files changed, 127 insertions(+)
 create mode 100644 public/service.js
 create mode 100644 src/components/preferences/AddPreferences.css

diff --git a/public/service.js b/public/service.js
new file mode 100644
index 00000000..663e7afc
--- /dev/null
+++ b/public/service.js
@@ -0,0 +1,110 @@
+/*
+
+// urlB64ToUint8Array is a magic function that will encode the base64 public key
+// to Array buffer which is needed by the subscription option
+const urlB64ToUint8Array = base64String => {
+    const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
+    const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
+    const rawData = atob(base64)
+    const outputArray = new Uint8Array(rawData.length)
+    for (let i = 0; i < rawData.length; ++i) {
+      outputArray[i] = rawData.charCodeAt(i)
+    }
+    return outputArray
+  }
+  
+  
+  const saveSubscription = async subscription => {
+    const SERVER_URL = 'https://send-push-test.app.cern.ch/save-subscription'
+    const response = await fetch(SERVER_URL, {
+      method: 'post',
+      headers: {
+        'Content-Type': 'application/json',
+      },
+      body: JSON.stringify(subscription),
+    })
+    return response.json()
+  }
+  
+  self.addEventListener('activate', async () => {
+    // This will be called only once when the service worker is installed for first time.
+    try {
+      const applicationServerKey = urlB64ToUint8Array(
+        'BDC_Z4KiJzs0II7qLJT3DvlVJ0qcWkCMA6u7Q8B1QnpJeax5kShz6-PjoTM7HlTJua1qlXVGLbiZRT3SBM7ZzaY'
+      )
+      const options = { applicationServerKey, userVisibleOnly: true }
+      const subscription = await self.registration.pushManager.subscribe(options)
+      const response = await saveSubscription(subscription)
+      console.log(response)
+    } catch (err) {
+      console.log('Error', err)
+    }
+  })*/
+  
+  self.addEventListener('push', async function(event) {
+    if (event.data) {
+      console.log('Push event text: ', event.data.text());
+      console.log(event.data);
+  
+      var options = {
+        //body: str,
+        icon: '/images/Logo-Outline-web-Blue100.png',
+        badge: '/images/Logo-Outline-web-Blue100.png',
+      //  image: '/images/pic.jpg',
+        actions: [
+          {
+            action: 'yes-action',
+            title: 'Yes',
+            //icon: '/images/action-1-128x128.png'
+          },
+          {
+            action: 'no-action',
+            title: 'No',
+            //icon: '/images/action-2-128x128.png'
+          }
+        ]
+      };
+  
+      try {
+        const blob = JSON.parse(event.data.text());
+        options.body = blob.message;
+        options.image = blob.image;
+        if (blob.url)
+          options.data = { url: blob.url};
+      }
+      catch {}
+      
+      // fallback is no json blob in message
+      if (!options.body)
+        options.body = event.data.text();
+  
+      // 2 Actions Max
+      //return showLocalNotification('Yolo', event.data.text(), self.registration)
+      event.waitUntil(
+        self.registration.showNotification('Yola', options)
+      );
+    } else {
+      console.log('Push event but no data');
+    }
+  })
+  
+  self.addEventListener('notificationclick', function(event) {
+    if (event.notification.data && event.notification.data.url)
+    {
+      event.notification.close();
+      event.waitUntil(
+        clients.openWindow(event.notification.data.url)
+      );
+    } else
+      console.log('Url undefined.');
+  })
+  
+  const showLocalNotification = (title, body, swRegistration) => {
+    const options = {
+      body,
+      // here you can add more properties like icon, image, vibrate, etc.
+    }
+    return swRegistration.showNotification(title, options)
+  }
+  
+  
\ No newline at end of file
diff --git a/src/components/preferences/AddPreferences.css b/src/components/preferences/AddPreferences.css
new file mode 100644
index 00000000..dd79445f
--- /dev/null
+++ b/src/components/preferences/AddPreferences.css
@@ -0,0 +1,17 @@
+.ui.form .add-preferences-time {
+  display: flex;
+  font-weight: bold;
+  flex-wrap: wrap;
+  vertical-align: middle;
+}
+.ui.form .add-preferences-time-label {
+  margin-right: 1em;
+  font-weight: bold;
+}
+.ui.form .add-preferences-time-clock > input[type='text'] {
+  border: 2px solid #666;
+  width: 60px !important;
+  padding: 5px 8px;
+  color: #333;
+  border-radius: 3px;
+}
-- 
GitLab


From 5840895f6c4f2c1a50e74e6fdcfb4f3556487585 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 5 Jan 2021 16:16:03 +0100
Subject: [PATCH 10/46] [#28] Added try push functionnality

---
 public/service.js | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/public/service.js b/public/service.js
index 663e7afc..b1d58847 100644
--- a/public/service.js
+++ b/public/service.js
@@ -51,6 +51,7 @@ const urlB64ToUint8Array = base64String => {
         icon: '/images/Logo-Outline-web-Blue100.png',
         badge: '/images/Logo-Outline-web-Blue100.png',
       //  image: '/images/pic.jpg',
+      /*
         actions: [
           {
             action: 'yes-action',
@@ -63,12 +64,17 @@ const urlB64ToUint8Array = base64String => {
             //icon: '/images/action-2-128x128.png'
           }
         ]
+        */
       };
   
+      let title = "CERN Notification";
       try {
         const blob = JSON.parse(event.data.text());
         options.body = blob.message;
-        options.image = blob.image;
+        if (blob.title)
+          title = blob.title;
+        if (blob.image)
+          options.image = blob.image;
         if (blob.url)
           options.data = { url: blob.url};
       }
@@ -81,7 +87,7 @@ const urlB64ToUint8Array = base64String => {
       // 2 Actions Max
       //return showLocalNotification('Yolo', event.data.text(), self.registration)
       event.waitUntil(
-        self.registration.showNotification('Yola', options)
+        self.registration.showNotification(title, options)
       );
     } else {
       console.log('Push event but no data');
-- 
GitLab


From 8f82b3d256cd496b4a55e367f42c14bc5641f214 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Wed, 6 Jan 2021 15:43:16 +0100
Subject: [PATCH 11/46] [#28] -

---
 src/components/preferences/AddPreferences.css | 23 ++++++++-----------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/src/components/preferences/AddPreferences.css b/src/components/preferences/AddPreferences.css
index dd79445f..f2486b1b 100644
--- a/src/components/preferences/AddPreferences.css
+++ b/src/components/preferences/AddPreferences.css
@@ -2,16 +2,13 @@
   display: flex;
   font-weight: bold;
   flex-wrap: wrap;
-  vertical-align: middle;
-}
-.ui.form .add-preferences-time-label {
-  margin-right: 1em;
-  font-weight: bold;
-}
-.ui.form .add-preferences-time-clock > input[type='text'] {
-  border: 2px solid #666;
-  width: 60px !important;
-  padding: 5px 8px;
-  color: #333;
-  border-radius: 3px;
-}
+  vertical-align: middle; }
+  .ui.form .add-preferences-time-label {
+    margin-right: 1em;
+    font-weight: bold; }
+  .ui.form .add-preferences-time-clock > input[type='text'] {
+    border: 2px solid #666;
+    width: 60px !important;
+    padding: 5px 8px;
+    color: #333;
+    border-radius: 3px; }
-- 
GitLab


From 553ba815f140a0d5d2a93c7b042cccd195645b4b Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Thu, 14 Jan 2021 09:31:55 +0100
Subject: [PATCH 12/46] list devices on prefernce pages

---
 src/actions/preferences.js                  | 28 +++++++++++++++++++++
 src/components/preferences/AddPreference.js |  1 +
 2 files changed, 29 insertions(+)

diff --git a/src/actions/preferences.js b/src/actions/preferences.js
index cfe21426..490b6249 100644
--- a/src/actions/preferences.js
+++ b/src/actions/preferences.js
@@ -17,6 +17,14 @@ export const TOGGLE_GLOBAL_PREFERENCE = 'TOGGLE_GLOBAL_PREFERENCE';
 export const TOGGLE_GLOBAL_PREFERENCE_SUCCESS = 'TOGGLE_GLOBAL_PREFERENCE_SUCCESS';
 export const TOGGLE_GLOBAL_PREFERENCE_FAILURE = 'TOGGLE_GLOBAL_PREFERENCE_FAILURE';
 
+export const ADD_TARGET_DEVICE = 'ADD_TARGET_DEVICE';
+export const ADD_TARGET_DEVICE_SUCCESS = 'ADD_TARGET_DEVICE_SUCCESS';
+export const ADD_TARGET_DEVICE_FAILURE = 'ADD_TARGET_DEVICE_FAILURE';
+
+export const DEL_TARGET_DEVICE = 'DEL_TARGET_DEVICE';
+export const DEL_TARGET_DEVICE_SUCCESS = 'DEL_TARGET_DEVICE_SUCCESS';
+export const DEL_TARGET_DEVICE_FAILURE = 'DEL_TARGET_DEVICE_FAILURE';
+
 export const getPreferences = channelId => ({
   [RSAA]: {
     endpoint: `${process.env.REACT_APP_BASE_URL}/preferences/${channelId || ''}`,
@@ -61,3 +69,23 @@ export const toggleGlobalPreferenceForChannel = (preferenceId, channelId, isEnab
     ],
   },
 });
+
+export const addTargetDevice = (preferenceId, deviceId, type) => ({
+  [RSAA]: {
+    endpoint: `${process.env.REACT_APP_BASE_URL}/preferences/${preferenceId}/target-devices/${type}/${deviceId}`,
+    method: 'POST',
+    credentials: 'include',
+    headers: withAuth({'Content-Type': 'application/json'}),
+    types: [ADD_TARGET_DEVICE, ADD_TARGET_DEVICE_SUCCESS, ADD_TARGET_DEVICE_FAILURE],
+  },
+});
+
+export const delTargetDevice = (preferenceId, deviceId, type) => ({
+  [RSAA]: {
+    endpoint: `${process.env.REACT_APP_BASE_URL}/preferences/${preferenceId}/target-devices/${type}/${deviceId}`,
+    method: 'DELETE',
+    credentials: 'include',
+    headers: withAuth({'Content-Type': 'application/json'}),
+    types: [DEL_TARGET_DEVICE, DEL_TARGET_DEVICE_SUCCESS, DEL_TARGET_DEVICE_FAILURE],
+  },
+});
diff --git a/src/components/preferences/AddPreference.js b/src/components/preferences/AddPreference.js
index 462a965f..28fe3d48 100644
--- a/src/components/preferences/AddPreference.js
+++ b/src/components/preferences/AddPreference.js
@@ -9,6 +9,7 @@ import * as preferenceActions from 'actions/preferences';
 import * as devicesActions from 'actions/devices';
 import DeviceTypeIcon from 'utils/device-type-icon';
 import * as showSnackBarActionCreators from '../../common/actions/Snackbar';
+import * as devicesActions from 'actions/devices';
 import './AddPreferences.scss';
 import {notificationPriorityTypes} from '../../common/types/NotificationPriorityTypes';
 
-- 
GitLab


From de0fd885f467dd80d3c805d80ffaa79086761571 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Fri, 15 Jan 2021 15:34:10 +0100
Subject: [PATCH 13/46] Management of target devices in preferences

---
 public/service.js                           | 116 --------------------
 src/components/preferences/AddPreference.js |   1 +
 2 files changed, 1 insertion(+), 116 deletions(-)
 delete mode 100644 public/service.js

diff --git a/public/service.js b/public/service.js
deleted file mode 100644
index b1d58847..00000000
--- a/public/service.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
-
-// urlB64ToUint8Array is a magic function that will encode the base64 public key
-// to Array buffer which is needed by the subscription option
-const urlB64ToUint8Array = base64String => {
-    const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
-    const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
-    const rawData = atob(base64)
-    const outputArray = new Uint8Array(rawData.length)
-    for (let i = 0; i < rawData.length; ++i) {
-      outputArray[i] = rawData.charCodeAt(i)
-    }
-    return outputArray
-  }
-  
-  
-  const saveSubscription = async subscription => {
-    const SERVER_URL = 'https://send-push-test.app.cern.ch/save-subscription'
-    const response = await fetch(SERVER_URL, {
-      method: 'post',
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify(subscription),
-    })
-    return response.json()
-  }
-  
-  self.addEventListener('activate', async () => {
-    // This will be called only once when the service worker is installed for first time.
-    try {
-      const applicationServerKey = urlB64ToUint8Array(
-        'BDC_Z4KiJzs0II7qLJT3DvlVJ0qcWkCMA6u7Q8B1QnpJeax5kShz6-PjoTM7HlTJua1qlXVGLbiZRT3SBM7ZzaY'
-      )
-      const options = { applicationServerKey, userVisibleOnly: true }
-      const subscription = await self.registration.pushManager.subscribe(options)
-      const response = await saveSubscription(subscription)
-      console.log(response)
-    } catch (err) {
-      console.log('Error', err)
-    }
-  })*/
-  
-  self.addEventListener('push', async function(event) {
-    if (event.data) {
-      console.log('Push event text: ', event.data.text());
-      console.log(event.data);
-  
-      var options = {
-        //body: str,
-        icon: '/images/Logo-Outline-web-Blue100.png',
-        badge: '/images/Logo-Outline-web-Blue100.png',
-      //  image: '/images/pic.jpg',
-      /*
-        actions: [
-          {
-            action: 'yes-action',
-            title: 'Yes',
-            //icon: '/images/action-1-128x128.png'
-          },
-          {
-            action: 'no-action',
-            title: 'No',
-            //icon: '/images/action-2-128x128.png'
-          }
-        ]
-        */
-      };
-  
-      let title = "CERN Notification";
-      try {
-        const blob = JSON.parse(event.data.text());
-        options.body = blob.message;
-        if (blob.title)
-          title = blob.title;
-        if (blob.image)
-          options.image = blob.image;
-        if (blob.url)
-          options.data = { url: blob.url};
-      }
-      catch {}
-      
-      // fallback is no json blob in message
-      if (!options.body)
-        options.body = event.data.text();
-  
-      // 2 Actions Max
-      //return showLocalNotification('Yolo', event.data.text(), self.registration)
-      event.waitUntil(
-        self.registration.showNotification(title, options)
-      );
-    } else {
-      console.log('Push event but no data');
-    }
-  })
-  
-  self.addEventListener('notificationclick', function(event) {
-    if (event.notification.data && event.notification.data.url)
-    {
-      event.notification.close();
-      event.waitUntil(
-        clients.openWindow(event.notification.data.url)
-      );
-    } else
-      console.log('Url undefined.');
-  })
-  
-  const showLocalNotification = (title, body, swRegistration) => {
-    const options = {
-      body,
-      // here you can add more properties like icon, image, vibrate, etc.
-    }
-    return swRegistration.showNotification(title, options)
-  }
-  
-  
\ No newline at end of file
diff --git a/src/components/preferences/AddPreference.js b/src/components/preferences/AddPreference.js
index 28fe3d48..f2e9bdf4 100644
--- a/src/components/preferences/AddPreference.js
+++ b/src/components/preferences/AddPreference.js
@@ -10,6 +10,7 @@ import * as devicesActions from 'actions/devices';
 import DeviceTypeIcon from 'utils/device-type-icon';
 import * as showSnackBarActionCreators from '../../common/actions/Snackbar';
 import * as devicesActions from 'actions/devices';
+import DeviceTypeIcon from 'utils/device-type-icon';
 import './AddPreferences.scss';
 import {notificationPriorityTypes} from '../../common/types/NotificationPriorityTypes';
 
-- 
GitLab


From 54c305ac9f810613628c41b07a689857694f8b46 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Fri, 15 Jan 2021 15:36:54 +0100
Subject: [PATCH 14/46] [#28] new css for add device, new component for
 deviceicon

---
 src/components/devices/AddDevice.css | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 src/components/devices/AddDevice.css

diff --git a/src/components/devices/AddDevice.css b/src/components/devices/AddDevice.css
new file mode 100644
index 00000000..d0ae1581
--- /dev/null
+++ b/src/components/devices/AddDevice.css
@@ -0,0 +1,8 @@
+.ui.form .add-device {
+  padding: 15px;
+}
+
+.ui.form .add-device-advanced {
+  background-color: #eee;
+  padding: 15px;
+}
-- 
GitLab


From 48c4fa0b816000f633ef856586d40c93e07fc01b Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 19 Jan 2021 14:57:55 +0100
Subject: [PATCH 15/46] Fixed target devices

---
 src/components/devices/AddDevice.css | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/components/devices/AddDevice.css b/src/components/devices/AddDevice.css
index d0ae1581..b6cc7510 100644
--- a/src/components/devices/AddDevice.css
+++ b/src/components/devices/AddDevice.css
@@ -1,5 +1,6 @@
 .ui.form .add-device {
-  padding: 15px;
+  padding-left: 15px;
+  padding-right: 15px;
 }
 
 .ui.form .add-device-advanced {
-- 
GitLab


From 2aef17d0b35fb309925b36206fc55e03e74fdecc Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Wed, 20 Jan 2021 13:27:29 +0000
Subject: [PATCH 16/46] Delete AddDevice.css

---
 src/components/devices/AddDevice.css | 9 ---------
 1 file changed, 9 deletions(-)
 delete mode 100644 src/components/devices/AddDevice.css

diff --git a/src/components/devices/AddDevice.css b/src/components/devices/AddDevice.css
deleted file mode 100644
index b6cc7510..00000000
--- a/src/components/devices/AddDevice.css
+++ /dev/null
@@ -1,9 +0,0 @@
-.ui.form .add-device {
-  padding-left: 15px;
-  padding-right: 15px;
-}
-
-.ui.form .add-device-advanced {
-  background-color: #eee;
-  padding: 15px;
-}
-- 
GitLab


From 5572a2fbcb6a80747b830db6e82994645dab027b Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Wed, 20 Jan 2021 13:27:46 +0000
Subject: [PATCH 17/46] Delete AddPreferences.css

---
 src/components/preferences/AddPreferences.css | 14 --------------
 1 file changed, 14 deletions(-)
 delete mode 100644 src/components/preferences/AddPreferences.css

diff --git a/src/components/preferences/AddPreferences.css b/src/components/preferences/AddPreferences.css
deleted file mode 100644
index f2486b1b..00000000
--- a/src/components/preferences/AddPreferences.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.ui.form .add-preferences-time {
-  display: flex;
-  font-weight: bold;
-  flex-wrap: wrap;
-  vertical-align: middle; }
-  .ui.form .add-preferences-time-label {
-    margin-right: 1em;
-    font-weight: bold; }
-  .ui.form .add-preferences-time-clock > input[type='text'] {
-    border: 2px solid #666;
-    width: 60px !important;
-    padding: 5px 8px;
-    color: #333;
-    border-radius: 3px; }
-- 
GitLab


From d22e11e552bc9dc93f13aca250e995487f3af501 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 26 Jan 2021 08:00:25 +0100
Subject: [PATCH 18/46] fixed radio boxes

---
 src/components/preferences/AddPreference.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/components/preferences/AddPreference.js b/src/components/preferences/AddPreference.js
index f2e9bdf4..b2429b22 100644
--- a/src/components/preferences/AddPreference.js
+++ b/src/components/preferences/AddPreference.js
@@ -109,6 +109,7 @@ const AddPreference = ({
             </label>
           </Form.Field>
           {filter.map(oneDevice => {
+            console.log(devices);
             return (
               <Form.Field
                 key={oneDevice.id}
-- 
GitLab


From 2ad96db8d96d80c75f9bc0da0cabe6a7d215c385 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 26 Jan 2021 09:39:05 +0100
Subject: [PATCH 19/46] checked default device and fixed target device
 selection

---
 src/components/preferences/AddPreference.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/components/preferences/AddPreference.js b/src/components/preferences/AddPreference.js
index b2429b22..f2e9bdf4 100644
--- a/src/components/preferences/AddPreference.js
+++ b/src/components/preferences/AddPreference.js
@@ -109,7 +109,6 @@ const AddPreference = ({
             </label>
           </Form.Field>
           {filter.map(oneDevice => {
-            console.log(devices);
             return (
               <Form.Field
                 key={oneDevice.id}
-- 
GitLab


From 2663eb14f42baa9a03a84d99424c0f728ab84b67 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Thu, 28 Jan 2021 15:45:37 +0100
Subject: [PATCH 20/46] Typo fixed on channel page

---
 .../CreateChannelPage/CreateChannelPage.js    | 234 ++++++++++++++++++
 1 file changed, 234 insertions(+)
 create mode 100644 src/channels/pages/CreateChannelPage/CreateChannelPage.js

diff --git a/src/channels/pages/CreateChannelPage/CreateChannelPage.js b/src/channels/pages/CreateChannelPage/CreateChannelPage.js
new file mode 100644
index 00000000..2adbded4
--- /dev/null
+++ b/src/channels/pages/CreateChannelPage/CreateChannelPage.js
@@ -0,0 +1,234 @@
+import React, {useState} from 'react';
+
+import {connect} from 'react-redux';
+import './CreateChannelPage.css';
+import {bindActionCreators} from 'redux';
+import {Form, Radio, Popup, Segment} from 'semantic-ui-react';
+
+import * as showSnackBarActionCreator from 'common/actions/Snackbar';
+import * as createChannelActionCreator from '../../actions/CreateChannel';
+
+const CreateChannelPage = ({createChannel, showSnackbar, history}) => {
+  const [channel, setChannel] = useState({
+    id: '',
+    name: '',
+    description: '',
+    adminGroup: {
+      groupIdentifier: '',
+    },
+    visibility: 'RESTRICTED',
+    subscriptionPolicy: 'SELF_SUBSCRIPTION',
+    archive: false,
+  });
+
+  const updateField = (e, d) => {
+    setChannel({
+      ...channel,
+      [d.name]: d.value || d.checked,
+    });
+  };
+
+  return (
+    <Segment
+      style={{
+        width: '933px',
+        marginTop: '100px',
+        marginLeft: 'auto',
+        marginRight: 'auto',
+      }}
+    >
+      <Form
+        style={{margin: 100}}
+        onSubmit={() =>
+          createChannel(channel).then(({payload, error}) => {
+            if (error) {
+              showSnackbar(`Error: ${payload.response.message}`, 'error');
+            } else {
+              showSnackbar('The channel has been created successfully', 'success');
+              history.push('/main/channels');
+            }
+          })
+        }
+      >
+        <Form.Input
+          label="ID"
+          name="id"
+          placeholder="ID"
+          value={channel.id}
+          onChange={updateField}
+        />
+        <Form.Input
+          label="Name"
+          name="name"
+          placeholder="Name"
+          value={channel.name}
+          onChange={updateField}
+        />
+        <Form.TextArea
+          label="Description"
+          placeholder="Description"
+          name="description"
+          value={channel.description}
+          onChange={updateField}
+        />
+        <Form.Input
+          label="Admin group"
+          name="adminGroup"
+          placeholder="Admin group"
+          value={channel.adminGroup.groupIdentifier}
+          onChange={(e, d) => {
+            setChannel({
+              ...channel,
+              adminGroup: {
+                ...channel.adminGroup,
+                groupIdentifier: d.value,
+              },
+            });
+          }}
+        />
+        <Popup
+          content="Who can send notifications via e-mail to this channel?"
+          trigger={
+            <Form.Input
+              label="Incoming e-mail address"
+              type="email"
+              name="incomingEmail"
+              placeholder="Incoming e-mail address"
+              value={channel.incomingEmail}
+              onChange={updateField}
+            />
+          }
+        />
+        <Form.Group inline>
+          <label>Visibility</label>
+          <Popup
+            content="Authenticated and not authenticated users can see the channel and its notifications"
+            trigger={
+              <Form.Field
+                control={Radio}
+                label="Public"
+                value="PUBLIC"
+                checked={channel.visibility === 'PUBLIC'}
+                onChange={() => setChannel({...channel, visibility: 'PUBLIC'})}
+              />
+            }
+          />
+          <Popup
+            content="Only authenticated users can see the channel and its notifications"
+            trigger={
+              <Form.Field
+                control={Radio}
+                label="Internal"
+                value="INTERNAL"
+                checked={channel.visibility === 'INTERNAL'}
+                onChange={() => setChannel({...channel, visibility: 'INTERNAL'})}
+              />
+            }
+          />
+          <Popup
+            content="Only channel members can see the channel and its notifictions"
+            trigger={
+              <Form.Field
+                control={Radio}
+                label="Restricted"
+                value="RESTRICTED"
+                checked={channel.visibility === 'RESTRICTED'}
+                onChange={() => setChannel({...channel, visibility: 'RESTRICTED'})}
+              />
+            }
+          />
+        </Form.Group>
+        {channel.visibility !== 'PRIVATE' && (
+          <Form.Group inline>
+            <label>Subscription Policy</label>
+            <Popup
+              content="All authenticated users can subscribe to the channel"
+              trigger={
+                <Form.Field
+                  control={Radio}
+                  label="Self subscription"
+                  value="SELF_SUBSCRIPTION"
+                  checked={channel.subscriptionPolicy === 'SELF_SUBSCRIPTION'}
+                  onChange={() =>
+                    setChannel({
+                      ...channel,
+                      subscriptionPolicy: 'SELF_SUBSCRIPTION',
+                    })
+                  }
+                />
+              }
+            />
+
+            <Popup
+              content="All authenticated users can subscribe to the channel"
+              trigger={
+                <Form.Field
+                  control={Radio}
+                  label="Self subscription with approval"
+                  value="SELF_SUBSCRIPTION_APPROVAL"
+                  checked={channel.subscriptionPolicy === 'SELF_SUBSCRIPTION_APPROVAL'}
+                  onChange={() =>
+                    setChannel({
+                      ...channel,
+                      subscriptionPolicy: 'SELF_SUBSCRIPTION_APPROVAL',
+                    })
+                  }
+                />
+              }
+            />
+          </Form.Group>
+        )}
+
+        <Form.Group inline>
+          <label>Archive</label>
+          <Popup
+            content={`Channel content will be automatically archived in ${process.env.REACT_APP_ARCHIVE_URL}`}
+            trigger={
+              <Form.Field
+                control={Radio}
+                label="Enabled"
+                value="true"
+                checked={channel.archive === true}
+                onChange={() => setChannel({...channel, archive: true})}
+              />
+            }
+          />
+          <Popup
+            content="Channel content will not be archived."
+            trigger={
+              <Form.Field
+                control={Radio}
+                label="Disabled"
+                value="false"
+                checked={channel.archive === false}
+                onChange={() => setChannel({...channel, archive: false})}
+              />
+            }
+          />
+          <span>
+            (archive content to{' '}
+            <a href={process.env.REACT_APP_ARCHIVE_URL} target="_blank" rel="noopener noreferrer">
+              {process.env.REACT_APP_ARCHIVE_URL}
+            </a>
+            )
+          </span>
+        </Form.Group>
+
+        <Form.Button>Submit</Form.Button>
+      </Form>
+    </Segment>
+  );
+};
+
+const mapStateToProps = state => {
+  return {};
+};
+
+const mapDispatchToProps = dispatch => {
+  return {
+    ...bindActionCreators(createChannelActionCreator, dispatch),
+    ...bindActionCreators(showSnackBarActionCreator, dispatch),
+  };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(CreateChannelPage);
-- 
GitLab


From d2cbb60d564668d4415bed087ab758a770fce990 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 5 Jan 2021 13:53:11 +0100
Subject: [PATCH 21/46] [#28] Added device management (browser only)

---
 public/service.js                             | 110 ++++++++++++++++++
 src/components/preferences/AddPreferences.css |  17 +++
 2 files changed, 127 insertions(+)
 create mode 100644 public/service.js
 create mode 100644 src/components/preferences/AddPreferences.css

diff --git a/public/service.js b/public/service.js
new file mode 100644
index 00000000..663e7afc
--- /dev/null
+++ b/public/service.js
@@ -0,0 +1,110 @@
+/*
+
+// urlB64ToUint8Array is a magic function that will encode the base64 public key
+// to Array buffer which is needed by the subscription option
+const urlB64ToUint8Array = base64String => {
+    const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
+    const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
+    const rawData = atob(base64)
+    const outputArray = new Uint8Array(rawData.length)
+    for (let i = 0; i < rawData.length; ++i) {
+      outputArray[i] = rawData.charCodeAt(i)
+    }
+    return outputArray
+  }
+  
+  
+  const saveSubscription = async subscription => {
+    const SERVER_URL = 'https://send-push-test.app.cern.ch/save-subscription'
+    const response = await fetch(SERVER_URL, {
+      method: 'post',
+      headers: {
+        'Content-Type': 'application/json',
+      },
+      body: JSON.stringify(subscription),
+    })
+    return response.json()
+  }
+  
+  self.addEventListener('activate', async () => {
+    // This will be called only once when the service worker is installed for first time.
+    try {
+      const applicationServerKey = urlB64ToUint8Array(
+        'BDC_Z4KiJzs0II7qLJT3DvlVJ0qcWkCMA6u7Q8B1QnpJeax5kShz6-PjoTM7HlTJua1qlXVGLbiZRT3SBM7ZzaY'
+      )
+      const options = { applicationServerKey, userVisibleOnly: true }
+      const subscription = await self.registration.pushManager.subscribe(options)
+      const response = await saveSubscription(subscription)
+      console.log(response)
+    } catch (err) {
+      console.log('Error', err)
+    }
+  })*/
+  
+  self.addEventListener('push', async function(event) {
+    if (event.data) {
+      console.log('Push event text: ', event.data.text());
+      console.log(event.data);
+  
+      var options = {
+        //body: str,
+        icon: '/images/Logo-Outline-web-Blue100.png',
+        badge: '/images/Logo-Outline-web-Blue100.png',
+      //  image: '/images/pic.jpg',
+        actions: [
+          {
+            action: 'yes-action',
+            title: 'Yes',
+            //icon: '/images/action-1-128x128.png'
+          },
+          {
+            action: 'no-action',
+            title: 'No',
+            //icon: '/images/action-2-128x128.png'
+          }
+        ]
+      };
+  
+      try {
+        const blob = JSON.parse(event.data.text());
+        options.body = blob.message;
+        options.image = blob.image;
+        if (blob.url)
+          options.data = { url: blob.url};
+      }
+      catch {}
+      
+      // fallback is no json blob in message
+      if (!options.body)
+        options.body = event.data.text();
+  
+      // 2 Actions Max
+      //return showLocalNotification('Yolo', event.data.text(), self.registration)
+      event.waitUntil(
+        self.registration.showNotification('Yola', options)
+      );
+    } else {
+      console.log('Push event but no data');
+    }
+  })
+  
+  self.addEventListener('notificationclick', function(event) {
+    if (event.notification.data && event.notification.data.url)
+    {
+      event.notification.close();
+      event.waitUntil(
+        clients.openWindow(event.notification.data.url)
+      );
+    } else
+      console.log('Url undefined.');
+  })
+  
+  const showLocalNotification = (title, body, swRegistration) => {
+    const options = {
+      body,
+      // here you can add more properties like icon, image, vibrate, etc.
+    }
+    return swRegistration.showNotification(title, options)
+  }
+  
+  
\ No newline at end of file
diff --git a/src/components/preferences/AddPreferences.css b/src/components/preferences/AddPreferences.css
new file mode 100644
index 00000000..dd79445f
--- /dev/null
+++ b/src/components/preferences/AddPreferences.css
@@ -0,0 +1,17 @@
+.ui.form .add-preferences-time {
+  display: flex;
+  font-weight: bold;
+  flex-wrap: wrap;
+  vertical-align: middle;
+}
+.ui.form .add-preferences-time-label {
+  margin-right: 1em;
+  font-weight: bold;
+}
+.ui.form .add-preferences-time-clock > input[type='text'] {
+  border: 2px solid #666;
+  width: 60px !important;
+  padding: 5px 8px;
+  color: #333;
+  border-radius: 3px;
+}
-- 
GitLab


From b7b0e4b5d25e0e87c2c9688877c6a39a60f0bfef Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 5 Jan 2021 16:16:03 +0100
Subject: [PATCH 22/46] [#28] Added try push functionnality

---
 public/service.js | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/public/service.js b/public/service.js
index 663e7afc..b1d58847 100644
--- a/public/service.js
+++ b/public/service.js
@@ -51,6 +51,7 @@ const urlB64ToUint8Array = base64String => {
         icon: '/images/Logo-Outline-web-Blue100.png',
         badge: '/images/Logo-Outline-web-Blue100.png',
       //  image: '/images/pic.jpg',
+      /*
         actions: [
           {
             action: 'yes-action',
@@ -63,12 +64,17 @@ const urlB64ToUint8Array = base64String => {
             //icon: '/images/action-2-128x128.png'
           }
         ]
+        */
       };
   
+      let title = "CERN Notification";
       try {
         const blob = JSON.parse(event.data.text());
         options.body = blob.message;
-        options.image = blob.image;
+        if (blob.title)
+          title = blob.title;
+        if (blob.image)
+          options.image = blob.image;
         if (blob.url)
           options.data = { url: blob.url};
       }
@@ -81,7 +87,7 @@ const urlB64ToUint8Array = base64String => {
       // 2 Actions Max
       //return showLocalNotification('Yolo', event.data.text(), self.registration)
       event.waitUntil(
-        self.registration.showNotification('Yola', options)
+        self.registration.showNotification(title, options)
       );
     } else {
       console.log('Push event but no data');
-- 
GitLab


From 93202bc712d7b0747c24485338c906ada97f1bdb Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Wed, 6 Jan 2021 15:43:16 +0100
Subject: [PATCH 23/46] [#28] -

---
 src/components/preferences/AddPreferences.css | 23 ++++++++-----------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/src/components/preferences/AddPreferences.css b/src/components/preferences/AddPreferences.css
index dd79445f..f2486b1b 100644
--- a/src/components/preferences/AddPreferences.css
+++ b/src/components/preferences/AddPreferences.css
@@ -2,16 +2,13 @@
   display: flex;
   font-weight: bold;
   flex-wrap: wrap;
-  vertical-align: middle;
-}
-.ui.form .add-preferences-time-label {
-  margin-right: 1em;
-  font-weight: bold;
-}
-.ui.form .add-preferences-time-clock > input[type='text'] {
-  border: 2px solid #666;
-  width: 60px !important;
-  padding: 5px 8px;
-  color: #333;
-  border-radius: 3px;
-}
+  vertical-align: middle; }
+  .ui.form .add-preferences-time-label {
+    margin-right: 1em;
+    font-weight: bold; }
+  .ui.form .add-preferences-time-clock > input[type='text'] {
+    border: 2px solid #666;
+    width: 60px !important;
+    padding: 5px 8px;
+    color: #333;
+    border-radius: 3px; }
-- 
GitLab


From e496f07a6a9047e8c388f3f068b3ff1e80e19d7b Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Fri, 15 Jan 2021 15:34:10 +0100
Subject: [PATCH 24/46] Management of target devices in preferences

---
 public/service.js | 116 ----------------------------------------------
 1 file changed, 116 deletions(-)
 delete mode 100644 public/service.js

diff --git a/public/service.js b/public/service.js
deleted file mode 100644
index b1d58847..00000000
--- a/public/service.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
-
-// urlB64ToUint8Array is a magic function that will encode the base64 public key
-// to Array buffer which is needed by the subscription option
-const urlB64ToUint8Array = base64String => {
-    const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
-    const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
-    const rawData = atob(base64)
-    const outputArray = new Uint8Array(rawData.length)
-    for (let i = 0; i < rawData.length; ++i) {
-      outputArray[i] = rawData.charCodeAt(i)
-    }
-    return outputArray
-  }
-  
-  
-  const saveSubscription = async subscription => {
-    const SERVER_URL = 'https://send-push-test.app.cern.ch/save-subscription'
-    const response = await fetch(SERVER_URL, {
-      method: 'post',
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify(subscription),
-    })
-    return response.json()
-  }
-  
-  self.addEventListener('activate', async () => {
-    // This will be called only once when the service worker is installed for first time.
-    try {
-      const applicationServerKey = urlB64ToUint8Array(
-        'BDC_Z4KiJzs0II7qLJT3DvlVJ0qcWkCMA6u7Q8B1QnpJeax5kShz6-PjoTM7HlTJua1qlXVGLbiZRT3SBM7ZzaY'
-      )
-      const options = { applicationServerKey, userVisibleOnly: true }
-      const subscription = await self.registration.pushManager.subscribe(options)
-      const response = await saveSubscription(subscription)
-      console.log(response)
-    } catch (err) {
-      console.log('Error', err)
-    }
-  })*/
-  
-  self.addEventListener('push', async function(event) {
-    if (event.data) {
-      console.log('Push event text: ', event.data.text());
-      console.log(event.data);
-  
-      var options = {
-        //body: str,
-        icon: '/images/Logo-Outline-web-Blue100.png',
-        badge: '/images/Logo-Outline-web-Blue100.png',
-      //  image: '/images/pic.jpg',
-      /*
-        actions: [
-          {
-            action: 'yes-action',
-            title: 'Yes',
-            //icon: '/images/action-1-128x128.png'
-          },
-          {
-            action: 'no-action',
-            title: 'No',
-            //icon: '/images/action-2-128x128.png'
-          }
-        ]
-        */
-      };
-  
-      let title = "CERN Notification";
-      try {
-        const blob = JSON.parse(event.data.text());
-        options.body = blob.message;
-        if (blob.title)
-          title = blob.title;
-        if (blob.image)
-          options.image = blob.image;
-        if (blob.url)
-          options.data = { url: blob.url};
-      }
-      catch {}
-      
-      // fallback is no json blob in message
-      if (!options.body)
-        options.body = event.data.text();
-  
-      // 2 Actions Max
-      //return showLocalNotification('Yolo', event.data.text(), self.registration)
-      event.waitUntil(
-        self.registration.showNotification(title, options)
-      );
-    } else {
-      console.log('Push event but no data');
-    }
-  })
-  
-  self.addEventListener('notificationclick', function(event) {
-    if (event.notification.data && event.notification.data.url)
-    {
-      event.notification.close();
-      event.waitUntil(
-        clients.openWindow(event.notification.data.url)
-      );
-    } else
-      console.log('Url undefined.');
-  })
-  
-  const showLocalNotification = (title, body, swRegistration) => {
-    const options = {
-      body,
-      // here you can add more properties like icon, image, vibrate, etc.
-    }
-    return swRegistration.showNotification(title, options)
-  }
-  
-  
\ No newline at end of file
-- 
GitLab


From 1272dc17bd7f7130b2abb3076ee3c3bc28224586 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Fri, 15 Jan 2021 15:36:54 +0100
Subject: [PATCH 25/46] [#28] new css for add device, new component for
 deviceicon

---
 src/components/devices/AddDevice.css | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 src/components/devices/AddDevice.css

diff --git a/src/components/devices/AddDevice.css b/src/components/devices/AddDevice.css
new file mode 100644
index 00000000..d0ae1581
--- /dev/null
+++ b/src/components/devices/AddDevice.css
@@ -0,0 +1,8 @@
+.ui.form .add-device {
+  padding: 15px;
+}
+
+.ui.form .add-device-advanced {
+  background-color: #eee;
+  padding: 15px;
+}
-- 
GitLab


From d13a8abd07cc897a90372deeb74be4cfe61c0c00 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 19 Jan 2021 14:57:55 +0100
Subject: [PATCH 26/46] Fixed target devices

---
 src/components/devices/AddDevice.css | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/components/devices/AddDevice.css b/src/components/devices/AddDevice.css
index d0ae1581..b6cc7510 100644
--- a/src/components/devices/AddDevice.css
+++ b/src/components/devices/AddDevice.css
@@ -1,5 +1,6 @@
 .ui.form .add-device {
-  padding: 15px;
+  padding-left: 15px;
+  padding-right: 15px;
 }
 
 .ui.form .add-device-advanced {
-- 
GitLab


From 67bfe7c397abd60c3ec7e208540b712687967cc0 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Wed, 20 Jan 2021 14:25:59 +0100
Subject: [PATCH 27/46] gitignore css

-- 
GitLab


From 7a3966875b3c99d84c7441ba00e5e9e6a97569e5 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Wed, 20 Jan 2021 13:27:29 +0000
Subject: [PATCH 28/46] Delete AddDevice.css

---
 src/components/devices/AddDevice.css | 9 ---------
 1 file changed, 9 deletions(-)
 delete mode 100644 src/components/devices/AddDevice.css

diff --git a/src/components/devices/AddDevice.css b/src/components/devices/AddDevice.css
deleted file mode 100644
index b6cc7510..00000000
--- a/src/components/devices/AddDevice.css
+++ /dev/null
@@ -1,9 +0,0 @@
-.ui.form .add-device {
-  padding-left: 15px;
-  padding-right: 15px;
-}
-
-.ui.form .add-device-advanced {
-  background-color: #eee;
-  padding: 15px;
-}
-- 
GitLab


From 04582d70c3410771bf6e50b5a648af8a257fe764 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Wed, 20 Jan 2021 13:27:46 +0000
Subject: [PATCH 29/46] Delete AddPreferences.css

---
 src/components/preferences/AddPreferences.css | 14 --------------
 1 file changed, 14 deletions(-)
 delete mode 100644 src/components/preferences/AddPreferences.css

diff --git a/src/components/preferences/AddPreferences.css b/src/components/preferences/AddPreferences.css
deleted file mode 100644
index f2486b1b..00000000
--- a/src/components/preferences/AddPreferences.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.ui.form .add-preferences-time {
-  display: flex;
-  font-weight: bold;
-  flex-wrap: wrap;
-  vertical-align: middle; }
-  .ui.form .add-preferences-time-label {
-    margin-right: 1em;
-    font-weight: bold; }
-  .ui.form .add-preferences-time-clock > input[type='text'] {
-    border: 2px solid #666;
-    width: 60px !important;
-    padding: 5px 8px;
-    color: #333;
-    border-radius: 3px; }
-- 
GitLab


From 3daae0be8ac5f0a90d8c194d1b84b67eaa6f2f60 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 26 Jan 2021 08:00:25 +0100
Subject: [PATCH 30/46] fixed radio boxes

---
 src/components/preferences/AddPreference.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/components/preferences/AddPreference.js b/src/components/preferences/AddPreference.js
index f2e9bdf4..b2429b22 100644
--- a/src/components/preferences/AddPreference.js
+++ b/src/components/preferences/AddPreference.js
@@ -109,6 +109,7 @@ const AddPreference = ({
             </label>
           </Form.Field>
           {filter.map(oneDevice => {
+            console.log(devices);
             return (
               <Form.Field
                 key={oneDevice.id}
-- 
GitLab


From 945e1b78b9ec03d4405c1970d7842828203d0ad0 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 26 Jan 2021 09:39:05 +0100
Subject: [PATCH 31/46] checked default device and fixed target device
 selection

---
 src/components/preferences/AddPreference.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/components/preferences/AddPreference.js b/src/components/preferences/AddPreference.js
index b2429b22..f2e9bdf4 100644
--- a/src/components/preferences/AddPreference.js
+++ b/src/components/preferences/AddPreference.js
@@ -109,7 +109,6 @@ const AddPreference = ({
             </label>
           </Form.Field>
           {filter.map(oneDevice => {
-            console.log(devices);
             return (
               <Form.Field
                 key={oneDevice.id}
-- 
GitLab


From 8bef1fd3298d01b34aba89f861d36a339917d167 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Thu, 28 Jan 2021 15:45:37 +0100
Subject: [PATCH 32/46] Typo fixed on channel page

-- 
GitLab


From a7af5833ff8786203c02f7950a358a15acb07287 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Sun, 31 Jan 2021 12:41:02 +0100
Subject: [PATCH 33/46] latest comments 02.02.21 fixed

-- 
GitLab


From c2a27440866e367941672655fa35a6a69a94c6e8 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Sun, 31 Jan 2021 13:31:09 +0100
Subject: [PATCH 34/46] Bugfix on frequency change reset target devices

---
 src/actions/preferences.js | 20 --------------------
 1 file changed, 20 deletions(-)

diff --git a/src/actions/preferences.js b/src/actions/preferences.js
index 490b6249..e0608ac0 100644
--- a/src/actions/preferences.js
+++ b/src/actions/preferences.js
@@ -69,23 +69,3 @@ export const toggleGlobalPreferenceForChannel = (preferenceId, channelId, isEnab
     ],
   },
 });
-
-export const addTargetDevice = (preferenceId, deviceId, type) => ({
-  [RSAA]: {
-    endpoint: `${process.env.REACT_APP_BASE_URL}/preferences/${preferenceId}/target-devices/${type}/${deviceId}`,
-    method: 'POST',
-    credentials: 'include',
-    headers: withAuth({'Content-Type': 'application/json'}),
-    types: [ADD_TARGET_DEVICE, ADD_TARGET_DEVICE_SUCCESS, ADD_TARGET_DEVICE_FAILURE],
-  },
-});
-
-export const delTargetDevice = (preferenceId, deviceId, type) => ({
-  [RSAA]: {
-    endpoint: `${process.env.REACT_APP_BASE_URL}/preferences/${preferenceId}/target-devices/${type}/${deviceId}`,
-    method: 'DELETE',
-    credentials: 'include',
-    headers: withAuth({'Content-Type': 'application/json'}),
-    types: [DEL_TARGET_DEVICE, DEL_TARGET_DEVICE_SUCCESS, DEL_TARGET_DEVICE_FAILURE],
-  },
-});
-- 
GitLab


From 675c105b57faa4a4bce3a2fbb3ab299057d7c37b Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Thu, 4 Feb 2021 08:03:40 +0100
Subject: [PATCH 35/46] Added Safari registration

---
 .env.development | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.env.development b/.env.development
index b22fa355..5bfa60f8 100644
--- a/.env.development
+++ b/.env.development
@@ -1,4 +1,4 @@
-REACT_APP_BASE_URL=https://localhost:8080
-REACT_APP_OAUTH_REDIRECT_URL=https://localhost:3000/redirect
-REACT_APP_OAUTH_LOGOUT_URL=https://localhost:3000/logout
+REACT_APP_BASE_URL=https://192.168.0.168:8080
+REACT_APP_OAUTH_REDIRECT_URL=https://192.168.0.168:3000/redirect
+REACT_APP_OAUTH_LOGOUT_URL=https://192.168.0.168:3000/logout
 REACT_APP_NODE_TLS_REJECT_UNAUTHORIZED=0
-- 
GitLab


From bac96eaa189ab9179de444b395885fe9a584bdbd Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Thu, 4 Feb 2021 08:04:14 +0100
Subject: [PATCH 36/46] Added Safari registration

-- 
GitLab


From 8437ccf84fdee1b79f083095ebfc5d4491405e19 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Thu, 4 Feb 2021 16:47:34 +0100
Subject: [PATCH 37/46] Added uuid for device and safari ref

---
 .env.development | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.env.development b/.env.development
index 5bfa60f8..b22fa355 100644
--- a/.env.development
+++ b/.env.development
@@ -1,4 +1,4 @@
-REACT_APP_BASE_URL=https://192.168.0.168:8080
-REACT_APP_OAUTH_REDIRECT_URL=https://192.168.0.168:3000/redirect
-REACT_APP_OAUTH_LOGOUT_URL=https://192.168.0.168:3000/logout
+REACT_APP_BASE_URL=https://localhost:8080
+REACT_APP_OAUTH_REDIRECT_URL=https://localhost:3000/redirect
+REACT_APP_OAUTH_LOGOUT_URL=https://localhost:3000/logout
 REACT_APP_NODE_TLS_REJECT_UNAUTHORIZED=0
-- 
GitLab


From afb9621d1752c7dfde9662849acf465e86c327f3 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Fri, 5 Feb 2021 16:49:36 +0100
Subject: [PATCH 38/46] Changed notification list and notiifcation display

---
 src/channels/pages/CreateChannelPage/CreateChannelPage.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/channels/pages/CreateChannelPage/CreateChannelPage.js b/src/channels/pages/CreateChannelPage/CreateChannelPage.js
index 2adbded4..a9f6de53 100644
--- a/src/channels/pages/CreateChannelPage/CreateChannelPage.js
+++ b/src/channels/pages/CreateChannelPage/CreateChannelPage.js
@@ -18,7 +18,7 @@ const CreateChannelPage = ({createChannel, showSnackbar, history}) => {
     },
     visibility: 'RESTRICTED',
     subscriptionPolicy: 'SELF_SUBSCRIPTION',
-    archive: false,
+    archive: true,
   });
 
   const updateField = (e, d) => {
-- 
GitLab


From 41852841b26952b00278054e4f04f73fbeaab2f7 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Fri, 5 Feb 2021 16:50:37 +0100
Subject: [PATCH 39/46] Changed notification list and notiifcation display

---
 .../NotificationsList/NotificationList.scss         | 13 +++++++++++++
 1 file changed, 13 insertions(+)
 create mode 100644 src/notifications/components/NotificationsList/NotificationList.scss

diff --git a/src/notifications/components/NotificationsList/NotificationList.scss b/src/notifications/components/NotificationsList/NotificationList.scss
new file mode 100644
index 00000000..63c8e591
--- /dev/null
+++ b/src/notifications/components/NotificationsList/NotificationList.scss
@@ -0,0 +1,13 @@
+.ui.feed .event {
+  .label {
+    margin-top: 10px;
+  }
+
+  .content .extra {
+    margin: 0;
+  }
+
+  .content .extra.text {
+    font-size: small;
+  }
+}
-- 
GitLab


From ef663edbf08629dcc59f068a04527942531bbfc4 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Mon, 15 Feb 2021 13:58:02 +0100
Subject: [PATCH 40/46] Notification Display v0

-- 
GitLab


From 74a93e594ef2c1be053e4aed46b6bdc99a120300 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 16 Feb 2021 09:00:01 +0100
Subject: [PATCH 41/46] Notification Display v0.1

---
 .../NotificationsList/NotificationList.scss         | 13 -------------
 1 file changed, 13 deletions(-)
 delete mode 100644 src/notifications/components/NotificationsList/NotificationList.scss

diff --git a/src/notifications/components/NotificationsList/NotificationList.scss b/src/notifications/components/NotificationsList/NotificationList.scss
deleted file mode 100644
index 63c8e591..00000000
--- a/src/notifications/components/NotificationsList/NotificationList.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.ui.feed .event {
-  .label {
-    margin-top: 10px;
-  }
-
-  .content .extra {
-    margin: 0;
-  }
-
-  .content .extra.text {
-    font-size: small;
-  }
-}
-- 
GitLab


From 66ebc00c894c5cb907398b47b6e76417ebb726d9 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 16 Feb 2021 09:03:51 +0100
Subject: [PATCH 42/46] Notification Display v0.1

-- 
GitLab


From cd471cd1fcdc33b85a45b2c98c93bf01c9fe4d28 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 16 Feb 2021 15:27:25 +0100
Subject: [PATCH 43/46] single notification display and nicer list

---
 src/common/pages/MainPage/MainPage.js         |   9 +-
 .../pages/Notification/NotificationPage.js    |  52 ----
 src/components/devices/AddDevice.js           | 274 ++++++++++--------
 src/components/preferences/AddPreference.js   |   2 -
 src/notifications/actions/GetNotifications.js |  15 -
 .../NotificationsList/NotificationsList.js    |  61 ++--
 .../NotificationsList/NotificationsList.scss  |  22 +-
 .../NotificationsPage/NotificationsPage.js    |   1 +
 .../reducers/NotificationsList.js             |   9 -
 src/registerServiceWorker.js                  |   6 +
 src/utils/notification-type-icon.js           |  10 +-
 11 files changed, 213 insertions(+), 248 deletions(-)
 delete mode 100644 src/common/pages/Notification/NotificationPage.js

diff --git a/src/common/pages/MainPage/MainPage.js b/src/common/pages/MainPage/MainPage.js
index cd1b2f62..044de4e9 100644
--- a/src/common/pages/MainPage/MainPage.js
+++ b/src/common/pages/MainPage/MainPage.js
@@ -12,7 +12,6 @@ import CERNToolBar from '../../components/CERNToolBar/CERNToolBar';
 import EditChannelPage from '../../../channels/pages/EditChannelPage/EditChannelPage';
 import ChannelGlobalPreferences from '../../../channels/pages/ChannelGlobalPreferences/ChannelGlobalPreferences';
 import DevicesGlobal from 'common/pages/Devices/DevicesGlobal';
-import NotificationPage from 'common/pages/Notification/NotificationPage';
 
 const MainPage = props => {
   const {snackbars, isAuthenticated} = props;
@@ -42,19 +41,13 @@ const MainPage = props => {
             <Switch>
               <Route exact path="/main/channels" component={AllChannelsPage} key={1} />
               <Route
-                path="/main/channels/:channelId/notifications"
+                path="/main/channels/:channelId/notifications/:notificationId?"
                 component={NotificationsPage}
                 key={3}
               />
               <Route path="/main/channels/:channelId" component={EditChannelPage} key={4} />
               <Route path="/main/preferences" component={ChannelGlobalPreferences} key={5} />
               <Route path="/main/devices" component={DevicesGlobal} key={6} />
-
-              <Route
-                path="/main/notification/:notificationId"
-                component={NotificationPage}
-                key={7}
-              />
             </Switch>
           </div>
         </div>
diff --git a/src/common/pages/Notification/NotificationPage.js b/src/common/pages/Notification/NotificationPage.js
deleted file mode 100644
index b6ee6ccd..00000000
--- a/src/common/pages/Notification/NotificationPage.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import React, { useEffect } from 'react';
-import PropTypes from 'prop-types';
-import { bindActionCreators } from 'redux';
-import { connect } from 'react-redux';
-import { useParams } from 'react-router-dom';
-
-import * as notificationActions from '../../../notifications/actions/GetNotifications';
-import NotificationDisplay from '../../../notifications/components/NotificationDisplay/NotificationDisplay';
-
-function NotificationPage({ isAuthenticated, notification, getNotification }) {
-  const { notificationId } = useParams();
-
-  useEffect(() => {
-    getNotification(notificationId);
-  }, [getNotification, notificationId]);
-
-  const onCloseModal = () => {
-    console.log('closed modal, redirecting to channel notifications list');
-    console.log(notification);
-    //history.push(`/main/channels/${channel.id}`);
-  };
-
-  // TODO2: onclose modal -> redirect to channel notification list
-  return (
-    isAuthenticated &&
-    notification && (
-      <NotificationDisplay
-        notification={notification}
-        modalinitialopenstate={true}
-        onCloseModal={onCloseModal}
-      />
-    )
-  );
-}
-
-NotificationPage.propTypes = {
-  isAuthenticated: PropTypes.bool.isRequired,
-};
-
-export default connect(
-  state => ({
-    isAuthenticated: state.auth.loggedIn,
-    notification: state.notifications.notificationsList.notification,
-  }),
-  dispatch =>
-    bindActionCreators(
-      {
-        getNotification: notificationActions.getNotification,
-      },
-      dispatch
-    )
-)(NotificationPage);
diff --git a/src/components/devices/AddDevice.js b/src/components/devices/AddDevice.js
index 5c88e73e..f48737b2 100644
--- a/src/components/devices/AddDevice.js
+++ b/src/components/devices/AddDevice.js
@@ -1,7 +1,8 @@
 import React, {useState} from 'react';
 import {bindActionCreators} from 'redux';
 import {connect} from 'react-redux';
-import {Form, Modal, Button, Label, Message, Segment} from 'semantic-ui-react';
+import {Form, Modal, Button, Label, Radio, Message} from 'semantic-ui-react';
+import {v4 as uuidv4} from 'uuid';
 
 import * as deviceActions from 'actions/devices';
 import * as showSnackBarActionCreators from '../../common/actions/Snackbar';
@@ -12,8 +13,6 @@ import './AddDevice.scss';
 
 const clientInformation = getClientInformation();
 
-// Full list
-// const deviceTypesEnum = ['BROWSER', 'ANDROID', 'IOS', 'WINDOWS', 'LINUX', 'MAC', 'MAIL'];
 // Current supported list
 const deviceTypesEnum = ['BROWSER', 'APP', 'MAIL'];
 const deviceSubTypesEnum = [
@@ -27,7 +26,7 @@ const deviceSubTypesEnum = [
   'PRIMARY',
 ];
 
-const AddDevice = ({createDevice, showSnackbar, loading}) => {
+const AddDevice = ({createDevice, showSnackbar}) => {
   const [deviceName, setDeviceName] = useState(clientInformation);
   const [deviceInfo, setDeviceInfo] = useState(navigator.userAgent);
   const [deviceType, setDeviceType] = useState('BROWSER');
@@ -36,6 +35,7 @@ const AddDevice = ({createDevice, showSnackbar, loading}) => {
   const [deviceToken, setDeviceToken] = useState('');
 
   const [modalOpen, setModalOpen] = useState(false);
+  const [submitButtonDisabled, setSubmitButtonDisabled] = useState(false);
   const [advancedAdd, setAdvancedAdd] = useState(false);
   const [workerAlreadyRegistered, setWorkerAlreadyRegistered] = useState(false);
 
@@ -46,145 +46,173 @@ const AddDevice = ({createDevice, showSnackbar, loading}) => {
     setDeviceSubType(isSafari() ? 'SAFARI' : 'OTHER');
     setdeviceUuid(uuidv4());
     setDeviceToken('');
-    setModalOpen(false);
   };
 
   const saveSubscriptionBlob = subscriptionBlob => {
     setDeviceToken(JSON.stringify(subscriptionBlob));
   };
 
-  function handleClose() {
-    resetFormValues();
-  }
-
-  async function handleSubmit() {
-    if (!deviceToken) {
-      showSnackbar(
-        'Notification subscription data missing, please try to toggle subscription again.',
-        'error'
-      );
-      return;
-    }
-
-    const response = await createDevice({
-      name: deviceName,
-      info: deviceInfo,
-      type: deviceType,
-      token: deviceToken,
-    });
-
-    if (response.error) {
-      showSnackbar('An error occurred while adding your device', 'error');
-    } else {
-      handleClose();
-      showSnackbar('The device has been added successfully', 'success');
-    }
-  }
+  const handleTypeChange = (e, d) => setDeviceType(d.value);
+  const handleSubTypeChange = (e, d) => setDeviceSubType(d.value);
 
   return (
     <Modal
-      trigger={
-        <Button primary onClick={() => setModalOpen(true)}>
-          Add device
-        </Button>
-      }
+      trigger={<Button onClick={() => setModalOpen(true)}>Add device</Button>}
       open={modalOpen}
-      onClose={handleClose}
+      onClose={() => setModalOpen(false)}
     >
       <Modal.Header>Add device</Modal.Header>
-      <Segment basic>
-        <Form>
-          <Form.Group grouped className="add-device">
-            <Label color="blue" ribbon>
-              Specify a device or computer name for easy identification in preferences targets
-            </Label>
-            <Form.Input
-              label="Device name"
-              placeholder="Device name"
-              value={deviceName}
-              onChange={(e, d) => setDeviceName(d.value)}
-            />
-          </Form.Group>
-
-          {advancedAdd ? (
-            <Form.Group grouped className="add-device-advanced">
-              <Label color="blue" ribbon>
-                Advanced options, use at your own risk
+      <Modal.Content>
+        <Modal.Description>
+          <Form>
+            <Form.Group grouped className="add-device">
+              <Label color="teal" ribbon>
+                Specify a device or computer name for easy identification in preferences targets
               </Label>
-
+              <Form.Field>
+                <Form.Input
+                  label="Device name"
+                  placeholder="Device name"
+                  value={deviceName}
+                  onChange={(e, d) => setDeviceName(d.value)}
+                />
+              </Form.Field>
+            </Form.Group>
+            {!advancedAdd && (
               <Form.Group grouped>
-                <label>Device Type</label>
-                <Form.Group inline>
-                  {deviceTypesEnum.map(type => (
-                    <Form.Radio
-                      key={type}
-                      label={type.charAt(0) + type.substring(1).toLowerCase()}
-                      name="radioDeviceTypes"
-                      value={type}
-                      checked={deviceType === type}
-                      onChange={(e, d) => setDeviceType(d.value)}
-                    />
-                  ))}
-                </Form.Group>
+                <Button size="mini" onClick={() => setAdvancedAdd(true)} icon="settings" /> Advanced
+                options, use at your own risk
               </Form.Group>
+            )}
+            {advancedAdd && (
+              <Form.Group grouped className="add-device-advanced">
+                <Label color="teal" ribbon>
+                  Advanced options, use at your own risk
+                </Label>
+
+                <Form.Group grouped>
+                  <label>Device Type</label>
+                  <Form.Group inline>
+                    {deviceTypesEnum.map(oneType => (
+                      <Form.Field key={oneType}>
+                        <Radio
+                          label={oneType.charAt(0) + oneType.substring(1).toLowerCase()}
+                          control="input"
+                          name="radioDeviceTypes"
+                          value={oneType}
+                          checked={deviceType === oneType}
+                          onChange={handleTypeChange}
+                        />
+                      </Form.Field>
+                    ))}
+                  </Form.Group>
+
+                  <label>Device Subtype</label>
+                  <Form.Group inline>
+                    {deviceSubTypesEnum.map(oneSubType => (
+                      <Form.Field key={oneSubType}>
+                        <Radio
+                          label={oneSubType.charAt(0) + oneSubType.substring(1).toLowerCase()}
+                          control="input"
+                          name="radioDeviceSubTypes"
+                          value={oneSubType}
+                          checked={deviceSubType === oneSubType}
+                          onChange={handleSubTypeChange}
+                        />
+                      </Form.Field>
+                    ))}
+                  </Form.Group>
+                </Form.Group>
 
-              <Form.Input
-                label="Information"
-                placeholder="Information"
-                value={deviceInfo}
-                onChange={(e, d) => setDeviceInfo(d.value)}
-              />
-
-              <Form.Input
-                label="Device Token"
-                placeholder="Device Token"
-                value={deviceToken}
-                onChange={(e, d) => setDeviceToken(d.value)}
-              />
-            </Form.Group>
-          ) : (
-            <Form.Group grouped>
-              <Button size="mini" onClick={() => setAdvancedAdd(true)} icon="settings" /> Advanced
-              options, use at your own risk
-            </Form.Group>
-          )}
-
-          {deviceType === 'BROWSER' && (
-            <Form.Group grouped className="add-device">
-              <Label color="blue" ribbon>
-                Toggle to activate push notifications, click authorize if browser asks permission
-              </Label>
-              <ServiceWorkerRegistration
-                onSubscription={saveSubscriptionBlob}
-                onLoadStatus={setWorkerAlreadyRegistered}
-              />
-            </Form.Group>
-          )}
-
-          {deviceType === 'BROWSER' && workerAlreadyRegistered && (
-            <Message negative>
-              This browser seems to be already registered. If you want to proceed anyway, make sure
-              you delete the other device entry if it still exists, and toggle the above
-              subscription box off and on.
-            </Message>
-          )}
-
-          <Form.Button disabled={loading} loading={loading} primary onClick={handleSubmit}>
-            Submit
-          </Form.Button>
-        </Form>
-      </Segment>
+                <Form.Field>
+                  <Form.Input
+                    label="Information"
+                    placeholder="Information"
+                    value={deviceInfo}
+                    onChange={(e, d) => setDeviceInfo(d.value)}
+                  />
+                </Form.Field>
+
+                <Form.Field>
+                  <Form.Input
+                    label="Device Token"
+                    placeholder="Device Token"
+                    value={deviceToken}
+                    onChange={(e, d) => setDeviceToken(d.value)}
+                  />
+                </Form.Field>
+              </Form.Group>
+            )}
+
+            {deviceType === 'BROWSER' && (
+              <Form.Group grouped className="add-device">
+                <Label color="teal" ribbon>
+                  Toggle to activate push notifications, click authorize if browser asks permission
+                </Label>
+                {deviceSubType !== 'SAFARI' && (
+                  <ServiceWorkerRegistration
+                    onSubscription={saveSubscriptionBlob}
+                    onLoadStatus={setWorkerAlreadyRegistered}
+                  />
+                )}
+                {deviceSubType === 'SAFARI' && (
+                  <SafariRegistration
+                    onSubscription={saveSubscriptionBlob}
+                    onLoadStatus={setWorkerAlreadyRegistered}
+                    deviceUuid={deviceUuid}
+                  />
+                )}
+              </Form.Group>
+            )}
+
+            {deviceType === 'BROWSER' && deviceSubType !== 'SAFARI' && workerAlreadyRegistered && (
+              <Message negative>
+                This browser seems to be already registered. If you want to proceed anyway, make
+                sure you delete the other device entry if it still exists, and toggle the above
+                subscription box off and on.
+              </Message>
+            )}
+
+            <Form.Button
+              disabled={submitButtonDisabled}
+              onClick={() => {
+                if (deviceToken) {
+                  setSubmitButtonDisabled(true);
+                  createDevice({
+                    name: deviceName,
+                    info: deviceInfo,
+                    type: deviceType,
+                    subType: deviceSubType,
+                    uuid: deviceUuid,
+                    token: deviceToken,
+                  }).then(({error}) => {
+                    setSubmitButtonDisabled(false);
+                    if (error) {
+                      showSnackbar('An error occurred while adding your device', 'error');
+                    } else {
+                      showSnackbar('The device has been added successfully', 'success');
+                      resetFormValues();
+                      setModalOpen(false);
+                    }
+                  });
+                } else {
+                  showSnackbar(
+                    'Notification subscription data missing, please try to toggle subscription again.',
+                    'error'
+                  );
+                }
+              }}
+            >
+              Submit
+            </Form.Button>
+          </Form>
+        </Modal.Description>
+      </Modal.Content>
     </Modal>
   );
 };
 
-const mapStateToProps = state => {
-  return {
-    loading: state.devices.loadingCreate,
-  };
-};
-
-export default connect(mapStateToProps, dispatch =>
+export default connect(null, dispatch =>
   bindActionCreators(
     {
       createDevice: deviceActions.createDevice,
diff --git a/src/components/preferences/AddPreference.js b/src/components/preferences/AddPreference.js
index f2e9bdf4..462a965f 100644
--- a/src/components/preferences/AddPreference.js
+++ b/src/components/preferences/AddPreference.js
@@ -9,8 +9,6 @@ import * as preferenceActions from 'actions/preferences';
 import * as devicesActions from 'actions/devices';
 import DeviceTypeIcon from 'utils/device-type-icon';
 import * as showSnackBarActionCreators from '../../common/actions/Snackbar';
-import * as devicesActions from 'actions/devices';
-import DeviceTypeIcon from 'utils/device-type-icon';
 import './AddPreferences.scss';
 import {notificationPriorityTypes} from '../../common/types/NotificationPriorityTypes';
 
diff --git a/src/notifications/actions/GetNotifications.js b/src/notifications/actions/GetNotifications.js
index 9ce6b8e0..cb36ca60 100644
--- a/src/notifications/actions/GetNotifications.js
+++ b/src/notifications/actions/GetNotifications.js
@@ -7,11 +7,6 @@ export const GET_NOTIFICATIONS = 'GET_NOTIFICATIONS';
 export const GET_NOTIFICATIONS_SUCCESS = 'GET_NOTIFICATIONS_SUCCESS';
 export const GET_NOTIFICATIONS_FAILURE = 'GET_NOTIFICATIONS_FAILURE';
 
-// Get notification (from ID)
-export const GET_NOTIFICATION = 'GET_NOTIFICATION';
-export const GET_NOTIFICATION_SUCCESS = 'GET_NOTIFICATION_SUCCESS';
-export const GET_NOTIFICATION_FAILURE = 'GET_NOTIFICATION_FAILURE';
-
 export const SET_GET_NOTIFICATIONS_QUERY = 'SET_GET_NOTIFICATIONS_QUERY';
 
 export const getNotifications = (channelId, query) => ({
@@ -37,13 +32,3 @@ export const setGetNotificationsQuery = query => {
     payload: query,
   };
 };
-
-export const getNotification = notificationId => ({
-  [RSAA]: {
-    endpoint: `${process.env.REACT_APP_BASE_URL}/notifications/notification/${notificationId}`,
-    method: 'GET',
-    credentials: 'include',
-    headers: withAuth({'Content-Type': 'application/json'}),
-    types: [GET_NOTIFICATION, GET_NOTIFICATION_SUCCESS, GET_NOTIFICATION_FAILURE],
-  },
-});
diff --git a/src/notifications/components/NotificationsList/NotificationsList.js b/src/notifications/components/NotificationsList/NotificationsList.js
index 967ca285..50e22b04 100644
--- a/src/notifications/components/NotificationsList/NotificationsList.js
+++ b/src/notifications/components/NotificationsList/NotificationsList.js
@@ -1,8 +1,8 @@
-import React, { useEffect } from 'react';
-import { Feed } from 'semantic-ui-react';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import { useParams } from 'react-router-dom';
+import React, {useEffect} from 'react';
+import {Item} from 'semantic-ui-react';
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {useParams} from 'react-router-dom';
 import * as he from 'he';
 
 import NotificationDisplay from '../NotificationDisplay/NotificationDisplay';
@@ -16,58 +16,51 @@ const NotificationsList = props => {
     notifications,
     getNotifications,
     getNotificationsQuery,
-    //    setGetNotificationsQuery,
+    //    setGetNotificationsQuery, // was used for search, unsure it's still needed. W8 for search fix.
     isAuthenticated,
     getPublicNotifications,
   } = props;
-  //const [activeIndex, setActiveIndex] = useState(-1);
-  const { channelId } = useParams();
+  const {channelId, notificationId} = useParams();
 
   useEffect(() => {
     if (isAuthenticated) getNotifications(channelId, getNotificationsQuery);
     else getPublicNotifications(channelId, getNotificationsQuery);
   }, [getNotifications, channelId, getNotificationsQuery, getPublicNotifications, isAuthenticated]);
 
-  // const handleClick = (e, titleProps) => {
-  //   const { index } = titleProps;
-  //   const newIndex = activeIndex === index ? -1 : index;
-  //   setActiveIndex(newIndex);
-  // };
-
   const bodyPreview = body => {
     const preview = body.replace(/(<([^>]+)>)/gi, '');
     // Using he.decode to decode html special chars
     return preview && he.decode(preview).substring(0, 100) + '...';
   };
 
-  // const feedIcon = priority => {
-  //   if (priority === 'IMPORTANT') return 'warning circle';
-  //   else return 'info circle';
-  // };
-
   if (notifications.length === 0) {
     return <p>The are no notifications in this channel</p>;
   }
 
   return (
     <>
-      <Feed>
+      <Item.Group divided relaxed>
         {notifications.map((notification, index) => (
-          <Feed.Event key={index} className="sep">
-            <Feed.Label>
-              <NotificationIcon priority={notification.priority} />
-            </Feed.Label>
-            <Feed.Content>
-              <Feed.Date>{new Date(notification.sentAt).toLocaleString()}</Feed.Date>
-              <Feed.Summary>
-                {/* {notification.summary} */}
-                <NotificationDisplay notification={notification} />
-              </Feed.Summary>
-              <Feed.Extra text>{bodyPreview(notification.body)}</Feed.Extra>
-            </Feed.Content>
-          </Feed.Event>
+          <Item key={index}>
+            {notification.imgUrl && <Item.Image size="tiny" src={notification.imgUrl} />}
+            <Item.Content>
+              <Item.Header>
+                <NotificationIcon priority={notification.priority} />
+                <NotificationDisplay
+                  notification={notification}
+                  modalinitialopenstate={
+                    notificationId
+                      ? notification.id.toString() === notificationId.toString()
+                      : false
+                  }
+                />
+              </Item.Header>
+              <Item.Description>{bodyPreview(notification.body)}</Item.Description>
+              <Item.Extra>{new Date(notification.sentAt).toLocaleString()}</Item.Extra>
+            </Item.Content>
+          </Item>
         ))}
-      </Feed>
+      </Item.Group>
 
       {/* <Accordion style={{ width: '100%' }} styled>
         <Visibility
diff --git a/src/notifications/components/NotificationsList/NotificationsList.scss b/src/notifications/components/NotificationsList/NotificationsList.scss
index 3670ff66..48b33058 100644
--- a/src/notifications/components/NotificationsList/NotificationsList.scss
+++ b/src/notifications/components/NotificationsList/NotificationsList.scss
@@ -15,10 +15,30 @@
   }
 
   .content .extra.text {
-    font-size: small;
+    font-size: smaller;
   }
 }
 
 .ui.feed .sep {
   border-bottom: 1px solid $separator-lightgrey;
 }
+
+.ui.items .item .content {
+  .header {
+    // margin-top: -10px !important;
+
+    .button {
+      font-size: large;
+      margin-top: -10px;
+    }
+  }
+
+  .description {
+    margin-top: -6px !important;
+  }
+
+  .extra {
+    margin-top: -2px;
+    font-size: small;
+  }
+}
diff --git a/src/notifications/pages/NotificationsPage/NotificationsPage.js b/src/notifications/pages/NotificationsPage/NotificationsPage.js
index b8db4c37..5156d0bd 100644
--- a/src/notifications/pages/NotificationsPage/NotificationsPage.js
+++ b/src/notifications/pages/NotificationsPage/NotificationsPage.js
@@ -21,6 +21,7 @@ const NotificationsPage = ({
 }) => {
   const [activeItem, setActiveItem] = useState('Notifications');
   const {channelId} = useParams();
+
   const handleItemClick = (e, {name}) => setActiveItem(name);
 
   useEffect(() => {
diff --git a/src/notifications/reducers/NotificationsList.js b/src/notifications/reducers/NotificationsList.js
index 56d4ff43..e62a5175 100644
--- a/src/notifications/reducers/NotificationsList.js
+++ b/src/notifications/reducers/NotificationsList.js
@@ -3,17 +3,12 @@ import {
   GET_NOTIFICATIONS_SUCCESS,
   GET_NOTIFICATIONS_FAILURE,
   SET_GET_NOTIFICATIONS_QUERY,
-  //  GET_NOTIFICATION,
-  GET_NOTIFICATION_SUCCESS,
-  //  GET_NOTIFICATION_SUCCESS,
-  //  GET_NOTIFICATION_FAILURE,
 } from 'notifications/actions/GetNotifications';
 
 import {CREATE_NOTIFICATION_SUCCESS} from 'notifications/actions/CreateNotification';
 
 const INITIAL_STATE = {
   notifications: [],
-  notification: '',
   getNotificationsQuery: {
     searchText: '',
     skip: 0,
@@ -105,10 +100,6 @@ export default function (state = INITIAL_STATE, action) {
     case SET_GET_NOTIFICATIONS_QUERY:
       return processSetGetNotificationsQuery(state, action.payload);
 
-    case GET_NOTIFICATION_SUCCESS: {
-      return {...state, notification: action.payload};
-    }
-
     default:
       return state;
   }
diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js
index 37798c60..9c9ff528 100644
--- a/src/registerServiceWorker.js
+++ b/src/registerServiceWorker.js
@@ -50,11 +50,15 @@ function checkValidServiceWorker(swUrl) {
   // Check if the service worker can be found. If it can't reload the page.
   fetch(swUrl)
     .then(response => {
+      console.log(swUrl);
+
       // Ensure service worker exists, and that we really are getting a JS file.
       if (
         response.status === 404 ||
         response.headers.get('content-type').indexOf('javascript') === -1
       ) {
+        console.log('404');
+
         // No service worker found. Probably a different app. Reload the page.
         navigator.serviceWorker.ready.then(registration => {
           registration.unregister().then(() => {
@@ -62,6 +66,8 @@ function checkValidServiceWorker(swUrl) {
           });
         });
       } else {
+        console.log('register');
+
         // Service worker found. Proceed as normal.
         registerValidSW(swUrl);
       }
diff --git a/src/utils/notification-type-icon.js b/src/utils/notification-type-icon.js
index 8d523bd3..f2f7286c 100644
--- a/src/utils/notification-type-icon.js
+++ b/src/utils/notification-type-icon.js
@@ -2,11 +2,13 @@ import React from 'react';
 import {Icon, Popup} from 'semantic-ui-react';
 
 const NotificationIcon = props => {
-  if (!props.priority || props.priority === 'LOW') return null;
+  if (!props.priority || props.priority === 'LOW' || props.priority === 'NORMAL') return null;
 
-  let icon = <Icon name="info circle" color="green" />;
-  if (props.priority === 'CRITICAL') icon = <Icon name="exclamation triangle" color="red" />;
-  else if (props.priority === 'IMPORTANT') icon = <Icon name="warning circle" color="orange" />;
+  let icon = <Icon name="info circle" color="green" size="small" />;
+  if (props.priority === 'CRITICAL')
+    icon = <Icon name="exclamation triangle" color="red" size="small" />;
+  else if (props.priority === 'IMPORTANT')
+    icon = <Icon name="exclamation" color="orange" size="small" />;
 
   return (
     <Popup trigger={icon} position="top center">
-- 
GitLab


From 221ebd6fc160bfbb62346f5f2640f9b7f0d560d9 Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 16 Feb 2021 15:29:14 +0100
Subject: [PATCH 44/46] removed default serviceworker registration

---
 src/registerServiceWorker.js | 120 -----------------------------------
 1 file changed, 120 deletions(-)
 delete mode 100644 src/registerServiceWorker.js

diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js
deleted file mode 100644
index 9c9ff528..00000000
--- a/src/registerServiceWorker.js
+++ /dev/null
@@ -1,120 +0,0 @@
-// In production, we register a service worker to serve assets from local cache.
-
-// This lets the app load faster on subsequent visits in production, and gives
-// it offline capabilities. However, it also means that developers (and users)
-// will only see deployed updates on the "N+1" visit to a page, since previously
-// cached resources are updated in the background.
-
-// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
-// This link also includes instructions on opting out of this behavior.
-
-const isLocalhost = Boolean(
-  window.location.hostname === 'localhost' ||
-    // [::1] is the IPv6 localhost address.
-    window.location.hostname === '[::1]' ||
-    // 127.0.0.1/8 is considered localhost for IPv4.
-    window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
-);
-
-function registerValidSW(swUrl) {
-  navigator.serviceWorker
-    .register(swUrl)
-    .then(registration => {
-      // eslint-disable-next-line no-param-reassign
-      registration.onupdatefound = () => {
-        const installingWorker = registration.installing;
-        installingWorker.onstatechange = () => {
-          if (installingWorker.state === 'installed') {
-            if (navigator.serviceWorker.controller) {
-              // At this point, the old content will have been purged and
-              // the fresh content will have been added to the cache.
-              // It's the perfect time to display a "New content is
-              // available; please refresh." message in your web app.
-              console.log('New content is available; please refresh.');
-            } else {
-              // At this point, everything has been precached.
-              // It's the perfect time to display a
-              // "Content is cached for offline use." message.
-              console.log('Content is cached for offline use.');
-            }
-          }
-        };
-      };
-    })
-    .catch(error => {
-      console.error('Error during service worker registration:', error);
-    });
-}
-
-function checkValidServiceWorker(swUrl) {
-  // Check if the service worker can be found. If it can't reload the page.
-  fetch(swUrl)
-    .then(response => {
-      console.log(swUrl);
-
-      // Ensure service worker exists, and that we really are getting a JS file.
-      if (
-        response.status === 404 ||
-        response.headers.get('content-type').indexOf('javascript') === -1
-      ) {
-        console.log('404');
-
-        // No service worker found. Probably a different app. Reload the page.
-        navigator.serviceWorker.ready.then(registration => {
-          registration.unregister().then(() => {
-            window.location.reload();
-          });
-        });
-      } else {
-        console.log('register');
-
-        // Service worker found. Proceed as normal.
-        registerValidSW(swUrl);
-      }
-    })
-    .catch(() => {
-      console.log('No internet connection found. App is running in offline mode.');
-    });
-}
-
-export default function register() {
-  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
-    // The URL constructor is available in all browsers that support SW.
-    const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
-    if (publicUrl.origin !== window.location.origin) {
-      // Our service worker won't work if PUBLIC_URL is on a different origin
-      // from what our page is served on. This might happen if a CDN is used to
-      // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
-      return;
-    }
-
-    window.addEventListener('load', () => {
-      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
-
-      if (isLocalhost) {
-        // This is running on localhost. Lets check if a service worker still exists or not.
-        checkValidServiceWorker(swUrl);
-
-        // Add some additional logging to localhost, pointing developers to the
-        // service worker/PWA documentation.
-        navigator.serviceWorker.ready.then(() => {
-          console.log(
-            'This web app is being served cache-first by a service ' +
-              'worker. To learn more, visit https://goo.gl/SC7cgQ'
-          );
-        });
-      } else {
-        // Is not local host. Just register service worker
-        registerValidSW(swUrl);
-      }
-    });
-  }
-}
-
-export function unregister() {
-  if ('serviceWorker' in navigator) {
-    navigator.serviceWorker.ready.then(registration => {
-      registration.unregister();
-    });
-  }
-}
-- 
GitLab


From 987aa2d2197ffc0cfc4ca315e273f8612a2d2b3b Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Tue, 16 Feb 2021 15:32:08 +0100
Subject: [PATCH 45/46] code cleanup

---
 .../NotificationDisplay.js                    |  7 +++--
 .../NotificationsList/NotificationsList.js    | 27 -------------------
 .../NotificationsList/NotificationsList.scss  |  2 --
 3 files changed, 3 insertions(+), 33 deletions(-)

diff --git a/src/notifications/components/NotificationDisplay/NotificationDisplay.js b/src/notifications/components/NotificationDisplay/NotificationDisplay.js
index a6224502..71a4306c 100644
--- a/src/notifications/components/NotificationDisplay/NotificationDisplay.js
+++ b/src/notifications/components/NotificationDisplay/NotificationDisplay.js
@@ -1,5 +1,5 @@
-import React, { useState } from 'react';
-import { Modal, Button, Item, Image } from 'semantic-ui-react';
+import React, {useState} from 'react';
+import {Modal, Button, Item, Image} from 'semantic-ui-react';
 
 import NotificationIcon from '../../../utils/notification-type-icon';
 import './NotificationDisplay.scss';
@@ -32,14 +32,13 @@ const NotificationDisplay = props => {
                   notification && notification.imgUrl ? notification.imgUrl : '/images/empty.png'
                 }
               />
-
               <Item.Content>
                 <Item.Header>
                   <NotificationIcon priority={notification.priority} /> {notification.summary}
                 </Item.Header>
                 <Item.Meta>{new Date(notification.sentAt).toLocaleString()}</Item.Meta>
                 <Item.Description>
-                  <div dangerouslySetInnerHTML={{ __html: notification.body }} />
+                  <div dangerouslySetInnerHTML={{__html: notification.body}} />
                 </Item.Description>
                 {notification && notification.imgUrl && (
                   <Item.Extra>
diff --git a/src/notifications/components/NotificationsList/NotificationsList.js b/src/notifications/components/NotificationsList/NotificationsList.js
index 50e22b04..54fe4c46 100644
--- a/src/notifications/components/NotificationsList/NotificationsList.js
+++ b/src/notifications/components/NotificationsList/NotificationsList.js
@@ -61,33 +61,6 @@ const NotificationsList = props => {
           </Item>
         ))}
       </Item.Group>
-
-      {/* <Accordion style={{ width: '100%' }} styled>
-        <Visibility
-          onBottomVisible={() => {
-            setGetNotificationsQuery({
-              ...getNotificationsQuery,
-              skip: getNotificationsQuery.skip + getNotificationsQuery.take,
-            });
-          }}
-        >
-          {notifications.map((notification, index) => (
-            // eslint-disable-next-line react/jsx-fragments
-            <Fragment>
-              <Accordion.Title active={activeIndex === index} index={index} onClick={handleClick}>
-                <Icon name="dropdown" />
-                {notification.summary}
-                <div style={{ float: 'right' }}>{new Date(notification.sentAt).toLocaleString()}</div>
-              </Accordion.Title>
-              <Accordion.Content active={activeIndex === index}>
-                <div dangerouslySetInnerHTML={{ __html: notification.body }} />
-                <div><img src={notification.imgUrl} border="0" alt="" /></div>
-                <div><a href={notification.link} target="_blank" rel="noopener noreferrer">{notification.link}</a></div>
-              </Accordion.Content>
-            </Fragment>
-          ))}
-        </Visibility>
-      </Accordion> */}
     </>
   );
 };
diff --git a/src/notifications/components/NotificationsList/NotificationsList.scss b/src/notifications/components/NotificationsList/NotificationsList.scss
index 48b33058..a75da83f 100644
--- a/src/notifications/components/NotificationsList/NotificationsList.scss
+++ b/src/notifications/components/NotificationsList/NotificationsList.scss
@@ -25,8 +25,6 @@
 
 .ui.items .item .content {
   .header {
-    // margin-top: -10px !important;
-
     .button {
       font-size: large;
       margin-top: -10px;
-- 
GitLab


From 2b68914df5c3082c1cc2acc2046ab7ddf92cda6e Mon Sep 17 00:00:00 2001
From: Emmanuel Ormancey <emmanuel.ormancey@cern.ch>
Date: Wed, 17 Feb 2021 09:02:20 +0100
Subject: [PATCH 46/46] fixed serviceworker disabling

---
 src/index.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/index.js b/src/index.js
index 3b93fbd2..cacb131f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,7 +6,8 @@ import {MuiThemeProvider, createMuiTheme} from '@material-ui/core/styles';
 
 import {createBrowserHistory} from 'history';
 import configureStore from './store/ConfigureStore';
-import registerServiceWorker from './registerServiceWorker';
+// Disable for now due to conflict with push service worker
+//import registerServiceWorker from './registerServiceWorker';
 import App from './App';
 
 import './index.scss';
@@ -30,4 +31,5 @@ ReactDOM.render(
   </MuiThemeProvider>,
   document.getElementById('root')
 );
-registerServiceWorker();
+// Disable for now due to conflict with push service worker
+//registerServiceWorker();
-- 
GitLab