From b5183fb5cd0948b9b8125c2b057c8e46cc611e38 Mon Sep 17 00:00:00 2001 From: Ochieng Paul Date: Wed, 11 Sep 2024 13:01:32 +0300 Subject: [PATCH 1/3] update api page --- platform/public/icons/Actions/DeleteIcon.js | 23 +++ platform/public/icons/Actions/PlusIcon.js | 23 +++ .../components/Settings/API/AddClientForm.jsx | 97 ++++++------ .../Settings/API/EditClientForm.jsx | 139 ++++++++++-------- .../Settings/API/UserClientsTable.jsx | 12 +- platform/src/lib/store/index.js | 13 +- 6 files changed, 194 insertions(+), 113 deletions(-) create mode 100644 platform/public/icons/Actions/DeleteIcon.js create mode 100644 platform/public/icons/Actions/PlusIcon.js diff --git a/platform/public/icons/Actions/DeleteIcon.js b/platform/public/icons/Actions/DeleteIcon.js new file mode 100644 index 0000000000..a926094a06 --- /dev/null +++ b/platform/public/icons/Actions/DeleteIcon.js @@ -0,0 +1,23 @@ +import React from 'react'; + +const DeleteIcon = (props) => { + return ( + + + + ); +}; + +export default DeleteIcon; diff --git a/platform/public/icons/Actions/PlusIcon.js b/platform/public/icons/Actions/PlusIcon.js new file mode 100644 index 0000000000..8dbc4cb84c --- /dev/null +++ b/platform/public/icons/Actions/PlusIcon.js @@ -0,0 +1,23 @@ +import React from 'react'; + +const PlusIcon = (props) => { + return ( + + + + ); +}; + +export default PlusIcon; diff --git a/platform/src/common/components/Settings/API/AddClientForm.jsx b/platform/src/common/components/Settings/API/AddClientForm.jsx index 9196243642..a89f91ca9f 100644 --- a/platform/src/common/components/Settings/API/AddClientForm.jsx +++ b/platform/src/common/components/Settings/API/AddClientForm.jsx @@ -1,12 +1,13 @@ import PersonIcon from '@/icons/Settings/person.svg'; import { useState } from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import DialogWrapper from '../../Modal/DialogWrapper'; import Toast from '@/components/Toast'; import { createClientApi } from '@/core/apis/Settings'; -import { useDispatch } from 'react-redux'; -import { addClients } from '@/lib/store/services/apiClient'; +import { addClients, performRefresh } from '@/lib/store/services/apiClient'; import { getUserDetails } from '@/core/apis/Account'; +import PlusIcon from '@/icons/Actions/PlusIcon'; +import DeleteIcon from '@/icons/Actions/DeleteIcon'; const AddClientForm = ({ open, closeModal }) => { const dispatch = useDispatch(); @@ -17,25 +18,32 @@ const AddClientForm = ({ open, closeModal }) => { type: '', }); const [clientName, setClientName] = useState(''); - const [ipAddress, setIpAddress] = useState(''); + const [ipAddresses, setIpAddresses] = useState(['']); const userInfo = useSelector((state) => state.login.userInfo); - const handleInputValueChange = (type, value) => { + const handleInputValueChange = (type, value, index) => { if (type === 'clientName') { setClientName(value); } else if (type === 'ipAddress') { - setIpAddress(value); + const newIpAddresses = [...ipAddresses]; + newIpAddresses[index] = value; + setIpAddresses(newIpAddresses); } }; - const handleRemoveInputValue = (value) => { - if (value === 'clientName') { + const handleRemoveInputValue = (type, index) => { + if (type === 'clientName') { setClientName(''); - } else if (value === 'ipAddress') { - setIpAddress(''); + } else if (type === 'ipAddress') { + const newIpAddresses = ipAddresses.filter((_, i) => i !== index); + setIpAddresses(newIpAddresses); } }; + const handleAddIpAddress = () => { + setIpAddresses([...ipAddresses, '']); + }; + const handleSubmit = async () => { setLoading(true); @@ -47,13 +55,8 @@ const AddClientForm = ({ open, closeModal }) => { }); }; - // TODO: Handling cases where clientName is empty if (!clientName) { - setIsError({ - isError: true, - message: "Client name can't be empty", - type: 'error', - }); + setErrorState("Client name can't be empty"); setLoading(false); return; } @@ -64,9 +67,9 @@ const AddClientForm = ({ open, closeModal }) => { user_id: userInfo?._id, }; - // Add ipAddress to data if it is not empty - if (ipAddress) { - data.ip_address = ipAddress; + const filteredIpAddresses = ipAddresses.filter((ip) => ip.trim() !== ''); + if (filteredIpAddresses.length > 0) { + data.ip_addresses = filteredIpAddresses; } const response = await createClientApi(data); @@ -78,6 +81,7 @@ const AddClientForm = ({ open, closeModal }) => { dispatch(addClients(res.users[0].clients)); } } + dispatch(performRefresh()); closeModal(); } catch (error) { setErrorState(error?.response?.data?.message || 'Failed to create client'); @@ -91,7 +95,7 @@ const AddClientForm = ({ open, closeModal }) => { open={open} onClose={closeModal} handleClick={handleSubmit} - primaryButtonText={'Register'} + primaryButtonText='Register' loading={loading} ModalIcon={PersonIcon}> {isError?.isError && } @@ -99,7 +103,7 @@ const AddClientForm = ({ open, closeModal }) => { Create new client -
+
{ value={clientName} onChange={(e) => handleInputValueChange('clientName', e.target.value)} /> - - {clientName?.length > 0 && ( + {clientName && ( )}
-
- handleInputValueChange('ipAddress', e.target.value)} - /> - - {ipAddress?.length > 0 && ( - - )} -
+ {ipAddresses.map((ip, index) => ( +
+ 0 ? `Enter IP address ${index + 1}` : 'Enter IP address (Optional)'}`} + className='input input-bordered w-full pl-3 placeholder-shown:text-secondary-neutral-light-300 text-secondary-neutral-light-800 text-sm leading-[26px] border border-secondary-neutral-light-100 bg-secondary-neutral-light-25 rounded' + value={ip} + onChange={(e) => handleInputValueChange('ipAddress', e.target.value, index)} + /> + {index > 0 && ( + + )} +
+ ))} + +
); diff --git a/platform/src/common/components/Settings/API/EditClientForm.jsx b/platform/src/common/components/Settings/API/EditClientForm.jsx index a69bc47d6a..386e3f1736 100644 --- a/platform/src/common/components/Settings/API/EditClientForm.jsx +++ b/platform/src/common/components/Settings/API/EditClientForm.jsx @@ -1,57 +1,70 @@ -import PersonIcon from '@/icons/Settings/person.svg'; -import { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useEffect, useState, useCallback } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; import DialogWrapper from '../../Modal/DialogWrapper'; import Toast from '@/components/Toast'; import { updateClientApi, getClientsApi } from '@/core/apis/Settings'; -import { useDispatch } from 'react-redux'; -import { addClients, addClientsDetails } from '@/lib/store/services/apiClient'; +import { addClients, addClientsDetails, performRefresh } from '@/lib/store/services/apiClient'; import { getUserDetails } from '@/core/apis/Account'; +import PlusIcon from '@/icons/Actions/PlusIcon'; +import DeleteIcon from '@/icons/Actions/DeleteIcon'; +import PersonIcon from '@/icons/Settings/person.svg'; -const EditClientForm = ({ open, closeModal, cIP = '', cName = '', clientID }) => { +const EditClientForm = ({ open, closeModal, data }) => { const dispatch = useDispatch(); + const userInfo = useSelector((state) => state.login.userInfo); + const clientID = data?._id; const [loading, setLoading] = useState(false); const [isError, setIsError] = useState({ isError: false, message: '', type: '', }); - const [clientName, setClientName] = useState(cName); - const [ipAddress, setIpAddress] = useState(cIP); - const userInfo = useSelector((state) => state.login.userInfo); + const [clientName, setClientName] = useState(''); + const [ipAddresses, setIpAddresses] = useState(['']); useEffect(() => { - setClientName(cName); - setIpAddress(cIP); - }, [cName, cIP]); + handleInitialData(); + }, [data]); + + const handleInitialData = () => { + setClientName(data?.name || ''); - const handleInputValueChange = (type, value) => { + const ipAddresses = Array.isArray(data?.ip_addresses) + ? data?.ip_addresses + : data?.ip_addresses + ? [data?.ip_addresses] + : ['']; + + setIpAddresses(ipAddresses); + }; + + const handleInputValueChange = useCallback((type, value, index) => { if (type === 'clientName') { setClientName(value); } else if (type === 'ipAddress') { - setIpAddress(value); + setIpAddresses((prev) => { + const newIpAddresses = [...prev]; + newIpAddresses[index] = value; + return newIpAddresses; + }); } - }; + }, []); - const handleRemoveInputValue = (value) => { - if (value === 'clientName') { + const handleRemoveInputValue = useCallback((type, index) => { + if (type === 'clientName') { setClientName(''); - } else if (value === 'ipAddress') { - setIpAddress(''); + } else if (type === 'ipAddress') { + setIpAddresses((prev) => prev.filter((_, i) => i !== index)); } - }; + }, []); + + const handleAddIpAddress = useCallback(() => { + setIpAddresses((prev) => [...prev, '']); + }, []); const handleSubmit = async () => { setLoading(true); - const setErrorState = (message) => { - setIsError({ - isError: true, - message, - type: 'error', - }); - }; - if (!clientName) { setIsError({ isError: true, @@ -68,33 +81,34 @@ const EditClientForm = ({ open, closeModal, cIP = '', cName = '', clientID }) => user_id: userInfo?._id, }; - if (ipAddress) { - data.ip_address = ipAddress; + const filteredIpAddresses = ipAddresses.filter((ip) => ip.trim() !== ''); + if (filteredIpAddresses.length > 0) { + data.ip_addresses = filteredIpAddresses; } const response = await updateClientApi(data, clientID); - if (response.success !== true) { throw new Error('Failed to update client'); } - const res = await getUserDetails(userInfo?._id); - const resp = await getClientsApi(userInfo?._id); - dispatch(addClients(res.users[0].clients)); dispatch(addClientsDetails(resp.clients)); - + dispatch(performRefresh()); closeModal(); } catch (error) { - setErrorState(error?.response?.data?.message || 'Failed to Edit client'); + setIsError({ + isError: true, + message: error?.response?.data?.message || 'Failed to Edit client', + type: 'error', + }); } finally { setLoading(false); } }; useEffect(() => { - if (isError?.isError) { + if (isError.isError) { const timer = setTimeout(() => { setIsError({ isError: false, @@ -105,22 +119,22 @@ const EditClientForm = ({ open, closeModal, cIP = '', cName = '', clientID }) => return () => clearTimeout(timer); } - }, [isError]); + }, [isError.isError]); return ( - {isError?.isError && } + {isError.isError && }

Edit client

-
+
value={clientName} onChange={(e) => handleInputValueChange('clientName', e.target.value)} /> - - {clientName?.length > 0 && ( + {clientName && ( )}
-
- handleInputValueChange('ipAddress', e.target.value)} - /> - - {ipAddress?.length > 0 && ( + {ipAddresses.map((ip, index) => ( +
+ handleInputValueChange('ipAddress', e.target.value, index)} + /> - )} -
+
+ ))} + +
); }; -export default EditClientForm; +export default React.memo(EditClientForm); diff --git a/platform/src/common/components/Settings/API/UserClientsTable.jsx b/platform/src/common/components/Settings/API/UserClientsTable.jsx index a8345762a8..cd8f52c8c0 100644 --- a/platform/src/common/components/Settings/API/UserClientsTable.jsx +++ b/platform/src/common/components/Settings/API/UserClientsTable.jsx @@ -160,6 +160,12 @@ const UserClientsTable = () => { } }; + const displayIPAddresses = (client) => { + return Array.isArray(client.ip_addresses) + ? client.ip_addresses.join(', ') + : client.ip_addresses; + }; + return (
{isError.isError && } @@ -208,7 +214,7 @@ const UserClientsTable = () => { - {client.ip_address} + {displayIPAddresses(client)}
{ setOpenEditForm(false)} - cIP={selectedClient?.ip_address} - cName={selectedClient?.name} - clientID={selectedClient?._id} + data={selectedClient} /> configureStore({ reducer: persistedReducer, - middleware: getDefaultMiddleware({ - thunk: true, - immutableCheck: false, - serializableCheck: false, - }), + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + thunk: true, + immutableCheck: false, + serializableCheck: false, + }), }); export const wrapper = createWrapper(store); From ea900b848a9cb546ade7ebbd4e57cf8e1c5d5655 Mon Sep 17 00:00:00 2001 From: Ochieng Paul Date: Wed, 11 Sep 2024 13:06:23 +0300 Subject: [PATCH 2/3] updated client admin table --- .../Settings/API/AdminClientsTable.jsx | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/platform/src/common/components/Settings/API/AdminClientsTable.jsx b/platform/src/common/components/Settings/API/AdminClientsTable.jsx index 3364453590..cd21b84620 100644 --- a/platform/src/common/components/Settings/API/AdminClientsTable.jsx +++ b/platform/src/common/components/Settings/API/AdminClientsTable.jsx @@ -101,14 +101,19 @@ const AdminClientsTable = () => { }); }; + const displayIPAddresses = (client) => { + return Array.isArray(client.ip_addresses) + ? client.ip_addresses.join(', ') + : client.ip_addresses; + }; + return (
{isError.isError && } + data-testid='settings-clients-table'> @@ -236,8 +234,7 @@ const AdminClientsTable = () => { onClose={() => setConfirmClientActivation(false)} handleClick={handleActivate} primaryButtonText={'Activate'} - loading={isLoadingActivation} - > + loading={isLoadingActivation}>

Activate client

@@ -248,8 +245,7 @@ const AdminClientsTable = () => { onClose={() => setConfirmClientDeactivation(false)} handleClick={handleDeactivate} primaryButtonText={'Deactivate'} - loading={isLoadingActivation} - > + loading={isLoadingActivation}>

Deactivate client

From 5f15f9a9e2fba16c3adcb793910c3384f93aeae4 Mon Sep 17 00:00:00 2001 From: Ochieng Paul Date: Thu, 12 Sep 2024 12:32:06 +0300 Subject: [PATCH 3/3] worked on isLoadingDeactivation --- .../common/components/Settings/API/AdminClientsTable.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/platform/src/common/components/Settings/API/AdminClientsTable.jsx b/platform/src/common/components/Settings/API/AdminClientsTable.jsx index cd21b84620..5133254569 100644 --- a/platform/src/common/components/Settings/API/AdminClientsTable.jsx +++ b/platform/src/common/components/Settings/API/AdminClientsTable.jsx @@ -19,6 +19,7 @@ const AdminClientsTable = () => { }); const [isLoading, setIsLoading] = useState(false); const [isLoadingActivation, setIsLoadingActivation] = useState(false); + const [isLoadingDeactivation, setIsLoadingDeactivation] = useState(false); const [confirmClientActivation, setConfirmClientActivation] = useState(false); const [confirmClientDeactivation, setConfirmClientDeactivation] = useState(false); const [isActivated, setIsActivated] = useState(false); @@ -80,7 +81,7 @@ const AdminClientsTable = () => { }; const handleDeactivate = async () => { - setIsLoadingActivation(true); + setIsLoadingDeactivation(true); const data = { _id: selectedClient._id, isActive: false, @@ -95,7 +96,7 @@ const AdminClientsTable = () => { setErrorState('Failed to deactivate client', 'error'); }) .finally(() => { - setIsLoadingActivation(false); + setIsLoadingDeactivation(false); setConfirmClientDeactivation(false); setSelectedClient(null); }); @@ -245,7 +246,7 @@ const AdminClientsTable = () => { onClose={() => setConfirmClientDeactivation(false)} handleClick={handleDeactivate} primaryButtonText={'Deactivate'} - loading={isLoadingActivation}> + loading={isLoadingDeactivation}>

Deactivate client

@@ -141,21 +146,18 @@ const AdminClientsTable = () => {
+ className='w-[200px] px-4 py-3 font-medium text-sm leading-5 text-secondary-neutral-light-800 uppercase'> {client?.name} + className='w-[138px] px-4 py-3 font-medium text-sm leading-5 text-secondary-neutral-light-400'> {client?._id} - {client?.ip_address} + className='w-[138px] px-4 py-3 font-medium text-sm leading-5 text-secondary-neutral-light-400'> + {displayIPAddresses(client)}
{ client?.isActive ? 'bg-success-50 text-success-700' : 'bg-secondary-neutral-light-50 text-secondary-neutral-light-500' - }`} - > + }`}> {client?.isActive ? 'Activated' : 'Not Activated'}
+ className='w-[138px] px-4 py-3 font-medium text-sm leading-5 text-secondary-neutral-light-400 capitalize flex items-center gap-2'>
{ }} title={ client?.isActive ? 'Client is already activated' : 'Activate client' - } - > + }>
{ !client?.isActive ? 'Client is already deactivated' : 'Deactivate client' - } - > + }>