From 1c95956f3d73568afdaac941d9ea3d33db8fca20 Mon Sep 17 00:00:00 2001 From: Vijay Kumar S <94220135+vijay151096@users.noreply.github.com> Date: Tue, 8 Oct 2024 19:57:35 +0530 Subject: [PATCH] [INJIWEB-700]: secure time bound storage (#144) * [INJIWEB-700]: secure time bound storage Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com> * [INJIWEB-700]: secure time bound storage- test cases Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com> * [INJIWEB-700]: secure time bound storage- correct file names Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com> * [INJIWEB-700]: secure time bound storage- correct file names Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com> * [INJIWEB-700]: secure time bound storage- reviewed changes Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com> * [INJIWEB-700]: secure time bound storage- types movement Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com> --------- Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com> Signed-off-by: Vijay Kumar S <94220135+vijay151096@users.noreply.github.com> --- .../CustomExpiryTimesContent.test.tsx | 73 ++++++++++++++ .../CustomExpiryTimesHeader.test.tsx | 22 +++++ .../DataShare/DataShareContent.test.tsx | 55 +++++++++++ .../DataShare/DataShareDisclaimer.test.tsx | 11 +++ .../DataShare/DataShareFooter.test.tsx | 40 ++++++++ .../DataShare/DataShareHeader.test.tsx | 22 +++++ .../__tests__/modals/ModalWrapper.test.tsx | 38 ++++++++ inji-web/src/components/Common/ItemBox.tsx | 2 - .../components/Credentials/Crendential.tsx | 47 +++++---- .../CustomExpiryTimesContent.tsx | 24 +++++ .../CustomExpiryTimesHeader.tsx | 9 ++ .../components/DataShare/DataShareContent.tsx | 95 +++++++++++++++++++ .../DataShare/DataShareDisclaimer.tsx | 9 ++ .../components/DataShare/DataShareFooter.tsx | 19 ++++ .../components/DataShare/DataShareHeader.tsx | 12 +++ inji-web/src/index.css | 18 ++++ inji-web/src/locales/ar.json | 25 +++++ inji-web/src/locales/en.json | 26 ++++- inji-web/src/locales/fr.json | 25 +++++ inji-web/src/locales/hi.json | 25 +++++ inji-web/src/locales/kn.json | 25 +++++ inji-web/src/locales/ta.json | 25 +++++ inji-web/src/modals/CustomExpiryModal.tsx | 25 +++++ inji-web/src/modals/DataShareExpiryModal.tsx | 24 +++++ inji-web/src/modals/ModalWrapper.tsx | 19 ++++ inji-web/src/pages/RedirectionPage.tsx | 3 +- inji-web/src/redux/reducers/commonReducer.ts | 15 +++ inji-web/src/setupTests.ts | 8 ++ inji-web/src/types/components.d.ts | 42 ++++++++ inji-web/src/types/data.d.ts | 1 + inji-web/src/types/redux.d.ts | 1 + inji-web/src/utils/misc.ts | 8 +- inji-web/tailwind.config.js | 14 +++ 33 files changed, 783 insertions(+), 24 deletions(-) create mode 100644 inji-web/src/__tests__/components/DataShare/CustomTimes/CustomExpiryTimesContent.test.tsx create mode 100644 inji-web/src/__tests__/components/DataShare/CustomTimes/CustomExpiryTimesHeader.test.tsx create mode 100644 inji-web/src/__tests__/components/DataShare/DataShareContent.test.tsx create mode 100644 inji-web/src/__tests__/components/DataShare/DataShareDisclaimer.test.tsx create mode 100644 inji-web/src/__tests__/components/DataShare/DataShareFooter.test.tsx create mode 100644 inji-web/src/__tests__/components/DataShare/DataShareHeader.test.tsx create mode 100644 inji-web/src/__tests__/modals/ModalWrapper.test.tsx create mode 100644 inji-web/src/components/DataShare/CustomExpiryTimes/CustomExpiryTimesContent.tsx create mode 100644 inji-web/src/components/DataShare/CustomExpiryTimes/CustomExpiryTimesHeader.tsx create mode 100644 inji-web/src/components/DataShare/DataShareContent.tsx create mode 100644 inji-web/src/components/DataShare/DataShareDisclaimer.tsx create mode 100644 inji-web/src/components/DataShare/DataShareFooter.tsx create mode 100644 inji-web/src/components/DataShare/DataShareHeader.tsx create mode 100644 inji-web/src/modals/CustomExpiryModal.tsx create mode 100644 inji-web/src/modals/DataShareExpiryModal.tsx create mode 100644 inji-web/src/modals/ModalWrapper.tsx diff --git a/inji-web/src/__tests__/components/DataShare/CustomTimes/CustomExpiryTimesContent.test.tsx b/inji-web/src/__tests__/components/DataShare/CustomTimes/CustomExpiryTimesContent.test.tsx new file mode 100644 index 00000000..b150329e --- /dev/null +++ b/inji-web/src/__tests__/components/DataShare/CustomTimes/CustomExpiryTimesContent.test.tsx @@ -0,0 +1,73 @@ +import {fireEvent, render, screen} from "@testing-library/react"; +import {CustomExpiryTimesContent} from "../../../../components/DataShare/CustomExpiryTimes/CustomExpiryTimesContent"; +import {useTranslation} from "react-i18next"; +import {mockUseTranslation} from "../../../../utils/mockUtils"; + +describe("Test the Layout of the Custom Expiry Content", () => { + + beforeEach(() => { + mockUseTranslation(); + } ) + + test("Test the presence of the Outer Container", ()=>{ + const expiryMockFn = jest.fn(); + render(); + const ctDocument = screen.getByTestId("CustomExpiryTimesContent-Outer-Container"); + expect(ctDocument).toBeInTheDocument(); + }) + test("Test the presence of the Time Range Container", ()=>{ + const expiryMockFn = jest.fn(); + render(); + const ctDocument = screen.getByTestId("CustomExpiryTimesContent-Times-Range-Container"); + expect(ctDocument).toBeInTheDocument(); + }) + test("Test the presence of the Time Range Increase Container", ()=>{ + const expiryMockFn = jest.fn(); + render(); + const ctDocument = screen.getByTestId("CustomExpiryTimesContent-Times-Range-Increase"); + expect(ctDocument).toBeInTheDocument(); + }) + test("Test the presence of the Time Range Decrease Container", ()=>{ + const expiryMockFn = jest.fn(); + render(); + const ctDocument = screen.getByTestId("CustomExpiryTimesContent-Times-Range-Decrease"); + expect(ctDocument).toBeInTheDocument(); + }) + test("Test the presence of the Time Range Value", ()=>{ + const expiryMockFn = jest.fn(); + render(); + const ctDocument = screen.getByTestId("CustomExpiryTimesContent-Times-Value"); + expect(ctDocument).toBeInTheDocument(); + }) + test("Test the presence of the Time Range Metrics", ()=>{ + const expiryMockFn = jest.fn(); + render(); + const ctDocument = screen.getByTestId("CustomExpiryTimesContent-Times-Metrics"); + expect(ctDocument).toBeInTheDocument(); + expect(ctDocument).toHaveTextContent("metrics"); + }) + + test("Test the Time Range Increase on Clicking the Increase Button", ()=>{ + const expiryTime=1; + const expiryMockFn = jest.fn(()=> expiryTime+1); + render(); + const increaseValueButton = screen.getByTestId("CustomExpiryTimesContent-Times-Range-Increase"); + const valueDiv = screen.getByTestId("CustomExpiryTimesContent-Times-Value"); + expect(valueDiv).toHaveValue(expiryTime + ""); + fireEvent.click(increaseValueButton); + expect(expiryMockFn).toHaveBeenCalledTimes(1); + expect(expiryMockFn).toHaveBeenCalledWith(expiryTime + 1); + }) + + test("Test the Time Range Decrease on Clicking the Decrease Button", ()=>{ + const expiryTime=3; + const expiryMockFn = jest.fn(()=> expiryTime+1); + render(); + const increaseValueButton = screen.getByTestId("CustomExpiryTimesContent-Times-Range-Decrease"); + const valueDiv = screen.getByTestId("CustomExpiryTimesContent-Times-Value"); + expect(valueDiv).toHaveValue(expiryTime + ""); + fireEvent.click(increaseValueButton); + expect(expiryMockFn).toHaveBeenCalledTimes(1); + expect(expiryMockFn).toHaveBeenCalledWith(expiryTime -1); + }) +}) diff --git a/inji-web/src/__tests__/components/DataShare/CustomTimes/CustomExpiryTimesHeader.test.tsx b/inji-web/src/__tests__/components/DataShare/CustomTimes/CustomExpiryTimesHeader.test.tsx new file mode 100644 index 00000000..4096d8b7 --- /dev/null +++ b/inji-web/src/__tests__/components/DataShare/CustomTimes/CustomExpiryTimesHeader.test.tsx @@ -0,0 +1,22 @@ +import {render, screen} from "@testing-library/react"; +import {CustomExpiryTimesHeader} from "../../../../components/DataShare/CustomExpiryTimes/CustomExpiryTimesHeader"; + +describe("Test the Layout of the Custom Expiry Header", () => { + + beforeEach( ()=> { + render(); + } ) + + test("Test the presence of the Outer Container", ()=>{ + const document = screen.getByTestId("CustomExpiryTimesHeader-Outer-Container"); + expect(document).toBeInTheDocument(); + }) + test("Test the presence of the Title Content", ()=>{ + const document = screen.getByTestId("CustomExpiryTimesHeader-Title-Content"); + expect(document).toBeInTheDocument(); + }) + test("Test to Have the content", ()=>{ + const document = screen.getByTestId("CustomExpiryTimesHeader-Title-Content"); + expect(document).toHaveTextContent("CTHeader"); + }) +}) diff --git a/inji-web/src/__tests__/components/DataShare/DataShareContent.test.tsx b/inji-web/src/__tests__/components/DataShare/DataShareContent.test.tsx new file mode 100644 index 00000000..c22001f7 --- /dev/null +++ b/inji-web/src/__tests__/components/DataShare/DataShareContent.test.tsx @@ -0,0 +1,55 @@ +import {fireEvent, render, screen} from "@testing-library/react"; +import {DataShareContent} from "../../../components/DataShare/DataShareContent"; +import {reduxStore} from "../../../redux/reduxStore"; +import {Provider} from "react-redux"; +import {mockUseTranslation} from "../../../utils/mockUtils"; + +describe("Test the Layout of the Expiry Content", () => { + + const customMockFn = jest.fn(); + beforeEach(() => { + mockUseTranslation(); + render( + + ); + } ) + + test("Test the presence of the Outer Container", ()=>{ + const document = screen.getByTestId("DataShareContent-Outer-Container"); + expect(document).toBeInTheDocument(); + }) + test("Test the presence of the Outer Title", ()=>{ + const document = screen.getByTestId("DataShareContent-Outer-Title"); + expect(document).toBeInTheDocument(); + }) + test("Test the presence of the Issuer Logo", ()=>{ + const document = screen.getByTestId("DataShareContent-Issuer-Logo"); + expect(document).toBeInTheDocument(); + }) + test("Test the presence of the Issuer Name", ()=>{ + const document = screen.getByTestId("DataShareContent-Issuer-Name"); + expect(document).toBeInTheDocument(); + }) + test("Test the presence of the Consent Container", ()=>{ + const document = screen.getByTestId("DataShareContent-Consent-Container"); + expect(document).toBeInTheDocument(); + }) + test("Test the Validity Times Dropdown should not show custom as selected option at first", ()=>{ + const selectedDocument = screen.getByTestId("DataShareContent-Selected-Validity-Times"); + expect(selectedDocument).not.toHaveTextContent("Custom"); + expect(selectedDocument).toHaveTextContent("Once"); + const document = screen.queryByTestId("DataShareContent-Validity-Times-DropDown"); + expect(document).not.toBeInTheDocument(); + }) + test.skip("Test the Validity Times Dropdown should option when custom is selected", ()=>{ + let selectedDocument = screen.getByTestId("DataShareContent-Selected-Validity-Times"); + fireEvent.click(selectedDocument); + const customValidityDocument = screen.getByTestId("DataShareContent-Validity-Times-DropDown-Custom"); + fireEvent.click(customValidityDocument); + selectedDocument = screen.getByTestId("DataShareContent-Selected-Validity-Times"); + expect(selectedDocument).toHaveTextContent("Custom"); + const document = screen.getByTestId("DataShareContent-Validity-Times-DropDown"); + expect(document).toBeInTheDocument(); + expect(document.children.length).toBe(4); + }) +}) diff --git a/inji-web/src/__tests__/components/DataShare/DataShareDisclaimer.test.tsx b/inji-web/src/__tests__/components/DataShare/DataShareDisclaimer.test.tsx new file mode 100644 index 00000000..b0f2533c --- /dev/null +++ b/inji-web/src/__tests__/components/DataShare/DataShareDisclaimer.test.tsx @@ -0,0 +1,11 @@ +import {render, screen} from "@testing-library/react"; +import {DataShareDisclaimer} from "../../../components/DataShare/DataShareDisclaimer"; + +describe("Testing Layout of the Disclaimer", () => { + test("Test the presence of the Outer Container", ()=>{ + render(); + const document = screen.getByTestId("DataShareDisclaimer-Outer-Container"); + expect(document).toBeInTheDocument(); + expect(document).toHaveTextContent("Disclaimer"); + }) +}) diff --git a/inji-web/src/__tests__/components/DataShare/DataShareFooter.test.tsx b/inji-web/src/__tests__/components/DataShare/DataShareFooter.test.tsx new file mode 100644 index 00000000..7d24e585 --- /dev/null +++ b/inji-web/src/__tests__/components/DataShare/DataShareFooter.test.tsx @@ -0,0 +1,40 @@ +import {fireEvent, render, screen} from "@testing-library/react"; +import {DataShareFooter} from "../../../components/DataShare/DataShareFooter"; +import {mockUseTranslation} from "../../../utils/mockUtils"; + +describe("Testing Layout of the Expiry Footer", () => { + + const successMockFn = jest.fn(); + const cancelMockFn = jest.fn(); + beforeEach(() => { + mockUseTranslation() + render(); + } ) + + test("Test the presence of the Outer Container", ()=>{ + const document = screen.getByTestId("DataShareFooter-Outer-Container"); + expect(document).toBeInTheDocument(); + }) + test("Test the presence of the Cancel Button", ()=>{ + const document = screen.getByTestId("DataShareFooter-Cancel-Button"); + expect(document).toBeInTheDocument(); + }) + test("Test the presence of the Success Button", ()=>{ + const document = screen.getByTestId("DataShareFooter-Success-Button"); + expect(document).toBeInTheDocument(); + }) + + test("Test whether success button is invoked on clicking", ()=>{ + const successButton = screen.getByTestId("DataShareFooter-Success-Button"); + fireEvent.click(successButton); + expect(successMockFn).toBeCalled(); + expect(cancelMockFn).not.toBeCalled(); + }) + + test("Test whether cancel button is invoked on clicking", ()=>{ + const cancelButton = screen.getByTestId("DataShareFooter-Cancel-Button"); + fireEvent.click(cancelButton); + expect(successMockFn).not.toBeCalled(); + expect(cancelMockFn).toBeCalled(); + }) +}) diff --git a/inji-web/src/__tests__/components/DataShare/DataShareHeader.test.tsx b/inji-web/src/__tests__/components/DataShare/DataShareHeader.test.tsx new file mode 100644 index 00000000..05d9805a --- /dev/null +++ b/inji-web/src/__tests__/components/DataShare/DataShareHeader.test.tsx @@ -0,0 +1,22 @@ +import {render, screen} from "@testing-library/react"; +import {DataShareHeader} from "../../../components/DataShare/DataShareHeader"; + +describe("Testing Layout of the Expiry Header", () => { + beforeEach(()=>{ + render(); + }) + test("Test Presence of the Outer Container", ()=>{ + const document = screen.getByTestId("DataShareHeader-Outer-Container"); + expect(document).toBeInTheDocument(); + }) + test("Test Presence of the Header Title", ()=>{ + const document = screen.getByTestId("DataShareHeader-Header-Title"); + expect(document).toBeInTheDocument(); + expect(document).toHaveTextContent("title"); + }) + test("Test Presence of the Header SubTitle", ()=>{ + const document = screen.getByTestId("DataShareHeader-Header-SubTitle"); + expect(document).toBeInTheDocument(); + expect(document).toHaveTextContent("subTitle"); + }) +}) diff --git a/inji-web/src/__tests__/modals/ModalWrapper.test.tsx b/inji-web/src/__tests__/modals/ModalWrapper.test.tsx new file mode 100644 index 00000000..77de5c92 --- /dev/null +++ b/inji-web/src/__tests__/modals/ModalWrapper.test.tsx @@ -0,0 +1,38 @@ +import {render, screen} from "@testing-library/react"; +import {ModalWrapper} from "../../modals/ModalWrapper"; +import {DataShareHeader} from "../../components/DataShare/DataShareHeader"; +import {DataShareFooter} from "../../components/DataShare/DataShareFooter"; +import React from "react"; +import {DataShareContent} from "../../components/DataShare/DataShareContent"; +import {reduxStore} from "../../redux/reduxStore"; +import {Provider} from "react-redux"; + +describe("Test the Layout of the Modal Wrapper", () => { + + const customMockFn = jest.fn(); + beforeEach(() => { + render( + + } + content={} + footer={} + size={"3xl"} + zIndex={40} /> + ) + }) + + test("Test the presence of the Outer Container", ()=>{ + const document = screen.getByTestId("ModalWrapper-Outer-Container"); + expect(document).toBeInTheDocument(); + }) + test("Test the presence of the Inner Container", ()=>{ + const document = screen.getByTestId("ModalWrapper-Inner-Container"); + expect(document).toBeInTheDocument(); + expect(document.children.length).toBe(3) + }) + test("Test the presence of the Back Drop", ()=>{ + const document = screen.getByTestId("ModalWrapper-BackDrop"); + expect(document).toBeInTheDocument(); + }) + +}) diff --git a/inji-web/src/components/Common/ItemBox.tsx b/inji-web/src/components/Common/ItemBox.tsx index f1a0a6c2..ba246bb5 100644 --- a/inji-web/src/components/Common/ItemBox.tsx +++ b/inji-web/src/components/Common/ItemBox.tsx @@ -2,8 +2,6 @@ import React from "react"; import {ItemBoxProps} from "../../types/components"; export const ItemBox: React.FC = (props) => { - console.log(`Index -> ${props.index}`) - console.log(`Mod Index -> ${props.index%6}`) return
= (props) => { const selectedIssuer = useSelector((state: RootState) => state.issuers); + const [credentialExpiry, setCredentialExpiry] = useState(false); const language = useSelector((state: RootState) => state.common.language); const filteredCredentialConfig: CredentialConfigurationObject = props.credentialWellknown.credential_configurations_supported[props.credentialId]; const credentialObject = getObjectForCurrentLanguage(filteredCredentialConfig.display, language); - return { - const state = generateRandomString(); - const code_challenge: CodeChallengeObject = generateCodeChallenge(state); - window.open(api.authorization(selectedIssuer.selected_issuer, props.credentialWellknown, filteredCredentialConfig, state, code_challenge), '_self', 'noopener'); - addNewSession({ - selectedIssuer: selectedIssuer.selected_issuer, - certificateId: props.credentialId, - codeVerifier: state, - state: state, - }); - }} - /> + const vcStorageExpiryLimitInTimes = useSelector((state: RootState) => state.common.vcStorageExpiryLimitInTimes); + + const onSuccess = () => { + const state = generateRandomString(); + const code_challenge: CodeChallengeObject = generateCodeChallenge(state); + window.open(api.authorization(selectedIssuer.selected_issuer, props.credentialWellknown, filteredCredentialConfig, state, code_challenge), '_self', 'noopener'); + addNewSession({ + selectedIssuer: selectedIssuer.selected_issuer, + certificateId: props.credentialId, + codeVerifier: state, + vcStorageExpiryLimitInTimes: vcStorageExpiryLimitInTimes ?? 1, + state: state, + }); + } + + return + setCredentialExpiry(true)}/> + { credentialExpiry && + setCredentialExpiry(false)} + onSuccess={onSuccess} + credentialName={credentialObject.name} + credentialLogo={credentialObject.logo.url}/> + } + } diff --git a/inji-web/src/components/DataShare/CustomExpiryTimes/CustomExpiryTimesContent.tsx b/inji-web/src/components/DataShare/CustomExpiryTimes/CustomExpiryTimesContent.tsx new file mode 100644 index 00000000..444268db --- /dev/null +++ b/inji-web/src/components/DataShare/CustomExpiryTimes/CustomExpiryTimesContent.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import {RiArrowDownSLine, RiArrowUpSLine} from "react-icons/ri"; +import {useTranslation} from "react-i18next"; +import {CTContentProps} from "../../../types/components"; + +export const CustomExpiryTimesContent:React.FC= (props) => { + + const {t} = useTranslation("CustomExpiryModal"); + return
+
+
+ + +
+
{props.setExpiryTime(isNaN(parseInt(event.target.value)) ? 0 : parseInt(event.target.value)); }} className={"appearance-none w-full p-2 focus:outline-none no-spinner"} placeholder={"Enter Number here"} data-testid={"CustomExpiryTimesContent-Times-Value"}/>
+
{t("metrics")}
+
+
; +} + diff --git a/inji-web/src/components/DataShare/CustomExpiryTimes/CustomExpiryTimesHeader.tsx b/inji-web/src/components/DataShare/CustomExpiryTimes/CustomExpiryTimesHeader.tsx new file mode 100644 index 00000000..f6dcfaa4 --- /dev/null +++ b/inji-web/src/components/DataShare/CustomExpiryTimes/CustomExpiryTimesHeader.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import {CTHeaderProps} from "../../../types/components"; + +export const CustomExpiryTimesHeader:React.FC = (props) => { + return
+
{props.title}
+
; +} + diff --git a/inji-web/src/components/DataShare/DataShareContent.tsx b/inji-web/src/components/DataShare/DataShareContent.tsx new file mode 100644 index 00000000..736a0b55 --- /dev/null +++ b/inji-web/src/components/DataShare/DataShareContent.tsx @@ -0,0 +1,95 @@ +import React, {useState} from "react"; +import {MdOutlineKeyboardArrowDown} from "react-icons/md"; +import {DataShareDisclaimer} from "./DataShareDisclaimer"; +import {useDispatch, useSelector} from "react-redux"; +import {RootState} from "../../types/redux"; +import {storevcStorageExpiryLimitInTimes} from "../../redux/reducers/commonReducer"; +import {useTranslation} from "react-i18next"; +import {DSContentProps} from "../../types/components"; + +export const DataShareContent:React.FC = (props) => { + + const [timesDropDown, setTimesDropDown] = useState(false); + const vcStorageExpiryLimitInTimes = useSelector((state: RootState) => state.common.vcStorageExpiryLimitInTimes); + const dispatch = useDispatch(); + const {t} = useTranslation("DataShareExpiryModal"); + + + const getExpiryDisplayName = (expiry: number) => { + let expiryDisplayName = expiry.toString(); + switch(expiry){ + case 1: + expiryDisplayName = t("content.validityTimesOptions.once"); + break; + case 3: + expiryDisplayName = t("content.validityTimesOptions.thrice"); + break; + case -1: + expiryDisplayName = t("content.validityTimesOptions.noLimit"); + break; + } + return expiryDisplayName; + } + + + return
+
+
{t("content.selectedDocument")}
+
+
+ Issuer Logo +
+
{props.credentialName}
+
+
+
+
{t("content.consent")}
+
+ + + +
+
+
setTimesDropDown(times => !times)}> +
+
+ + +
+
+ + {timesDropDown &&
+
+
+ + + + +
+
} + +
; +} + diff --git a/inji-web/src/components/DataShare/DataShareDisclaimer.tsx b/inji-web/src/components/DataShare/DataShareDisclaimer.tsx new file mode 100644 index 00000000..da0296b0 --- /dev/null +++ b/inji-web/src/components/DataShare/DataShareDisclaimer.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import {DSDisclaimerProps} from "../../types/components"; + +export const DataShareDisclaimer:React.FC = (props) => { + return
+
{props.content}
+
; +} + diff --git a/inji-web/src/components/DataShare/DataShareFooter.tsx b/inji-web/src/components/DataShare/DataShareFooter.tsx new file mode 100644 index 00000000..7f8bf510 --- /dev/null +++ b/inji-web/src/components/DataShare/DataShareFooter.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import {DSFooterProps} from "../../types/components"; + +export const DataShareFooter:React.FC = (props) => { + return
+ + +
; +} + + diff --git a/inji-web/src/components/DataShare/DataShareHeader.tsx b/inji-web/src/components/DataShare/DataShareHeader.tsx new file mode 100644 index 00000000..21b857c0 --- /dev/null +++ b/inji-web/src/components/DataShare/DataShareHeader.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import {DSHeaderProps} from "../../types/components"; + +export const DataShareHeader:React.FC = (props) => { + return
+
+
{props.title}
+
{props.subTitle}
+
+
; +} + diff --git a/inji-web/src/index.css b/inji-web/src/index.css index c08f8de1..7d8162f5 100644 --- a/inji-web/src/index.css +++ b/inji-web/src/index.css @@ -34,6 +34,15 @@ --iw-color-shieldSuccessShadow: #f1f7ee; --iw-color-shieldErrorShadow: #FEF2F2; --iw-color-shieldLoadingShadow: #f6dfbe; + --iw-color-backDrop: #000000; + --iw-color-borderDark: #717171; + --iw-color-borderLight: #E8EBEC; + --iw-color-arrowDown: #164EA840; + --iw-color-hoverBackGround: #164EA840; + --iw-color-text: #FFFFFF; + --iw-color-subText: #A0A8AC; + --iw-color-disclaimerBackGround: #FFF7E5; + --iw-color-disclaimerText: #8B6105; } [class="purple_theme"] { --iw-font-base: "Gentium Plus", serif; @@ -62,6 +71,15 @@ --iw-color-shieldSuccessShadow: #f1f7ee; --iw-color-shieldErrorShadow: #FEF2F2; --iw-color-shieldLoadingShadow: #f6dfbe; + --iw-color-backDrop: #000000; + --iw-color-borderDark: #717171; + --iw-color-borderLight: #E8EBEC; + --iw-color-arrowDown: #164EA840; + --iw-color-hoverBackGround: #164EA840; + --iw-color-text: #FFFFFF; + --iw-color-subText: #A0A8AC; + --iw-color-disclaimerBackGround: #FFF7E5; + --iw-color-disclaimerText: #8B6105; } } diff --git a/inji-web/src/locales/ar.json b/inji-web/src/locales/ar.json index 7c2c7929..ef1d966b 100644 --- a/inji-web/src/locales/ar.json +++ b/inji-web/src/locales/ar.json @@ -221,5 +221,30 @@ "description3": "2. المشاركة عبر الإنترنت: يقوم هذا الخيار بإنشاء رمز QR مع عنوان URL الذي يشير إلى VC المخزن بشكل آمن عبر الإنترنت. ", "description4": "يمكن العثور على هذه التكوينات في تكوين جهة إصدار Mimoto. " } + }, + "DataShareExpiryModal": { + "title": "صلاحية المشاركة", + "subTitle": "يرجى تقديم موافقتك على تعيين صلاحية مشاركة {{credentialName}}", + "content": { + "selectedDocument": "المستند المحدد", + "consent": "صلاحية الموافقة *", + "validityTimesHeader": "عدد المرات", + "validityTimesOptions": { + "once": "مرة واحدة", + "thrice": "ثلاث مرات", + "noLimit": "لا يوجد حد", + "custom": "مخصص" + }, + "validityDate": "تاريخ" + }, + "success": "يتابع", + "cancel": "خلف", + "disclaimer": "يرجى ملاحظة أنه لا يمكن مشاركة رمز الاستجابة السريعة إلا لعدد المرات التي اخترتها هنا. " + }, + "CustomExpiryModal": { + "title": "الرجاء إدخال عدد المرات التي تريد فيها استخدام بيانات الاعتماد هذه ", + "metrics": "مرات", + "success": "يتابع", + "cancel": "يلغي" } } \ No newline at end of file diff --git a/inji-web/src/locales/en.json b/inji-web/src/locales/en.json index c32a7928..39c6bdfa 100644 --- a/inji-web/src/locales/en.json +++ b/inji-web/src/locales/en.json @@ -221,6 +221,30 @@ "description3":"2. OnlineSharing: This option generates a QR code with a URL that points to the VC stored securely online. This ensures a less dense QR code, making it easier to scan, and supports seamless online sharing.", "description4":"These configurations can be found in the Mimoto issuer's configuration. Depending on your needs, you can switch between these two options to adjust how the VC is downloaded and shared." } - + }, + "DataShareExpiryModal": { + "title": "Share Validity", + "subTitle": "Please provide your consent to set a validity for sharing the {{credentialName}}", + "content": { + "selectedDocument": "Selected Document", + "consent" : "Consent Validity *", + "validityTimesHeader": "Number of times", + "validityTimesOptions": { + "once": "Once", + "thrice": "Thrice", + "noLimit": "No Limit", + "custom": "Custom" + }, + "validityDate": "Date" + }, + "success": "Proceed", + "cancel": "Back", + "disclaimer": "Please note that the QR Code can be shared only for the number of times you have chosen here. To use the QR Code for sharing again, you have to download the PDF again." + }, + "CustomExpiryModal": { + "title": "Please enter the number of times you want to use this credential ", + "metrics": "Times", + "success": "Proceed", + "cancel": "Cancel" } } diff --git a/inji-web/src/locales/fr.json b/inji-web/src/locales/fr.json index 851407dc..963dffe6 100644 --- a/inji-web/src/locales/fr.json +++ b/inji-web/src/locales/fr.json @@ -221,5 +221,30 @@ "description3": "2. Partage en ligne : cette option génère un code QR avec une URL qui pointe vers le VC stocké en ligne de manière sécurisée. ", "description4": "Ces configurations se trouvent dans la configuration de l'émetteur Mimoto. " } + }, + "DataShareExpiryModal": { + "title": "Validité du partage", + "subTitle": "Veuillez donner votre consentement pour définir une validité pour le partage du {{credentialName}}", + "content": { + "selectedDocument": "Document sélectionné", + "consent": "Validité du consentement *", + "validityTimesHeader": "Nombre de fois", + "validityTimesOptions": { + "once": "Une fois", + "thrice": "Trois fois", + "noLimit": "Aucune limite", + "custom": "Coutume" + }, + "validityDate": "Date" + }, + "success": "Procéder", + "cancel": "Dos", + "disclaimer": "Veuillez noter que le QR Code ne peut être partagé que le nombre de fois que vous avez choisi ici. " + }, + "CustomExpiryModal": { + "title": "Veuillez saisir le nombre de fois que vous souhaitez utiliser cet identifiant ", + "metrics": "Fois", + "success": "Procéder", + "cancel": "Annuler" } } \ No newline at end of file diff --git a/inji-web/src/locales/hi.json b/inji-web/src/locales/hi.json index 95611d11..d2bc50fe 100644 --- a/inji-web/src/locales/hi.json +++ b/inji-web/src/locales/hi.json @@ -221,5 +221,30 @@ "description3": "2. ऑनलाइन शेयरिंग: यह विकल्प एक यूआरएल के साथ एक क्यूआर कोड उत्पन्न करता है जो सुरक्षित रूप से ऑनलाइन संग्रहीत वीसी को इंगित करता है। ", "description4": "ये कॉन्फ़िगरेशन मिमोटो जारीकर्ता के कॉन्फ़िगरेशन में पाए जा सकते हैं। " } + }, + "DataShareExpiryModal": { + "title": "वैधता साझा करें", + "subTitle": "कृपया साझा करने की वैधता निर्धारित करने के लिए अपनी सहमति प्रदान करें {{credentialName}}", + "content": { + "selectedDocument": "चयनित दस्तावेज़", + "consent": "सहमति की वैधता*", + "validityTimesHeader": "कितनी बार", + "validityTimesOptions": { + "once": "एक बार", + "thrice": "तीन बार", + "noLimit": "कोई सीमा नहीं", + "custom": "रिवाज़" + }, + "validityDate": "तारीख" + }, + "success": "आगे बढ़ना", + "cancel": "पीछे", + "disclaimer": "कृपया ध्यान दें कि क्यूआर कोड केवल उतनी ही बार साझा किया जा सकता है जितनी बार आपने यहां चुना है। " + }, + "CustomExpiryModal": { + "title": "कृपया वह संख्या दर्ज करें जितनी बार आप इस क्रेडेंशियल का उपयोग करना चाहते हैं ", + "metrics": "टाइम्स", + "success": "आगे बढ़ना", + "cancel": "रद्द करना" } } \ No newline at end of file diff --git a/inji-web/src/locales/kn.json b/inji-web/src/locales/kn.json index 4b108f5e..7517f7ed 100644 --- a/inji-web/src/locales/kn.json +++ b/inji-web/src/locales/kn.json @@ -221,5 +221,30 @@ "description3": "2. ಆನ್‌ಲೈನ್‌ಹಂಚಿಕೆ: ಈ ಆಯ್ಕೆಯು ಆನ್‌ಲೈನ್‌ನಲ್ಲಿ ಸುರಕ್ಷಿತವಾಗಿ ಸಂಗ್ರಹಿಸಲಾದ VC ಯನ್ನು ಸೂಚಿಸುವ URL ನೊಂದಿಗೆ QR ಕೋಡ್ ಅನ್ನು ರಚಿಸುತ್ತದೆ. ", "description4": "ಈ ಸಂರಚನೆಗಳನ್ನು Mimoto ನೀಡುವವರ ಸಂರಚನೆಯಲ್ಲಿ ಕಾಣಬಹುದು. " } + }, + "DataShareExpiryModal": { + "title": "Share Validity", + "subTitle": "გთხოვთ, მიუთითოთ თქვენი თანხმობა გაზიარების მოქმედების დაწესებაზე {{credentialName}}", + "content": { + "selectedDocument": "არჩეული დოკუმენტი", + "consent": "თანხმობის ვადა *", + "validityTimesHeader": "რამდენჯერმე", + "validityTimesOptions": { + "once": "ერთხელ", + "thrice": "სამჯერ", + "noLimit": "არანაირი ლიმიტი", + "custom": "საბაჟო" + }, + "validityDate": "თარიღი" + }, + "success": "გაგრძელება", + "cancel": "უკან", + "disclaimer": "გთხოვთ, გაითვალისწინოთ, რომ QR კოდის გაზიარება შესაძლებელია მხოლოდ იმდენჯერ, რამდენჯერაც აირჩევთ აქ. " + }, + "CustomExpiryModal": { + "title": "გთხოვთ, შეიყვანოთ რამდენჯერ გსურთ ამ რწმუნებათა სიგელის გამოყენება ", + "metrics": "ჯერ", + "success": "გაგრძელება", + "cancel": "გაუქმება" } } \ No newline at end of file diff --git a/inji-web/src/locales/ta.json b/inji-web/src/locales/ta.json index fced838d..4c015088 100644 --- a/inji-web/src/locales/ta.json +++ b/inji-web/src/locales/ta.json @@ -221,5 +221,30 @@ "description3": "2. ஆன்லைன் பகிர்வு: இந்த விருப்பம் URL உடன் QR குறியீட்டை உருவாக்குகிறது, இது ஆன்லைனில் பாதுகாப்பாக சேமிக்கப்பட்ட VC ஐக் குறிக்கிறது. ", "description4": "இந்த உள்ளமைவுகளை Mimoto வழங்குபவரின் உள்ளமைவில் காணலாம். " } + }, + "DataShareExpiryModal": { + "title": "பங்கு செல்லுபடியாகும்", + "subTitle": "பகிர்வதற்கான செல்லுபடியை அமைக்க உங்கள் ஒப்புதலை வழங்கவும் {{credentialName}}", + "content": { + "selectedDocument": "தேர்ந்தெடுக்கப்பட்ட ஆவணம்", + "consent": "ஒப்புதல் செல்லுபடியாகும் *", + "validityTimesHeader": "முறைகளின் எண்ணிக்கை", + "validityTimesOptions": { + "once": "ஒருமுறை", + "thrice": "மூன்று முறை", + "noLimit": "வரம்பு இல்லை", + "custom": "தனிப்பயன்" + }, + "validityDate": "தேதி" + }, + "success": "தொடரவும்", + "cancel": "மீண்டும்", + "disclaimer": "நீங்கள் இங்கு எத்தனை முறை தேர்வு செய்தீர்களோ அந்த அளவுக்கு மட்டுமே QR குறியீட்டைப் பகிர முடியும் என்பதை நினைவில் கொள்ளவும். " + }, + "CustomExpiryModal": { + "title": "இந்த நற்சான்றிதழை நீங்கள் எத்தனை முறை பயன்படுத்த விரும்புகிறீர்கள் என்பதை உள்ளிடவும் ", + "metrics": "நேரங்கள்", + "success": "தொடரவும்", + "cancel": "ரத்து செய்" } } \ No newline at end of file diff --git a/inji-web/src/modals/CustomExpiryModal.tsx b/inji-web/src/modals/CustomExpiryModal.tsx new file mode 100644 index 00000000..b0cd76c4 --- /dev/null +++ b/inji-web/src/modals/CustomExpiryModal.tsx @@ -0,0 +1,25 @@ +import React, {useState} from "react"; +import {ModalWrapper} from "./ModalWrapper"; +import {DataShareFooter} from "../components/DataShare/DataShareFooter"; +import {CustomExpiryTimesContent} from "../components/DataShare/CustomExpiryTimes/CustomExpiryTimesContent"; +import {CustomExpiryTimesHeader} from "../components/DataShare/CustomExpiryTimes/CustomExpiryTimesHeader"; +import {useDispatch, useSelector} from "react-redux"; +import {storevcStorageExpiryLimitInTimes} from "../redux/reducers/commonReducer"; +import {RootState} from "../types/redux"; +import {useTranslation} from "react-i18next"; +import {CustomExpiryModalProps} from "../types/components"; + + +export const CustomExpiryModal: React.FC = (props) => { + + const vcStorageExpiryLimitInTimes = useSelector((state: RootState) => state.common.vcStorageExpiryLimitInTimes); + const [expiryTime, setExpiryTime] = useState(vcStorageExpiryLimitInTimes); + const {t} = useTranslation("CustomExpiryModal"); + const dispatch = useDispatch(); + return } + content={} + footer={ {dispatch(storevcStorageExpiryLimitInTimes(expiryTime));props.onSuccess()} } onCancel={props.onCancel}/>} + size={"sm"} + zIndex={50} /> +} + diff --git a/inji-web/src/modals/DataShareExpiryModal.tsx b/inji-web/src/modals/DataShareExpiryModal.tsx new file mode 100644 index 00000000..92643d67 --- /dev/null +++ b/inji-web/src/modals/DataShareExpiryModal.tsx @@ -0,0 +1,24 @@ +import React, {useState} from "react"; +import {ModalWrapper} from "./ModalWrapper"; +import {DataShareHeader} from "../components/DataShare/DataShareHeader"; +import {DataShareFooter} from "../components/DataShare/DataShareFooter"; +import {DataShareContent} from "../components/DataShare/DataShareContent"; +import {CustomExpiryModal} from "./CustomExpiryModal"; +import {useTranslation} from "react-i18next"; +import {DataShareExpiryModalProps} from "../types/components"; + +export const DataShareExpiryModal: React.FC = (props) => { + + const [isCustomExpiryInTimesModalOpen, setIsCustomExpiryInTimesModalOpen] = useState(false); + const {t} = useTranslation("DataShareExpiryModal") + return + } + content={} + footer={} + size={"3xl"} + zIndex={40}/> + {isCustomExpiryInTimesModalOpen && setIsCustomExpiryInTimesModalOpen(false)} onCancel={() => setIsCustomExpiryInTimesModalOpen(false)}/>} + + +} + diff --git a/inji-web/src/modals/ModalWrapper.tsx b/inji-web/src/modals/ModalWrapper.tsx new file mode 100644 index 00000000..6ed314fb --- /dev/null +++ b/inji-web/src/modals/ModalWrapper.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import {ModalWrapperProps} from "../types/components"; + +export const ModalWrapper:React.FC = (props) => { + + return <> +
+
+
+ {props.header} + {props.content} + {props.footer} +
+
+
+
+ +} + diff --git a/inji-web/src/pages/RedirectionPage.tsx b/inji-web/src/pages/RedirectionPage.tsx index c2eb008d..2ba52f04 100644 --- a/inji-web/src/pages/RedirectionPage.tsx +++ b/inji-web/src/pages/RedirectionPage.tsx @@ -37,8 +37,9 @@ export const RedirectionPage: React.FC = () => { const codeVerifier = activeSessionInfo?.codeVerifier; const issuerId = activeSessionInfo?.selectedIssuer?.credential_issuer ?? ""; const certificateId = activeSessionInfo?.certificateId; + const vcStorageExpiryLimitInTimes = activeSessionInfo?.vcStorageExpiryLimitInTimes; - const requestBody = new URLSearchParams(getTokenRequestBody(code, codeVerifier, issuerId, certificateId)); + const requestBody = new URLSearchParams(getTokenRequestBody(code, codeVerifier, issuerId, certificateId, vcStorageExpiryLimitInTimes)); const apiRequest = api.fetchTokenAnddownloadVc; let credentialDownloadResponse = await fetchRequest( apiRequest.url(), diff --git a/inji-web/src/redux/reducers/commonReducer.ts b/inji-web/src/redux/reducers/commonReducer.ts index 6cd6070a..23a01a18 100644 --- a/inji-web/src/redux/reducers/commonReducer.ts +++ b/inji-web/src/redux/reducers/commonReducer.ts @@ -4,10 +4,12 @@ import {defaultLanguage} from "../../utils/i18n"; const initialState = { language: storage.getItem(storage.SELECTED_LANGUAGE) ? storage.getItem(storage.SELECTED_LANGUAGE) : defaultLanguage, + vcStorageExpiryLimitInTimes: 1 } const CommonReducerAction: CommonReducerActionType = { STORE_LANGUAGE: 'STORE_LANGUAGE', + STORE_VC_STORAGE_EXPIRY_LIMIT_IN_TIMES: 'STORE_VC_STORAGE_EXPIRY_LIMIT_IN_TIMES' } export const commonReducer = (state = initialState, actions: any) => { @@ -18,6 +20,12 @@ export const commonReducer = (state = initialState, actions: any) => { language: actions.language } } + case CommonReducerAction.STORE_VC_STORAGE_EXPIRY_LIMIT_IN_TIMES: { + return { + ...state, + vcStorageExpiryLimitInTimes: actions.vcStorageExpiryLimitInTimes + } + } default : return state; } @@ -30,4 +38,11 @@ export const storeLanguage = (language: string) => { } } +export const storevcStorageExpiryLimitInTimes = (vcStorageExpiryLimitInTimes: number) => { + return { + type: CommonReducerAction.STORE_VC_STORAGE_EXPIRY_LIMIT_IN_TIMES, + vcStorageExpiryLimitInTimes: vcStorageExpiryLimitInTimes + } +} + diff --git a/inji-web/src/setupTests.ts b/inji-web/src/setupTests.ts index 8f2609b7..3d1ffda7 100644 --- a/inji-web/src/setupTests.ts +++ b/inji-web/src/setupTests.ts @@ -3,3 +3,11 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; + +global.window._env_ = { + DEFAULT_FAVICON: "favicon.ico", + DEFAULT_FONT_URL: "", + DEFAULT_THEME: "purple_theme", + DEFAULT_TITLE: "Inji Web Test", + DEFAULT_LANG: 'en' +}; diff --git a/inji-web/src/types/components.d.ts b/inji-web/src/types/components.d.ts index f0f87016..383bbbcb 100644 --- a/inji-web/src/types/components.d.ts +++ b/inji-web/src/types/components.d.ts @@ -55,3 +55,45 @@ export type IssuersListProps = { export type CredentialListProps = { state: RequestStatus; } +export type CTContentProps = { + expiryTime: number; + setExpiryTime: (expiry: number) => void; +} +export type CTHeaderProps = { + title: string; +} +export type DSContentProps = { + credentialName: string; + credentialLogo: string; + setIsCustomExpiryInTimesModalOpen: (isCustomExpiryInTimesModalOpen: boolean) => void; +} +export type DSDisclaimerProps = { + content: string; +} +export type DSFooterProps = { + successText: string; + onSuccess: () => void; + cancelText: string; + onCancel: () => void; +} +export type DSHeaderProps = { + title: string; + subTitle: string; +} +export type CustomExpiryModalProps = { + onCancel: () => void; + onSuccess: () => void; +} +export type DataShareExpiryModalProps = { + credentialName: string; + credentialLogo: string; + onSuccess: () => void; + onCancel: () => void; +} +export type ModalWrapperProps = { + header: React.ReactNode; + content: React.ReactNode; + footer: React.ReactNode; + zIndex: number; + size: string; +} diff --git a/inji-web/src/types/data.d.ts b/inji-web/src/types/data.d.ts index 6b177776..3df9abf1 100644 --- a/inji-web/src/types/data.d.ts +++ b/inji-web/src/types/data.d.ts @@ -77,6 +77,7 @@ export type SessionObject = { selectedIssuer?: IssuerObject; certificateId: string; codeVerifier: string; + vcStorageExpiryLimitInTimes: number; state: string; } export type ApiRequest = { diff --git a/inji-web/src/types/redux.d.ts b/inji-web/src/types/redux.d.ts index cbfeb528..996e60f9 100644 --- a/inji-web/src/types/redux.d.ts +++ b/inji-web/src/types/redux.d.ts @@ -8,6 +8,7 @@ export type CredentialsReducerActionType = { } export type CommonReducerActionType = { STORE_LANGUAGE: string; + STORE_VC_STORAGE_EXPIRY_LIMIT_IN_TIMES: string; } export type IssuersReducerActionType = { STORE_SELECTED_ISSUER: string; diff --git a/inji-web/src/utils/misc.ts b/inji-web/src/utils/misc.ts index e8a67fef..053b6d9f 100644 --- a/inji-web/src/utils/misc.ts +++ b/inji-web/src/utils/misc.ts @@ -30,14 +30,15 @@ export const isObjectEmpty = (object: any) => { return object === null || object === undefined || Object.keys(object).length === 0; } -export const getTokenRequestBody = (code: string, codeVerifier: string, issuerId: string, credentialType: string) => { +export const getTokenRequestBody = (code: string, codeVerifier: string, issuerId: string, credentialType: string, vcStorageExpiryLimitInTimes: string) => { return { 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': api.authorizationRedirectionUrl, 'code_verifier': codeVerifier, 'issuer': issuerId, - 'credential': credentialType + 'credential': credentialType, + 'vcStorageExpiryLimitInTimes': vcStorageExpiryLimitInTimes } } @@ -74,7 +75,6 @@ export const getErrorObject = (downloadResponse: any) => { message: "error.generic.subTitle" } } - export const constructContent = (descriptions: string[],applyHTML:boolean) => { return descriptions.map((desc, index) => { if (applyHTML) { @@ -82,4 +82,4 @@ export const constructContent = (descriptions: string[],applyHTML:boolean) => { } return desc; }); -}; \ No newline at end of file +}; diff --git a/inji-web/tailwind.config.js b/inji-web/tailwind.config.js index efc0ed6a..8fa071ac 100644 --- a/inji-web/tailwind.config.js +++ b/inji-web/tailwind.config.js @@ -9,6 +9,11 @@ module.exports = { fontFamily:{ base: 'var(--iw-font-base)', }, + zIndex: { + '50': '50', + '40': '40', + '30': '30' + }, colors: { iw: { background: 'var(--iw-color-background)', @@ -36,6 +41,15 @@ module.exports = { shieldSuccessShadow: 'var(--iw-color-shieldSuccessShadow)', shieldErrorShadow: 'var(--iw-color-shieldErrorShadow)', shieldLoadingShadow: 'var(--iw-color-shieldLoadingShadow)', + backDrop: 'var(--iw-color-backDrop)', + borderLight: 'var(--iw-color-borderLight)', + borderDark: 'var(--iw-color-borderDark)', + arrowDown: 'var(--iw-color-arrowDown)', + hoverBackGround: 'var(--iw-color-hoverBackGround)', + text: 'var(--iw-color-text)', + subText: 'var(--iw-color-subText)', + disclaimerBackGround: 'var(--iw-color-disclaimerBackGround)', + disclaimerText: 'var(--iw-color-disclaimerText)' } } },