Skip to content

Commit

Permalink
Merge pull request #250 from wwWallet/enh/render-new-vc
Browse files Browse the repository at this point in the history
Optimize Credential Display with React Context and Highlight Animation
  • Loading branch information
gkatrakazas committed May 21, 2024
2 parents 85b579e + 0282406 commit d5d0201
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 127 deletions.
59 changes: 30 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,35 @@ 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)} />
}
</Suspense>
</Router>
</CredentialsProvider>
</I18nextProvider>
);
}
Expand Down
48 changes: 13 additions & 35 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 All @@ -29,16 +30,12 @@ const ToastDisplay = ({ id, notification }) => {
);
};

const HandlerNotification = ({ children }) => {
const HandlerNotification = () => {
const [notification, setNotification] = useState({ title: '', body: '' });
const [isMessageReceived, setMessageReceived] = useState(null);
const { getData } = 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,28 @@ 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
getData();
})
.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>
);
}
}, [getData]);

return (
<Toaster />
);
};

export default HandlerNotification;
45 changes: 45 additions & 0 deletions src/context/CredentialsContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { createContext, useState, useEffect, useCallback } 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([]);
const [latestCredentials, setLatestCredentials] = useState(new Set());

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

const latestIssuanceDate = vcEntityList[0]?.issuanceDate;
const latestCreds = new Set(vcEntityList.filter(vc => vc.issuanceDate === latestIssuanceDate).map(vc => vc.id));

if (window.location.pathname.includes('/cb')) {
setLatestCredentials(latestCreds);
setTimeout(() => {
setLatestCredentials(new Set());
}, 4000);
}

setVcEntityList(vcEntityList);
} catch (error) {
console.error('Failed to fetch data', error);
}
}, [api]);

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

export default CredentialsContext;
27 changes: 27 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,33 @@ button.reactour__close {
right: 12px;
}

/* Animations for new credentials */
@keyframes highlight-filter {
0%, 100% {
filter: brightness(1);
}
50% {
filter: brightness(1.17);
}
}

@keyframes fade-in {
0% {
opacity: 0;
transform: translateY(-300px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}

.highlight-filter {
animation: highlight-filter 3s ease-in-out;
}

.fade-in {
animation: fade-in 1s ease-in-out;
/* Light and Dark mode input autofill */
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 30px rgb(245, 245, 245) inset !important;
Expand Down

0 comments on commit d5d0201

Please sign in to comment.