diff --git a/inji-web/src/pages/HomePage.tsx b/inji-web/src/pages/HomePage.tsx
index b220983..9bf3381 100644
--- a/inji-web/src/pages/HomePage.tsx
+++ b/inji-web/src/pages/HomePage.tsx
@@ -5,8 +5,8 @@ import {SearchIssuer} from "../components/Issuers/SearchIssuer";
import {IssuersList} from "../components/Issuers/IssuersList";
import {useDispatch} from "react-redux";
import {storeIssuers} from "../redux/reducers/issuersReducer";
-import {api, MethodType} from "../utils/api";
-import {IssuerObject} from "../types/data";
+import {api} from "../utils/api";
+import {ApiRequest, IssuerObject} from "../types/data";
import {HeaderTile} from "../components/Common/HeaderTile";
import {useTranslation} from "react-i18next";
import {toast} from "react-toastify";
@@ -19,7 +19,12 @@ export const HomePage: React.FC = () => {
useEffect(() => {
const fetchCall = async () => {
- const response = await fetchRequest(api.fetchIssuers(), MethodType.GET, null);
+ const apiRequest: ApiRequest = api.fetchIssuers;
+ const response = await fetchRequest(
+ apiRequest.url(),
+ apiRequest.methodType,
+ apiRequest.headers()
+ );
const issuers = response?.response?.issuers.filter((issuer: IssuerObject) => issuer.credential_issuer === "Sunbird")
dispatch(storeIssuers(issuers));
}
diff --git a/inji-web/src/pages/RedirectionPage.tsx b/inji-web/src/pages/RedirectionPage.tsx
index b7b7ea5..27aacc4 100644
--- a/inji-web/src/pages/RedirectionPage.tsx
+++ b/inji-web/src/pages/RedirectionPage.tsx
@@ -2,34 +2,33 @@ import React, {useEffect, useState} from 'react';
import {getActiveSession, removeActiveSession} from "../utils/sessions";
import {useLocation} from "react-router-dom";
import {NavBar} from "../components/Common/NavBar";
-import {useFetch} from "../hooks/useFetch";
+import {RequestStatus, useFetch} from "../hooks/useFetch";
import {DownloadResult} from "../components/Redirection/DownloadResult";
-import {api, MethodType} from "../utils/api";
-import {SessionObject} from "../types/data";
+import {api} from "../utils/api";
+import {ApiRequest, SessionObject} from "../types/data";
import {useTranslation} from "react-i18next";
+import {downloadCredentialPDF} from "../utils/misc";
export const RedirectionPage: React.FC = () => {
- const {error, fetchRequest} = useFetch();
+ const {error, state, fetchRequest} = useFetch();
const location = useLocation();
const searchParams = new URLSearchParams(location.search);
const redirectedSessionId = searchParams.get("state");
- const activeSessionInfo: SessionObject | {} = getActiveSession(redirectedSessionId);
+ const activeSessionInfo: SessionObject = getActiveSession(redirectedSessionId);
const {t} = useTranslation("RedirectionPage");
- const [session, setSession] = useState
(null);
+ const [session, setSession] = useState(activeSessionInfo);
useEffect(() => {
const fetchToken = async () => {
-
if (Object.keys(activeSessionInfo).length > 0) {
- const code = searchParams.get("code");
- const urlState = searchParams.get("state");
+ const code = searchParams.get("code") ?? "";
+ const urlState = searchParams.get("state") ?? "";
const clientId = activeSessionInfo?.clientId;
const codeVerifier = activeSessionInfo?.codeVerifier;
- const issuerId = activeSessionInfo?.issuerId;
+ const issuerId = activeSessionInfo?.issuerId ?? "";
const certificateId = activeSessionInfo?.certificateId;
-
const bodyJson = {
'grant_type': 'authorization_code',
'code': code,
@@ -41,8 +40,24 @@ export const RedirectionPage: React.FC = () => {
}
const requestBody = new URLSearchParams(bodyJson);
- const response = await fetchRequest(api.fetchToken(issuerId), MethodType.POST, requestBody);
- await api.invokeDownloadCredential(issuerId, certificateId, response?.access_token)
+ let apiRequest: ApiRequest = api.fetchToken;
+ let response = await fetchRequest(
+ apiRequest.url(issuerId) + `?code=${code}&clientId=${clientId}&codeVerifier=${codeVerifier}`,
+ apiRequest.methodType,
+ apiRequest.headers(),
+ requestBody
+ );
+
+ apiRequest = api.downloadVc;
+ response = await fetchRequest(
+ apiRequest.url(issuerId, certificateId) + `?token=${response?.access_token}`,
+ apiRequest.methodType,
+ apiRequest.headers(response?.access_token)
+ );
+ await downloadCredentialPDF(response, certificateId);
+ if (state === RequestStatus.DONE) {
+ await downloadCredentialPDF(response, certificateId);
+ }
if (urlState != null) {
removeActiveSession(urlState);
}
@@ -54,16 +69,28 @@ export const RedirectionPage: React.FC = () => {
}, [])
+ if (!session) {
+ return
+
+
+
+ }
+
+ if (state === RequestStatus.ERROR && error) {
+ return
+
+
+
+ }
return
- {(!error && session) &&
- }
- {!session &&
- }
- {error &&
- }
-
+
}
diff --git a/inji-web/src/redux/reducers/commonReducer.ts b/inji-web/src/redux/reducers/commonReducer.ts
index 1c27587..b8ac9dc 100644
--- a/inji-web/src/redux/reducers/commonReducer.ts
+++ b/inji-web/src/redux/reducers/commonReducer.ts
@@ -1,12 +1,15 @@
import {storage} from "../../utils/storage";
import {CommonReducerActionType} from "../../types/redux";
+import {Theme} from "../../components/Common/ThemeMode";
const initialState = {
- language: storage.getItem(storage.SELECTED_LANGUAGE) ? storage.getItem(storage.SELECTED_LANGUAGE) : 'en'
+ language: storage.getItem(storage.SELECTED_LANGUAGE) ? storage.getItem(storage.SELECTED_LANGUAGE) : 'en',
+ theme: storage.getItem(storage.SELECTED_THEME) ? storage.getItem(storage.SELECTED_THEME) : Theme.LIGHT
}
const CommonReducerAction: CommonReducerActionType = {
- STORE_LANGUAGE: 'STORE_LANGUAGE'
+ STORE_LANGUAGE: 'STORE_LANGUAGE',
+ STORE_THEME: 'STORE_THEME'
}
export const commonReducer = (state = initialState, actions: any) => {
@@ -17,6 +20,12 @@ export const commonReducer = (state = initialState, actions: any) => {
language: actions.language
}
}
+ case CommonReducerAction.STORE_THEME: {
+ return {
+ ...state,
+ theme: actions.theme
+ }
+ }
default :
return state;
}
@@ -29,4 +38,11 @@ export const storeLanguage = (language: string) => {
}
}
+export const storeTheme = (theme: string) => {
+ return {
+ type: CommonReducerAction.STORE_THEME,
+ theme: theme
+ }
+}
+
diff --git a/inji-web/src/types/components.d.ts b/inji-web/src/types/components.d.ts
index 6b090e8..31b2b53 100644
--- a/inji-web/src/types/components.d.ts
+++ b/inji-web/src/types/components.d.ts
@@ -11,7 +11,7 @@ export type ItemBoxProps = {
export type NavBarProps = {
title: string;
search: boolean;
- fetchRequest: () => ResponseTypeObject;
+ fetchRequest?: (...arg: any) => ResponseTypeObject;
}
export type CredentialProps = {
credential: CredentialWellknownObject;
@@ -42,5 +42,5 @@ export type HeaderTileProps = {
content: string;
}
export type SearchIssuerProps = {
- fetchRequest: () => ResponseTypeObject
+ fetchRequest: (...arg: any) => ResponseTypeObject
}
diff --git a/inji-web/src/types/data.d.ts b/inji-web/src/types/data.d.ts
index 5793d8d..f36751e 100644
--- a/inji-web/src/types/data.d.ts
+++ b/inji-web/src/types/data.d.ts
@@ -1,3 +1,5 @@
+import {MethodType} from "../utils/api";
+
export type DisplayArrayObject = {
name: string;
language: string;
@@ -61,3 +63,12 @@ export type SessionObject = {
state: string;
clientId: string;
}
+export type ApiRequest = {
+ url: (...args: string[]) => string;
+ methodType: MethodType;
+ headers: (...args: string[]) => any;
+}
+export type LanguageObject = {
+ label: string;
+ value: string;
+}
diff --git a/inji-web/src/types/redux.d.ts b/inji-web/src/types/redux.d.ts
index 0c099ad..03df080 100644
--- a/inji-web/src/types/redux.d.ts
+++ b/inji-web/src/types/redux.d.ts
@@ -6,7 +6,8 @@ export type CredentialsReducerActionType = {
STORE_CREDENTIAL: string
}
export type CommonReducerActionType = {
- STORE_LANGUAGE: string
+ STORE_LANGUAGE: string;
+ STORE_THEME: string;
}
export type IssuersReducerActionType = {
STORE_SELECTED_ISSUER: string;
diff --git a/inji-web/src/utils/api.ts b/inji-web/src/utils/api.ts
index 4fbfb1d..24dca52 100644
--- a/inji-web/src/utils/api.ts
+++ b/inji-web/src/utils/api.ts
@@ -1,5 +1,4 @@
-import {getFileName} from "./misc";
-import {CodeChallengeObject, IssuerObject, ResponseTypeObject} from "../types/data";
+import {ApiRequest, CodeChallengeObject, IssuerObject} from "../types/data";
export enum MethodType {
GET,
@@ -12,11 +11,75 @@ export class api {
static mimotoHost = window.location.origin + "/v1/mimoto";
static authorizationRedirectionUrl = window.location.origin + "/redirect";
- static fetchIssuers = () => "/issuers"
- static searchIssuers = (searchText: string) => `/issuers?search=${searchText}`
- static fetchSpecificIssuer = (issuerId: string) => `/issuers/${issuerId}`
- static fetchCredentialTypes = (issuerId: string) => `/issuers/${issuerId}/credentialTypes`
- static searchCredentialType = (issuerId: string, searchText: string) => `/issuers/${issuerId}/credentialTypes?search=${searchText}`
+
+
+ static fetchIssuers: ApiRequest = {
+ url: () => "/issuers",
+ methodType: MethodType.GET,
+ headers: () => {
+ return {
+ "Content-Type": "application/json"
+ }
+ }
+ }
+ static searchIssuers: ApiRequest = {
+ url: (searchText: string) => `/issuers?search=${searchText}`,
+ methodType: MethodType.GET,
+ headers: () => {
+ return {
+ "Content-Type": "application/json"
+ }
+ }
+ }
+ static fetchSpecificIssuer: ApiRequest = {
+ url: (issuerId: string) => `/issuers/${issuerId}`,
+ methodType: MethodType.GET,
+ headers: () => {
+ return {
+ "Content-Type": "application/json"
+ }
+ }
+ }
+ static fetchCredentialTypes = {
+ url: (issuerId: string) => `/issuers/${issuerId}/credentialTypes`,
+ methodType: MethodType.GET,
+ headers: () => {
+ return {
+ "Content-Type": "application/json"
+ }
+ }
+ }
+
+ static searchCredentialType: ApiRequest = {
+ url: (issuerId: string, searchText: string) => `/issuers/${issuerId}/credentialTypes?search=${searchText}`,
+ methodType: MethodType.GET,
+ headers: () => {
+ return {
+ "Content-Type": "application/json"
+ }
+ }
+ }
+ static fetchToken: ApiRequest = {
+ url: (issuer: string): string => `/get-token/${issuer}`,
+ methodType: MethodType.POST,
+ headers: () => {
+ return {
+ 'accept': 'application/json',
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ }
+ }
+ };
+ static downloadVc = {
+ url: (issuerId: string, credentialId: string) => `/issuers/${issuerId}/credentials/${credentialId}/download`,
+ methodType: MethodType.GET,
+ headers: (token: string) => {
+ return {
+ 'Bearer': token,
+ 'Cache-Control': 'no-cache, no-store, must-revalidate'
+ }
+ }
+ }
+
static authorization = (currentIssuer: IssuerObject, state: string, code_challenge: CodeChallengeObject) => {
return `${currentIssuer.authorization_endpoint}` +
`?response_type=code&` +
@@ -27,66 +90,6 @@ export class api {
`code_challenge=${code_challenge.codeChallenge}&` +
`code_challenge_method=S256`;
}
- static fetchToken = (issuer: string): string => `/get-token/${issuer}`;
- static vcDownload = (issuerId: string, credentialId: string) => `${api.mimotoHost}/${issuerId}/credentials/${credentialId}/download`;
- static fetchRequest = async (uri: string, method: MethodType, body?: any) => {
- let responseJson: (ResponseTypeObject) = {};
- const response = await fetch(`${api.mimotoHost}${uri}`, {
- method: MethodType[method],
- headers: {
- "Content-Type": "application/json",
- },
- body: body && JSON.stringify(body),
- });
- if (!response.ok) {
- throw new Error(`Exception Occurred while invoking ${uri}`);
- }
- responseJson = await response.json();
- return responseJson;
- };
-
- static invokeDownloadCredential = async (issuerId: string, certificateId: string, token: string) => {
- let response;
- try {
- response = await fetch(api.vcDownload(issuerId, certificateId) + `?token=${token}`, {
- method: "GET",
- headers: {
- 'Bearer': token,
- 'Cache-Control': 'no-cache, no-store, must-revalidate'
- },
- responseType: 'blob' // Set the response type to 'arraybuffer' to receive binary data
- });
- } catch (exception: any) {
- response = exception.response;
- }
-
- const blob = new Blob([response.data], {type: response.headers['content-type']});
-
- if (response.status === 500) {
- let responseObject = await blob.text();
- throw new Error(responseObject, {error: responseObject});
- }
- let fileName = getFileName(response.headers['content-disposition']) ?? `${certificateId}.pdf`;
-
- // Create a temporary URL for the Blob
- const url = window.URL.createObjectURL(blob);
-
- // Create a temporary link element
- const link = document.createElement('a');
- link.href = url;
-
- // Set the filename for download
- link.setAttribute('download', fileName);
- link.setAttribute('target', '_blank'); // Open the link in a new tab or window
-
- // Trigger a click event to download the file
- document.body.appendChild(link);
- link.click();
-
- // Clean up
- document.body.removeChild(link);
- window.URL.revokeObjectURL(url);
- }
}
diff --git a/inji-web/src/utils/i18n.ts b/inji-web/src/utils/i18n.ts
index 2a8fe30..37a749e 100644
--- a/inji-web/src/utils/i18n.ts
+++ b/inji-web/src/utils/i18n.ts
@@ -6,10 +6,18 @@ import ta from '../locales/ta.json';
import hi from '../locales/hi.json';
import kn from '../locales/kn.json';
import {storage} from "./storage";
-import {DisplayArrayObject} from "../types/data";
+import {DisplayArrayObject, LanguageObject} from "../types/data";
const resources = {en, ta, kn, hi, fr};
+export const LanguagesSupported: LanguageObject[] = [
+ {label: "English", value: 'en'},
+ {label: "தமிழ்", value: 'ta'},
+ {label: "ಕನ್ನಡ", value: 'kn'},
+ {label: "हिंदी", value: 'hi'},
+ {label: "Français", value: 'fr'}
+]
+
const selected_language = storage.getItem(storage.SELECTED_LANGUAGE);
i18n
.use(initReactI18next) // passes i18n down to react-i18next
diff --git a/inji-web/src/utils/misc.ts b/inji-web/src/utils/misc.ts
index 97c6d1d..3ece559 100644
--- a/inji-web/src/utils/misc.ts
+++ b/inji-web/src/utils/misc.ts
@@ -25,10 +25,34 @@ export const generateRandomString = (length = 43) => {
export const getFileName = (contentDispositionHeader: any) => {
if (!contentDispositionHeader) return null;
- // sample header value => Content-Disposition: 'attachment; filename="x"' and we need "x"
const filenameMatch = contentDispositionHeader.match(/filename=(.*?)(;|$)/);
if (filenameMatch && filenameMatch.length > 1) {
return filenameMatch[1];
}
return null;
};
+
+export const downloadCredentialPDF = async (response: any, certificateId: string) => {
+
+ const blob: Blob = new Blob([response.data], {type: response.headers['content-type']});
+
+ let fileName = getFileName(response.headers['content-disposition']) ?? `${certificateId}.pdf`;
+ // Create a temporary URL for the Blob
+ const url = window.URL.createObjectURL(blob);
+
+ // Create a temporary link element
+ const link = document.createElement('a');
+ link.href = url;
+
+ // Set the filename for download
+ link.setAttribute('download', fileName);
+ link.setAttribute('target', '_blank'); // Open the link in a new tab or window
+
+ // Trigger a click event to download the file
+ document.body.appendChild(link);
+ link.click();
+
+ // Clean up
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(url);
+}
diff --git a/inji-web/src/utils/storage.ts b/inji-web/src/utils/storage.ts
index 1c538e5..10022e3 100644
--- a/inji-web/src/utils/storage.ts
+++ b/inji-web/src/utils/storage.ts
@@ -2,6 +2,7 @@ export class storage {
static SESSION_INFO = "download_session"
static SELECTED_LANGUAGE = "selected_language"
+ static SELECTED_THEME = "selected_theme"
static setItem = (key: string, value: string) => {
if (value) {
localStorage.setItem(key, JSON.stringify(value));
diff --git a/inji-web/tailwind.config.js b/inji-web/tailwind.config.js
index 287f926..74aa9bf 100644
--- a/inji-web/tailwind.config.js
+++ b/inji-web/tailwind.config.js
@@ -1,10 +1,48 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
+ darkMode: 'selector',
content: [
"./src/**/**/*.{js,jsx,ts,tsx}",
],
theme: {
- extend: {},
+ extend: {
+ colors: {
+ light: {
+ background: '#FFFFFF',
+ header: '#000000',
+ title: '#000000',
+ subTitle: '#717171',
+ searchTitle: '#3E3E3E',
+ primary: '#EB6F2D',
+ shadow: '#18479329',
+ navigationBar: '#F2FBFF',
+ languageIcon: '#EB6F2D',
+ closeIcon: '#8E8E8E',
+ searchIcon: '#8E8E8E',
+ shieldSuccessIcon: '#4b9d1f',
+ shieldErrorIcon: '#EF4444',
+ shieldSuccessShadow: '#f1f7ee',
+ shieldErrorShadow: '#FEF2F2',
+ },
+ dark: {
+ background: '#9DB2BF',
+ header: '#000000',
+ title: '#000000',
+ subTitle: '#717171',
+ searchTitle: '#3E3E3E',
+ primary: '#EB6F2D',
+ shadow: '#526D82',
+ navigationBar: '#DDE6ED',
+ languageIcon: '#EB6F2D',
+ closeIcon: '#8E8E8E',
+ searchIcon: '#8E8E8E',
+ shieldSuccessIcon: '#4b9d1f',
+ shieldErrorIcon: '#EF4444',
+ shieldSuccessShadow: '#f1f7ee',
+ shieldErrorShadow: '#FEF2F2',
+ }
+ }
+ },
},
plugins: [],
}