Skip to content

Commit

Permalink
Merge pull request #495 from wwWallet/refactor/fetch-data
Browse files Browse the repository at this point in the history
Refactor Credential Management: Reusable Hook and Code Optimization
  • Loading branch information
gkatrakazas authored Jan 16, 2025
2 parents c6d4a78 + 76b26ce commit 4444c89
Show file tree
Hide file tree
Showing 20 changed files with 433 additions and 406 deletions.
14 changes: 7 additions & 7 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,15 @@ function App() {
}

export default withSessionContext(
withCredentialParserContext(
withCredentialsContext(
withCredentialParserContext(
withOpenID4VPContext(
withOpenID4VCIContext(
withUriHandler(
App
)
withOpenID4VPContext(
withOpenID4VCIContext(
withUriHandler(
App
)
)
)
)
);
)
);
22 changes: 3 additions & 19 deletions src/components/Credentials/CredentialImage.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,15 @@
import { useState, useEffect, useContext } from "react";
import ExpiredRibbon from './ExpiredRibbon';
import UsagesRibbon from "./UsagesRibbon";
import CredentialParserContext from "../../context/CredentialParserContext";

const CredentialImage = ({ credential, className, onClick, showRibbon = true, vcEntityInstances = null }) => {
const [parsedCredential, setParsedCredential] = useState(null);
const { credentialParserRegistry } = useContext(CredentialParserContext);

useEffect(() => {
if (credentialParserRegistry) {
credentialParserRegistry.parse(credential).then((c) => {
if ('error' in c) {
return;
}
setParsedCredential(c);
});
}

}, [credential, credentialParserRegistry]);
const CredentialImage = ({ parsedCredential, className, onClick, showRibbon = true, vcEntityInstances = null }) => {

return (
<>
{parsedCredential && parsedCredential.credentialImage && (
{parsedCredential && (
<img src={parsedCredential.credentialImage.credentialImageURL} alt={"Credential"} className={className} onClick={onClick} />
)}
{parsedCredential && showRibbon &&
<ExpiredRibbon parsedCredential={parsedCredential.beautifiedForm} />
<ExpiredRibbon parsedCredential={parsedCredential} />
}
{vcEntityInstances && showRibbon &&
<UsagesRibbon vcEntityInstances={vcEntityInstances} />
Expand Down
43 changes: 14 additions & 29 deletions src/components/Credentials/CredentialInfo.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React, { useContext, useEffect, useState } from 'react';
import React from 'react';
import { BiSolidCategoryAlt, BiSolidUserCircle } from 'react-icons/bi';
import { AiFillCalendar } from 'react-icons/ai';
import { RiPassExpiredFill } from 'react-icons/ri';
import { MdTitle, MdGrade, MdOutlineNumbers } from 'react-icons/md';
import { GiLevelEndFlag } from 'react-icons/gi';
import { formatDate } from '../../functions/DateFormat';
import useScreenType from '../../hooks/useScreenType';
import CredentialParserContext from '../../context/CredentialParserContext';

const getFieldIcon = (fieldName) => {
switch (fieldName) {
Expand Down Expand Up @@ -51,41 +50,27 @@ const renderRow = (fieldName, label, fieldValue, screenType) => {
}
};

const CredentialInfo = ({ credential, mainClassName = "text-sm lg:text-base w-full" }) => {
const CredentialInfo = ({ parsedCredential, mainClassName = "text-sm lg:text-base w-full" }) => {

const [parsedCredential, setParsedCredential] = useState(null);
const { credentialParserRegistry } = useContext(CredentialParserContext);
const screenType = useScreenType();

useEffect(() => {
if (credentialParserRegistry) {
credentialParserRegistry.parse(credential).then((c) => {
if ('error' in c) {
return;
}
setParsedCredential(c.beautifiedForm);
});
}

}, [credential, credentialParserRegistry]);

return (
<div className={mainClassName}>
<table className="lg:w-4/5">
<tbody className="divide-y-4 divide-transparent">
{parsedCredential && (
{parsedCredential.beautifiedForm && (
<>
{renderRow('expdate', 'Expiration', formatDate(new Date(parsedCredential?.exp * 1000).toISOString()), screenType)}
{renderRow('familyName', 'Family Name', parsedCredential?.family_name, screenType)}
{renderRow('firstName', 'Given Name', parsedCredential?.given_name, screenType)}
{renderRow('id', 'Personal ID', parsedCredential?.personal_identifier, screenType)}
{renderRow('dateOfBirth', 'Birthday', formatDate(parsedCredential?.dateOfBirth, 'date'), screenType)}
{renderRow('dateOfBirth', 'Birthday', formatDate(parsedCredential?.birth_date, 'date'), screenType)}
{renderRow('diplomaTitle', 'Title', parsedCredential?.title, screenType)}
{renderRow('eqfLevel', 'EQF', parsedCredential?.eqf_level, screenType)}
{renderRow('grade', 'Grade', parsedCredential?.grade, screenType)}
{renderRow('id', 'Social Security Number', parsedCredential?.ssn, screenType)}
{renderRow('id', 'Document Number', parsedCredential?.document_number, screenType)}
{renderRow('expdate', 'Expiration', formatDate(new Date(parsedCredential.beautifiedForm?.exp * 1000).toISOString()), screenType)}
{renderRow('familyName', 'Family Name', parsedCredential.beautifiedForm?.family_name, screenType)}
{renderRow('firstName', 'Given Name', parsedCredential.beautifiedForm?.given_name, screenType)}
{renderRow('id', 'Personal ID', parsedCredential.beautifiedForm?.personal_identifier, screenType)}
{renderRow('dateOfBirth', 'Birthday', formatDate(parsedCredential.beautifiedForm?.dateOfBirth, 'date'), screenType)}
{renderRow('dateOfBirth', 'Birthday', formatDate(parsedCredential.beautifiedForm?.birth_date, 'date'), screenType)}
{renderRow('diplomaTitle', 'Title', parsedCredential.beautifiedForm?.title, screenType)}
{renderRow('eqfLevel', 'EQF', parsedCredential.beautifiedForm?.eqf_level, screenType)}
{renderRow('grade', 'Grade', parsedCredential.beautifiedForm?.grade, screenType)}
{renderRow('id', 'Social Security Number', parsedCredential.beautifiedForm?.ssn, screenType)}
{renderRow('id', 'Document Number', parsedCredential.beautifiedForm?.document_number, screenType)}
</>
)}
</tbody>
Expand Down
21 changes: 3 additions & 18 deletions src/components/Credentials/CredentialJson.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
// CredentialJson.js

import React, { useEffect, useState, useContext } from 'react';
import CredentialParserContext from "../../context/CredentialParserContext";
import React from 'react';


const CredentialJson = ({ credential, textAreaRows='10' }) => {
const { credentialParserRegistry } = useContext(CredentialParserContext);
const [parsedCredential, setParsedCredential] = useState(null);

useEffect(() => {
if (credentialParserRegistry) {
credentialParserRegistry.parse(credential).then((c) => {
if ('error' in c) {
return;
}
setParsedCredential(c.beautifiedForm);
});
}
}, [credential, credentialParserRegistry]);
const CredentialJson = ({ parsedCredential, textAreaRows='10' }) => {

return (
<div className='w-full'>
Expand All @@ -27,7 +12,7 @@ const CredentialJson = ({ credential, textAreaRows='10' }) => {
rows={textAreaRows}
readOnly
className="dark:bg-gray-900 dark:text-white border rounded p-2 text-sm w-full rounded-xl"
value={JSON.stringify(parsedCredential, null, 2)}
value={JSON.stringify(parsedCredential.beautifiedForm, null, 2)}
/>
</div>
)}
Expand Down
46 changes: 12 additions & 34 deletions src/components/Credentials/CredentialLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { PiCardsBold } from "react-icons/pi";

// Hooks
import useScreenType from '../../hooks/useScreenType';
import { useVcEntity } from '../../hooks/useVcEntity';

// Contexts
import SessionContext from '../../context/SessionContext';
import CredentialsContext from '../../context/CredentialsContext';

//Functions
import { CheckExpired } from '../../functions/CheckExpired';
Expand All @@ -19,13 +20,10 @@ import { H1 } from '../Shared/Heading';
import CredentialImage from './CredentialImage';
import FullscreenPopup from '../Popups/FullscreenImg';
import PageDescription from '../Shared/PageDescription';
import CredentialParserContext from '../../context/CredentialParserContext';

const CredentialLayout = ({ children, title = null }) => {
const { credentialId } = useParams();
const { api } = useContext(SessionContext);
const screenType = useScreenType();
const [vcEntity, setVcEntity] = useState(null);
const [showFullscreenImgPopup, setShowFullscreenImgPopup] = useState(false);
const [credentialFiendlyName, setCredentialFriendlyName] = useState(null);
const { t } = useTranslation();
Expand All @@ -34,38 +32,18 @@ const CredentialLayout = ({ children, title = null }) => {
const [zeroSigCount, setZeroSigCount] = useState(null)
const [sigTotal, setSigTotal] = useState(null);

const { credentialParserRegistry } = useContext(CredentialParserContext);
const { vcEntityList, fetchVcData } = useContext(CredentialsContext);
const vcEntity = useVcEntity(fetchVcData, vcEntityList, credentialId);

useEffect(() => {
const getData = async () => {
const response = await api.get('/storage/vc');
const vcEntity = response.data.vc_list
.filter((vcEntity) => vcEntity.credentialIdentifier === credentialId)[0];
const vcEntityInstances = response.data.vc_list
.filter((vcEntity) => vcEntity.credentialIdentifier === credentialId);
setZeroSigCount(vcEntityInstances.filter(instance => instance.sigCount === 0).length || 0);
setSigTotal(vcEntityInstances.length);
if (!vcEntity) {
throw new Error("Credential not found");
}
setVcEntity(vcEntity);
};

getData();
}, [api, credentialId]);
if (vcEntity) {
setZeroSigCount(vcEntity.instances.filter(instance => instance.sigCount === 0).length || 0);
setSigTotal(vcEntity.instances.length);
setIsExpired(CheckExpired(vcEntity.parsedCredential.beautifiedForm.expiry_date ?? vcEntity.parsedCredential.beautifiedForm.expiry_date))
setCredentialFriendlyName(vcEntity.parsedCredential.credentialFriendlyName);

useEffect(() => {
if (!vcEntity) {
return;
}
credentialParserRegistry.parse(vcEntity.credential).then((c) => {
if ('error' in c) {
return;
}
setIsExpired(CheckExpired(c.beautifiedForm.exp ?? c.beautifiedForm.expiry_date ))
setCredentialFriendlyName(c.credentialFriendlyName);
});
}, [vcEntity, credentialParserRegistry]);
}, [vcEntity]);

const UsageStats = ({ zeroSigCount, sigTotal }) => {
if (zeroSigCount === null || sigTotal === null) return null;
Expand Down Expand Up @@ -119,7 +97,7 @@ const CredentialLayout = ({ children, title = null }) => {
aria-label={`${credentialFiendlyName}`}
title={t('pageCredentials.credentialFullScreenTitle', { friendlyName: credentialFiendlyName })}
>
<CredentialImage vcEntity={vcEntity} credential={vcEntity.credential} className={"w-full object-cover"} showRibbon={screenType !== 'mobile'} />
<CredentialImage vcEntity={vcEntity} parsedCredential={vcEntity.parsedCredential} className={"w-full object-cover"} showRibbon={screenType !== 'mobile'} />
</button>
{screenType !== 'mobile' && zeroSigCount !== null && sigTotal &&
<UsageStats zeroSigCount={zeroSigCount} sigTotal={sigTotal} />
Expand Down Expand Up @@ -159,7 +137,7 @@ const CredentialLayout = ({ children, title = null }) => {
isOpen={showFullscreenImgPopup}
onClose={() => setShowFullscreenImgPopup(false)}
content={
<CredentialImage credential={vcEntity.credential} className={"max-w-full max-h-full rounded-xl"} showRibbon={false} />
<CredentialImage parsedCredential={vcEntity.parsedCredential} className={"max-w-full max-h-full rounded-xl"} showRibbon={false} />
}
/>
)}
Expand Down
45 changes: 33 additions & 12 deletions src/components/History/HistoryDetailContent.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// External libraries
import React from 'react';
import { useTranslation } from 'react-i18next';
import React, { useContext, useEffect, useState } from 'react';

// Context
import CredentialParserContext from '../../context/CredentialParserContext';

// Components
import Slider from '../Shared/Slider';
Expand All @@ -10,35 +12,54 @@ import CredentialInfo from '../Credentials/CredentialInfo';
import useScreenType from '../../hooks/useScreenType';

const HistoryDetailContent = ({ historyItem }) => {
const { t } = useTranslation();
const [currentSlide, setCurrentSlide] = React.useState(1);
const [parsedCredentials, setParsedCredentials] = useState([]);
const { parseCredential } = useContext(CredentialParserContext);
const screenType = useScreenType();

const renderSlideContent = (credential) => (
// Parse all the credentials when historyItem changes
useEffect(() => {
const parseAllCredentials = async () => {
const parsed = await Promise.all(
historyItem.map(async (credential) => {
const parsed = await parseCredential(credential);
return parsed; // Store each parsed credential
})
);
setParsedCredentials(parsed);
};

// Parse credentials on historyItem change
if (historyItem.length > 0) {
parseAllCredentials();
}
}, [historyItem, parseCredential]);

const renderSlideContent = (parsedCredential, index) => (
<div
key={credential.id}
className="relative rounded-xl w-full transition-shadow shadow-md hover:shadow-lg cursor-pointer"
aria-label={credential.friendlyName}
title={t('pageCredentials.credentialFullScreenTitle', { friendlyName: credential.friendlyName })}
key={index}
className="relative rounded-xl w-full transition-shadow shadow-md hover:shadow-lg"
aria-label={parsedCredential.credentialFriendlyName}
title={parsedCredential.credentialFriendlyName}
>
<CredentialImage credential={credential} showRibbon={false} className="w-full h-full rounded-xl" />
<CredentialImage parsedCredential={parsedCredential} showRibbon={false} className="w-full h-full rounded-xl" />
</div>
);

return (
<div className="py-2 w-full">
<div className="px-2">
<Slider
items={historyItem}
items={parsedCredentials}
renderSlideContent={renderSlideContent}
onSlideChange={(currentIndex) => setCurrentSlide(currentIndex + 1)}
/>
</div>

{/* Render details of the currently selected credential */}
{historyItem[currentSlide - 1] && (
{parsedCredentials[currentSlide - 1] && (
<div className={`pt-5 ${screenType !== 'mobile' ? 'overflow-y-auto items-center custom-scrollbar max-h-[30vh]' : ''} `}>
<CredentialInfo credential={historyItem[currentSlide - 1]} />
<CredentialInfo parsedCredential={parsedCredentials[currentSlide - 1]} />
</div>
)}
</div>
Expand Down
27 changes: 8 additions & 19 deletions src/components/Popups/SelectCredentialsPopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const StepBar = ({ totalSteps, currentStep, stepTitles }) => {
);
};

function SelectCredentialsPopup({ popupState, setPopupState, showPopup, hidePopup, vcEntityList, vcEntityListInstances }) {
function SelectCredentialsPopup({ popupState, setPopupState, showPopup, hidePopup, vcEntityList }) {

const { api } = useContext(SessionContext);
const credentialParserContext = useContext(CredentialParserContext);
Expand Down Expand Up @@ -90,22 +90,12 @@ function SelectCredentialsPopup({ popupState, setPopupState, showPopup, hidePopu
}

try {
const vcEntities = await Promise.all(
vcEntityList.map(async vcEntity => {
return credentialParserContext.credentialParserRegistry.parse(vcEntity.credential).then((c) => {
if ('error' in c) {
return;
}
return { ...vcEntity, friendlyName: c.credentialFriendlyName }
});
})
);

const filteredVcEntities = vcEntities.filter(vcEntity =>
const filteredVcEntities = vcEntityList.filter(vcEntity =>
popupState.options.conformantCredentialsMap[keys[currentIndex]].credentials.includes(vcEntity.credentialIdentifier)
);

setRequestedFields(popupState.options.conformantCredentialsMap[keys[currentIndex]].requestedFields);
console.log('filteredVcEntities',filteredVcEntities)
setVcEntities(filteredVcEntities);
} catch (error) {
console.error('Failed to fetch data', error);
Expand All @@ -127,7 +117,6 @@ function SelectCredentialsPopup({ popupState, setPopupState, showPopup, hidePopu
reinitialize
]);


useEffect(() => {
if (popupState?.options) {
const currentKey = keys[currentIndex];
Expand Down Expand Up @@ -176,13 +165,13 @@ function SelectCredentialsPopup({ popupState, setPopupState, showPopup, hidePopu
className="relative rounded-xl transition-shadow shadow-md hover:shadow-xl cursor-pointer"
tabIndex={currentSlide !== vcEntities.indexOf(vcEntity) + 1 ? -1 : 0}
onClick={() => handleClick(vcEntity.credentialIdentifier)}
aria-label={`${vcEntity.friendlyName}`}
title={t('selectCredentialPopup.credentialSelectTitle', { friendlyName: vcEntity.friendlyName })}
aria-label={`${vcEntity.parsedCredential.credentialFriendlyName}`}
title={t('selectCredentialPopup.credentialSelectTitle', { friendlyName: vcEntity.parsedCredential.credentialFriendlyName })}
>
<CredentialImage
vcEntityInstances={vcEntityListInstances.filter((vc) => vc.credentialIdentifier === vcEntity.credentialIdentifier)}
vcEntityInstances={vcEntity.instances}
key={vcEntity.credentialIdentifier}
credential={vcEntity.credential}
parsedCredential={vcEntity.parsedCredential}
className="w-full object-cover rounded-xl"
showRibbon={currentSlide === vcEntities.indexOf(vcEntity) + 1}
/>
Expand Down Expand Up @@ -265,7 +254,7 @@ function SelectCredentialsPopup({ popupState, setPopupState, showPopup, hidePopu
</div>
{vcEntities[currentSlide - 1] && (
<div className={`flex flex-wrap justify-center flex flex-row justify-center items-center mb-2 ${screenType === 'desktop' && 'overflow-y-auto items-center custom-scrollbar max-h-[20vh]'} ${screenType === 'tablet' && 'px-24'}`}>
<CredentialInfo credential={vcEntities[currentSlide - 1].credential} mainClassName={"text-xs w-full"} />
<CredentialInfo parsedCredential={vcEntities[currentSlide - 1].parsedCredential} mainClassName={"text-xs w-full"} />
</div>
)}
<div className={`flex justify-between pt-4 z-10 ${screenType !== 'desktop' && 'fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-800 flex px-6 pb-6 flex shadow-2xl rounded-t-lg w-auto'}`}>
Expand Down
Loading

0 comments on commit 4444c89

Please sign in to comment.