Skip to content

Commit

Permalink
Use usecontext for the rerender of credentials when we receive a new one
Browse files Browse the repository at this point in the history
  • Loading branch information
gkatrakazas committed May 15, 2024
1 parent 287f722 commit 4c691b3
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 123 deletions.
60 changes: 31 additions & 29 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Spinner from './components/Spinner'; // Make sure this Spinner component
import { I18nextProvider } from 'react-i18next';
import i18n from './i18n';

import { CredentialsProvider } from './context/CredentialsContext';
import useCheckURL from './components/useCheckURL'; // Import the custom hook
import handleServerMessagesGuard from './hoc/handleServerMessagesGuard';
import HandlerNotification from './components/HandlerNotification';
Expand Down Expand Up @@ -81,35 +82,36 @@ function App() {
};
return (
<I18nextProvider i18n={i18n}>
<Snowfalling />

<Router>
<Suspense fallback={<Spinner />}>
<HandlerNotification>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/settings" element={<PrivateRoute><Settings /></PrivateRoute>} />
<Route path="/" element={<PrivateRoute><Home /></PrivateRoute>} />
<Route path="/credential/:id" element={<PrivateRoute><CredentialDetail /></PrivateRoute>} />
<Route path="/history" element={<PrivateRoute><History /></PrivateRoute>} />
<Route path="/add" element={<PrivateRoute><AddCredentials /></PrivateRoute>} />
<Route path="/send" element={<PrivateRoute><SendCredentials /></PrivateRoute>} />
<Route path="/verification/result" element={<PrivateRoute><VerificationResult /></PrivateRoute>} />
<Route path="/cb" element={<PrivateRoute><Home /></PrivateRoute>} />
<Route path="*" element={<NotFound />} />
</Routes>
{showSelectCredentialsPopup &&
<SelectCredentialsPopup showPopup={showSelectCredentialsPopup} setShowPopup={setShowSelectCredentialsPopup} setSelectionMap={setSelectionMap} conformantCredentialsMap={conformantCredentialsMap} verifierDomainName={verifierDomainName} />
}
{showPinInputPopup &&
<PinInputPopup showPopup={showPinInputPopup} setShowPopup={setShowPinInputPopup} />
}
{showMessagePopup &&
<MessagePopup type={typeMessagePopup} message={textMessagePopup} onClose={() => setMessagePopup(false)} />
}
</HandlerNotification>
</Suspense>
</Router>
<CredentialsProvider>
<Snowfalling />
<Router>
<Suspense fallback={<Spinner />}>
<HandlerNotification>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/settings" element={<PrivateRoute><Settings /></PrivateRoute>} />
<Route path="/" element={<PrivateRoute><Home /></PrivateRoute>} />
<Route path="/credential/:id" element={<PrivateRoute><CredentialDetail /></PrivateRoute>} />
<Route path="/history" element={<PrivateRoute><History /></PrivateRoute>} />
<Route path="/add" element={<PrivateRoute><AddCredentials /></PrivateRoute>} />
<Route path="/send" element={<PrivateRoute><SendCredentials /></PrivateRoute>} />
<Route path="/verification/result" element={<PrivateRoute><VerificationResult /></PrivateRoute>} />
<Route path="/cb" element={<PrivateRoute><Home /></PrivateRoute>} />
<Route path="*" element={<NotFound />} />
</Routes>
{showSelectCredentialsPopup &&
<SelectCredentialsPopup showPopup={showSelectCredentialsPopup} setShowPopup={setShowSelectCredentialsPopup} setSelectionMap={setSelectionMap} conformantCredentialsMap={conformantCredentialsMap} verifierDomainName={verifierDomainName} />
}
{showPinInputPopup &&
<PinInputPopup showPopup={showPinInputPopup} setShowPopup={setShowPinInputPopup} />
}
{showMessagePopup &&
<MessagePopup type={typeMessagePopup} message={textMessagePopup} onClose={() => setMessagePopup(false)} />
}
</HandlerNotification>
</Suspense>
</Router>
</CredentialsProvider>
</I18nextProvider>
);
}
Expand Down
49 changes: 15 additions & 34 deletions src/components/HandlerNotification.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useContext } from 'react';
import toast, { Toaster } from 'react-hot-toast';
import { onMessageListener } from '../firebase';
import { AiOutlineClose } from 'react-icons/ai';
import logo from '../assets/images/logo.png';
import CredentialsContext from '../context/CredentialsContext';

