From d14798c61bc8d8125f246eb4d065f826919038dc Mon Sep 17 00:00:00 2001 From: Vijay Kumar S <94220135+vijay151096@users.noreply.github.com> Date: Sat, 27 Apr 2024 20:39:01 +0530 Subject: [PATCH] [INJIWEB-257]: injiweb theme customization (#44) Signed-off-by: Vijay <94220135+vijay151096@users.noreply.github.com> --- inji-web/src/Router.tsx | 28 ++-- .../components/Common/EmptyListContainer.tsx | 3 +- inji-web/src/components/Common/HeaderTile.tsx | 3 +- inji-web/src/components/Common/IntroBox.tsx | 6 +- inji-web/src/components/Common/ItemBox.tsx | 7 +- .../components/Common/LanguageSelector.tsx | 16 +- inji-web/src/components/Common/NavBar.tsx | 20 ++- inji-web/src/components/Common/ThemeMode.tsx | 31 ++++ .../src/components/Help/HelpAccordionItem.tsx | 13 +- .../src/components/Issuers/IssuersList.tsx | 2 +- .../src/components/Issuers/SearchIssuer.tsx | 18 ++- .../src/components/PageTemplate/Footer.tsx | 2 +- .../src/components/PageTemplate/Header.tsx | 10 +- .../components/Redirection/DownloadResult.tsx | 12 +- inji-web/src/hooks/useFetch.tsx | 12 +- inji-web/src/index.css | 2 +- inji-web/src/pages/CredentialsPage.tsx | 22 ++- inji-web/src/pages/HelpPage.tsx | 2 +- inji-web/src/pages/HomePage.tsx | 11 +- inji-web/src/pages/RedirectionPage.tsx | 69 ++++++--- inji-web/src/redux/reducers/commonReducer.ts | 20 ++- inji-web/src/types/components.d.ts | 4 +- inji-web/src/types/data.d.ts | 11 ++ inji-web/src/types/redux.d.ts | 3 +- inji-web/src/utils/api.ts | 137 +++++++++--------- inji-web/src/utils/i18n.ts | 10 +- inji-web/src/utils/misc.ts | 26 +++- inji-web/src/utils/storage.ts | 1 + inji-web/tailwind.config.js | 40 ++++- 29 files changed, 376 insertions(+), 165 deletions(-) create mode 100644 inji-web/src/components/Common/ThemeMode.tsx diff --git a/inji-web/src/Router.tsx b/inji-web/src/Router.tsx index 4ffcbbd..719fbd8 100644 --- a/inji-web/src/Router.tsx +++ b/inji-web/src/Router.tsx @@ -6,8 +6,24 @@ import {Footer} from "./components/PageTemplate/Footer"; import {HelpPage} from "./pages/HelpPage"; import {CredentialsPage} from "./pages/CredentialsPage"; import {RedirectionPage} from "./pages/RedirectionPage"; +import {useSelector} from "react-redux"; +import {RootState} from "./types/redux"; export const AppRouter = () => { + + const theme = useSelector((state: RootState) => state.common.theme); + const wrapElement = (element: JSX.Element) => { + return +
+
+
+ {element} +
+
+
+ } + return ( )}/> @@ -18,14 +34,4 @@ export const AppRouter = () => { ) } -const wrapElement = (element: JSX.Element) => { - return -
-
-
- {element} -
-
-
-} + diff --git a/inji-web/src/components/Common/EmptyListContainer.tsx b/inji-web/src/components/Common/EmptyListContainer.tsx index ebe20a7..fea1128 100644 --- a/inji-web/src/components/Common/EmptyListContainer.tsx +++ b/inji-web/src/components/Common/EmptyListContainer.tsx @@ -6,7 +6,8 @@ export const EmptyListContainer: React.FC = ({content})
-

{content}

+

{content}