const ToastDisplay = ({ id, notification }) => {
return (
Expand Down Expand Up @@ -31,14 +32,10 @@ const ToastDisplay = ({ id, notification }) => {

const HandlerNotification = ({ children }) => {
const [notification, setNotification] = useState({ title: '', body: '' });
const [isMessageReceived, setMessageReceived] = useState(null);
const { refreshCredentials } = useContext(CredentialsContext);

const showToast = () =>
toast((t) => <ToastDisplay id={t.id} notification={notification} />, {
onClick: () => {
window.location.href = '/';
},
});
toast((t) => <ToastDisplay id={t.id} notification={notification} />);

useEffect(() => {
if (notification?.title) {
Expand All @@ -47,47 +44,31 @@ const HandlerNotification = ({ children }) => {
}, [notification]);

useEffect(() => {
let messageReceived = false;
const unregisterMessageListener = onMessageListener()
const messageListener = onMessageListener()
.then((payload) => {
// Process the received message
setNotification({
title: payload?.notification?.title,
body: payload?.notification?.body,
});
setMessageReceived(true); // Message has been received
refreshCredentials();
})
.catch((err) => {
console.log('Failed to receive message:', err);
setMessageReceived(false); // Set isMessageReceived to false if there's an error
});


return () => {
if (!messageReceived) {
setMessageReceived(false); // Set isMessageReceived to false if no message was received before unmount
if (messageListener && typeof messageListener === 'function') {
messageListener();
}
};
}, []);

// Render just children when waiting for message reception
if (isMessageReceived === null || isMessageReceived === false) {
// Render children when waiting for a message
return (
<div>
{children}
</div>
);
} else {
// Render Toaster and children when a message is received
return (
<div>
<Toaster />
{children}
</div>
);
}
}, [refreshCredentials]);

return (
<div>
<Toaster />
{children}
</div>
);
};

export default HandlerNotification;
41 changes: 41 additions & 0 deletions src/context/CredentialsContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { createContext, useState, useEffect } from 'react';
import { useApi } from '../api';
import { extractCredentialFriendlyName } from '../functions/extractCredentialFriendlyName';

const CredentialsContext = createContext();

export const CredentialsProvider = ({ children }) => {
const api = useApi();
const [vcEntityList, setVcEntityList] = useState([]);

useEffect(() => {
const getData = async () => {
const response = await api.get('/storage/vc');
const vcEntityList = await Promise.all(response.data.vc_list.map(async vcEntity => {
const name = await extractCredentialFriendlyName(vcEntity.credential);
return { ...vcEntity, friendlyName: name };
}));
vcEntityList.sort((vcA, vcB) => vcB.issuanceDate - vcA.issuanceDate);
setVcEntityList(vcEntityList);
};
getData();
}, [api]);

const refreshCredentials = async () => {
const response = await api.get('/storage/vc');
const vcEntityList = await Promise.all(response.data.vc_list.map(async vcEntity => {
const name = await extractCredentialFriendlyName(vcEntity.credential);
return { ...vcEntity, friendlyName: name };
}));
vcEntityList.sort((vcA, vcB) => vcB.issuanceDate - vcA.issuanceDate);
setVcEntityList(vcEntityList);
};

return (
<CredentialsContext.Provider value={{ vcEntityList, refreshCredentials }}>
{children}
</CredentialsContext.Provider>
);
};

export default CredentialsContext;
101 changes: 41 additions & 60 deletions src/pages/Home/Home.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';

Expand All @@ -20,11 +20,11 @@ import FullscreenPopup from '../../components/Popups/FullscreenImg';
import DeletePopup from '../../components/Popups/DeletePopup';
import { CredentialImage } from '../../components/Credentials/CredentialImage';
import QRButton from '../../components/Buttons/QRButton';
import { extractCredentialFriendlyName } from "../../functions/extractCredentialFriendlyName";
import CredentialsContext from '../../context/CredentialsContext';

const Home = () => {
const api = useApi();
const [vcEntityList, setVcEntityList] = useState([]);
const { vcEntityList, refreshCredentials } = useContext(CredentialsContext);
const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth < 768);
const [currentSlide, setCurrentSlide] = useState(1);
const [showFullscreenImgPopup, setShowFullscreenImgPopup] = useState(false);
Expand Down Expand Up @@ -63,19 +63,6 @@ const Home = () => {
};
}, []);

useEffect(() => {
const getData = async () => {
const response = await api.get('/storage/vc');
const vcEntityList = await Promise.all(response.data.vc_list.map(async vcEntity => {
const name = await extractCredentialFriendlyName(vcEntity.credential);
return { ...vcEntity, friendlyName: name };
}));
vcEntityList.sort((vcA, vcB) => vcB.issuanceDate - vcA.issuanceDate);
setVcEntityList(vcEntityList);
};
getData();
}, [api]);

const handleAddCredential = () => {
navigate('/add');
};
Expand All @@ -99,12 +86,12 @@ const Home = () => {
setLoading(true);
try {
await api.del(`/storage/vc/${selectedVcEntity.credentialIdentifier}`);
await refreshCredentials();
} catch (error) {
console.error('Failed to delete data', error);
}
setLoading(false);
setShowDeletePopup(false);
window.location.href = '/';
};

return (
Expand Down Expand Up @@ -137,53 +124,48 @@ const Home = () => {
) : (
<>
<Slider ref={sliderRef} {...settings}>
{vcEntityList && vcEntityList.map((vcEntity, index) => (
<>
<React.Fragment key={vcEntity.id}>
{(currentSlide === index + 1 ? 'button' : 'div')
.split()
.map(Tag => (
<>
<Tag
className="relative rounded-xl xl:w-4/5 md:w-full sm:w-full overflow-hidden transition-shadow shadow-md hover:shadow-lg cursor-pointer w-full mb-2" onClick={() => { setShowFullscreenImgPopup(true); setSelectedVcEntity(vcEntity); }}
aria-label={`${vcEntity.friendlyName}`}
title={t('pageCredentials.credentialFullScreenTitle', { friendlyName: vcEntity.friendlyName })}
>
<CredentialImage credential={vcEntity.credential} className={"w-full h-full object-cover rounded-xl"} />
</Tag>
<div className="flex items-center justify-end">
<span className="mr-4 dark:text-white">{currentSlide} of {vcEntityList.length}</span>
<Tag
onClick={() => sliderRef.current.slickPrev()}
aria-label={currentSlide === 1 ? t('pageCredentials.slideButtonAriaLabelDisable', { direction: t('pageCredentials.slidePrevious') }) : t('pageCredentials.slideButtonAriaLabelEnable', { direction: t('pageCredentials.slidePrevious') })}
title={currentSlide === 1 ? t('pageCredentials.slideButtonTitleDisable', { direction: t('pageCredentials.slidePrevious') }) : t('pageCredentials.slideButtonTitleEnable', { direction: t('pageCredentials.slidePrevious') })}
disabled={currentSlide === 1}
className={`${currentSlide === 1 ? 'opacity-50 cursor-not-allowed dark:text-gray-400' : 'text-primary dark:text-white hover:text-primary-hover dark:hover:text-gray-300'}`}
>
<BiLeftArrow size={22} />
</Tag>
<Tag
onClick={() => sliderRef.current.slickNext()}
aria-label={currentSlide === vcEntityList.length ? t('pageCredentials.slideButtonAriaLabelDisable', { direction: t('pageCredentials.slideNext') }) : t('pageCredentials.slideButtonAriaLabelEnable', { direction: t('pageCredentials.slideNext') })}
title={currentSlide === vcEntityList.length ? t('pageCredentials.slideButtonTitleDisable', { direction: t('pageCredentials.slideNext') }) : t('pageCredentials.slideButtonTitleEnable', { direction: t('pageCredentials.slideNext') })}
disabled={currentSlide === vcEntityList.length}
className={`${currentSlide === vcEntityList.length ? 'opacity-50 cursor-not-allowed dark:text-gray-400' : 'text-primary dark:text-white hover:text-primary-hover dark:hover:text-gray-300'}`}
>
<BiRightArrow size={22} />
</Tag>
</div>
</>
))}

</React.Fragment>

{vcEntityList.map((vcEntity, index) => (
<div key={vcEntity.id}>
{(currentSlide === index + 1 ? 'button' : 'div')
.split()
.map(Tag => (
<Tag
key={`${vcEntity.id}-${Tag}`}
className="relative rounded-xl xl:w-4/5 md:w-full sm:w-full overflow-hidden transition-shadow shadow-md hover:shadow-lg cursor-pointer w-full mb-2"
onClick={() => { setShowFullscreenImgPopup(true); setSelectedVcEntity(vcEntity); }}
aria-label={`${vcEntity.friendlyName}`}
title={t('pageCredentials.credentialFullScreenTitle', { friendlyName: vcEntity.friendlyName })}
>
<CredentialImage credential={vcEntity.credential} className="w-full h-full object-cover rounded-xl" />
</Tag>
))}
<div className="flex items-center justify-end">
<span className="mr-4 dark:text-white">{currentSlide} of {vcEntityList.length}</span>
<button
onClick={() => sliderRef.current.slickPrev()}
aria-label={currentSlide === 1 ? t('pageCredentials.slideButtonAriaLabelDisable', { direction: t('pageCredentials.slidePrevious') }) : t('pageCredentials.slideButtonAriaLabelEnable', { direction: t('pageCredentials.slidePrevious') })}
title={currentSlide === 1 ? t('pageCredentials.slideButtonTitleDisable', { direction: t('pageCredentials.slidePrevious') }) : t('pageCredentials.slideButtonTitleEnable', { direction: t('pageCredentials.slidePrevious') })}
disabled={currentSlide === 1}
className={`${currentSlide === 1 ? 'opacity-50 cursor-not-allowed dark:text-gray-400' : 'text-primary dark:text-white hover:text-primary-hover dark:hover:text-gray-300'}`}
>
<BiLeftArrow size={22} />
</button>
<button
onClick={() => sliderRef.current.slickNext()}
aria-label={currentSlide === vcEntityList.length ? t('pageCredentials.slideButtonAriaLabelDisable', { direction: t('pageCredentials.slideNext') }) : t('pageCredentials.slideButtonAriaLabelEnable', { direction: t('pageCredentials.slideNext') })}
title={currentSlide === vcEntityList.length ? t('pageCredentials.slideButtonTitleDisable', { direction: t('pageCredentials.slideNext') }) : t('pageCredentials.slideButtonTitleEnable', { direction: t('pageCredentials.slideNext') })}
disabled={currentSlide === vcEntityList.length}
className={`${currentSlide === vcEntityList.length ? 'opacity-50 cursor-not-allowed dark:text-gray-400' : 'text-primary dark:text-white hover:text-primary-hover dark:hover:text-gray-300'}`}
>
<BiRightArrow size={22} />
</button>
</div>
<div className={`transition-all ease-in-out duration-500 ${(currentSlide === index + 1) ? 'max-h-auto opacity-100' : 'max-h-0 opacity-0'}`}>
<CredentialInfo credential={vcEntity.credential} />
<CredentialDeleteButton onDelete={() => { setShowDeletePopup(true); setSelectedVcEntity(vcEntity); }} />
<CredentialJson credential={vcEntity.credential} />
</div>

</>
</div>
))}
</Slider>
</>
Expand All @@ -202,7 +184,6 @@ const Home = () => {
<CredentialImage credential={vcEntity.credential} className={"w-full h-full object-cover rounded-xl"} />
</button>
))}

<button
className="step-1 relative rounded-xl overflow-hidden transition-shadow shadow-md hover:shadow-lg cursor-pointer"
onClick={handleAddCredential}
Expand Down

0 comments on commit 4c691b3

Please sign in to comment.