diff --git a/inji-web/src/components/Common/HeaderTile.tsx b/inji-web/src/components/Common/HeaderTile.tsx index 6a3af35..a89b9c6 100644 --- a/inji-web/src/components/Common/HeaderTile.tsx +++ b/inji-web/src/components/Common/HeaderTile.tsx @@ -3,6 +3,7 @@ import {HeaderTileProps} from "../../types/components"; export const HeaderTile: React.FC = ({content}) => { return -
{content}
+
{content}
} diff --git a/inji-web/src/components/Common/IntroBox.tsx b/inji-web/src/components/Common/IntroBox.tsx index 741417b..ff07bd2 100644 --- a/inji-web/src/components/Common/IntroBox.tsx +++ b/inji-web/src/components/Common/IntroBox.tsx @@ -5,8 +5,10 @@ export const IntroBox: React.FC = () => { const {t} = useTranslation("HomePage"); return
-

{t("Intro.title")}

-

{t("Intro.subTitle")}

+

{t("Intro.title")}

+

{t("Intro.subTitle")}

} diff --git a/inji-web/src/components/Common/ItemBox.tsx b/inji-web/src/components/Common/ItemBox.tsx index 641c879..27d80b4 100644 --- a/inji-web/src/components/Common/ItemBox.tsx +++ b/inji-web/src/components/Common/ItemBox.tsx @@ -5,13 +5,14 @@ export const ItemBox: React.FC = (props) => { return
Issuer Logo
-

{props.title}

- {props.description &&

{props.description}

} +

{props.title}

+ {props.description &&

{props.description}

}
diff --git a/inji-web/src/components/Common/LanguageSelector.tsx b/inji-web/src/components/Common/LanguageSelector.tsx index e2b22c6..9ffc01b 100644 --- a/inji-web/src/components/Common/LanguageSelector.tsx +++ b/inji-web/src/components/Common/LanguageSelector.tsx @@ -1,6 +1,6 @@ import React, {useState} from "react"; import {VscGlobe} from "react-icons/vsc"; -import {switchLanguage} from "../../utils/i18n"; +import {LanguagesSupported, switchLanguage} from "../../utils/i18n"; import {useDispatch, useSelector} from "react-redux"; import {storeLanguage} from "../../redux/reducers/commonReducer"; import {RootState} from "../../types/redux"; @@ -22,18 +22,18 @@ export const LanguageSelector: React.FC = () => { className="relative flex flex-row items-center font-bold bg-white rounded leading-tight"> + size={30} color={'orange'}/> diff --git a/inji-web/src/components/Common/NavBar.tsx b/inji-web/src/components/Common/NavBar.tsx index 206d45a..f66de26 100644 --- a/inji-web/src/components/Common/NavBar.tsx +++ b/inji-web/src/components/Common/NavBar.tsx @@ -5,9 +5,11 @@ import {IoArrowBack, IoCloseCircleSharp} from "react-icons/io5"; import {FaSearch} from "react-icons/fa"; import {useDispatch} from "react-redux"; import {storeCredentials} from "../../redux/reducers/credentialsReducer"; -import {api, MethodType} from "../../utils/api"; +import {api} from "../../utils/api"; import {NavBarProps} from "../../types/components"; +import {ApiRequest} from "../../types/data"; + export const NavBar: React.FC = (props) => { const params = useParams(); @@ -18,12 +20,18 @@ export const NavBar: React.FC = (props) => { const filterCredential = async (searchText: string) => { setSearchText(searchText) - const response = await props.fetchRequest(api.searchCredentialType(params.issuerId, searchText), MethodType.GET, null); + const apiRequest: ApiRequest = api.searchCredentialType; + const response = await props.fetchRequest( + apiRequest.url(params.issuerId, searchText), + apiRequest.methodType, + apiRequest.headers() + ); dispatch(storeCredentials(response?.response?.supportedCredentials)) } return -
+
diff --git a/inji-web/src/components/Redirection/DownloadResult.tsx b/inji-web/src/components/Redirection/DownloadResult.tsx index ddd66e7..2b0af00 100644 --- a/inji-web/src/components/Redirection/DownloadResult.tsx +++ b/inji-web/src/components/Redirection/DownloadResult.tsx @@ -10,11 +10,13 @@ export const DownloadResult: React.FC = (props) => {
{props.success ? -
+
+
: -
} +
+
}

{props.title}

@@ -26,7 +28,7 @@ export const DownloadResult: React.FC = (props) => {
diff --git a/inji-web/src/hooks/useFetch.tsx b/inji-web/src/hooks/useFetch.tsx index 2ddc99f..db6dd2e 100644 --- a/inji-web/src/hooks/useFetch.tsx +++ b/inji-web/src/hooks/useFetch.tsx @@ -12,19 +12,17 @@ export const useFetch = () => { const [state, setState] = useState(RequestStatus.LOADING); const [error, setError] = useState(""); - const fetchRequest = async (uri: string, method: MethodType, body?: any) => { + const fetchRequest = async (uri: string, method: MethodType, header: any, body?: any) => { try { setState(RequestStatus.LOADING); let responseJson: (ResponseTypeObject) = {}; const response = await fetch(`${api.mimotoHost}${uri}`, { method: MethodType[method], - headers: { - "Content-Type": "application/json", - }, - body: body && JSON.stringify(body), + headers: header, + body: body, }); - - if (response.ok) { + responseJson = response; + if (response.ok && uri.indexOf("download") === -1) { responseJson = await response.json(); setState(RequestStatus.DONE); } diff --git a/inji-web/src/index.css b/inji-web/src/index.css index 590652e..9436b06 100644 --- a/inji-web/src/index.css +++ b/inji-web/src/index.css @@ -14,7 +14,7 @@ } .root { - @apply flex flex-col min-h-screen bg + @apply flex flex-col min-h-screen bg bg-light-background dark:bg-dark-background } .root-body { diff --git a/inji-web/src/pages/CredentialsPage.tsx b/inji-web/src/pages/CredentialsPage.tsx index c032c93..203fb82 100644 --- a/inji-web/src/pages/CredentialsPage.tsx +++ b/inji-web/src/pages/CredentialsPage.tsx @@ -6,11 +6,13 @@ import {CredentialList} from "../components/Credentials/CredentialList"; import {useDispatch} from "react-redux"; import {storeSelectedIssuer} from "../redux/reducers/issuersReducer"; import {storeCredentials} from "../redux/reducers/credentialsReducer"; -import {api, MethodType} from "../utils/api"; +import {api} from "../utils/api"; import {HeaderTile} from "../components/Common/HeaderTile"; import {useTranslation} from "react-i18next"; import {toast} from "react-toastify"; +import {ApiRequest} from "../types/data"; + export const CredentialsPage: React.FC = () => { const {state, fetchRequest} = useFetch(); @@ -20,10 +22,20 @@ export const CredentialsPage: React.FC = () => { useEffect(() => { const fetchCall = async () => { - let response = await fetchRequest(api.fetchSpecificIssuer(params.issuerId), MethodType.GET, null); + let apiRequest: ApiRequest = api.fetchSpecificIssuer; + let response = await fetchRequest( + apiRequest.url(params.issuerId), + apiRequest.methodType, + apiRequest.headers() + ); dispatch(storeSelectedIssuer(response?.response)); - response = await fetchRequest(api.fetchCredentialTypes(params.issuerId), MethodType.GET, null); + apiRequest = api.fetchCredentialTypes; + response = await fetchRequest( + apiRequest.url(params.issuerId), + apiRequest.methodType, + apiRequest.headers() + ); dispatch(storeCredentials(response?.response?.supportedCredentials)); } fetchCall(); @@ -34,10 +46,12 @@ export const CredentialsPage: React.FC = () => { } return -
+
} + diff --git a/inji-web/src/pages/HelpPage.tsx b/inji-web/src/pages/HelpPage.tsx index f4667ca..af80369 100644 --- a/inji-web/src/pages/HelpPage.tsx +++ b/inji-web/src/pages/HelpPage.tsx @@ -6,7 +6,7 @@ import {useTranslation} from "react-i18next"; export const HelpPage: React.FC = () => { const {t} = useTranslation("HelpPage") return -
+
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: [], }