Added multi language support and started refactoring components to be functions instead of classes.
This commit is contained in:
parent
6e3ec1c243
commit
b559d9260c
8
i18n-unused.config.js
Normal file
8
i18n-unused.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
srcPaths: ["src/**/*.{ts,tsx}", "!src/components/common/ckeditor/**"],
|
||||||
|
localesPath: "public/locales",
|
||||||
|
defaultNamespace: "common",
|
||||||
|
|
||||||
|
// Match ANY t("...") call, anywhere in TS/TSX/JSX
|
||||||
|
translationKeyMatcher: "t\\([\"']([^\"']+)[\"']\\)",
|
||||||
|
};
|
||||||
15
i18next-parser.config.js
Normal file
15
i18next-parser.config.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module.exports = {
|
||||||
|
locales: ["en"],
|
||||||
|
namespaceSeparator: false,
|
||||||
|
keySeparator: false,
|
||||||
|
defaultNamespace: "common",
|
||||||
|
output: "public/locales/$LOCALE/$NAMESPACE.json",
|
||||||
|
createOldCatalogs: false,
|
||||||
|
keepRemoved: false,
|
||||||
|
lexers: {
|
||||||
|
ts: ["JsxLexer"],
|
||||||
|
tsx: ["JsxLexer"],
|
||||||
|
js: ["JsxLexer"],
|
||||||
|
jsx: ["JsxLexer"],
|
||||||
|
},
|
||||||
|
};
|
||||||
15
package.json
15
package.json
@ -31,6 +31,8 @@
|
|||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"html-react-parser": "^3.0.16",
|
"html-react-parser": "^3.0.16",
|
||||||
|
"i18next": "^22.5.1",
|
||||||
|
"i18next-http-backend": "^3.0.2",
|
||||||
"joi": "^17.9.1",
|
"joi": "^17.9.1",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
@ -40,13 +42,13 @@
|
|||||||
"react-bootstrap": "^2.7.4",
|
"react-bootstrap": "^2.7.4",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-helmet-async": "^1.3.0",
|
"react-helmet-async": "^1.3.0",
|
||||||
|
"react-i18next": "^12.3.1",
|
||||||
"react-router-dom": "^6.10.0",
|
"react-router-dom": "^6.10.0",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"react-toastify": "^9.1.2",
|
"react-toastify": "^9.1.2",
|
||||||
"react-toggle": "^4.1.3",
|
"react-toggle": "^4.1.3",
|
||||||
"runtime-env-cra": "^0.2.4",
|
"runtime-env-cra": "^0.2.4",
|
||||||
"sass": "^1.62.0",
|
"sass": "^1.62.0",
|
||||||
"typescript": "^4.7.4",
|
|
||||||
"web-vitals": "^3.3.1"
|
"web-vitals": "^3.3.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -56,7 +58,11 @@
|
|||||||
"start": "concurrently \"npm run start-react\" \"npm run watch-css\" ",
|
"start": "concurrently \"npm run start-react\" \"npm run watch-css\" ",
|
||||||
"build": "react-app-rewired build",
|
"build": "react-app-rewired build",
|
||||||
"test": "react-app-rewired test",
|
"test": "react-app-rewired test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject",
|
||||||
|
"i18n:extract": "i18next \"src/**/*.{ts,tsx,js,jsx}\" \"!src/components/common/ckeditor/**\" --config i18next-parser.config.js",
|
||||||
|
"i18n:unused": "i18n-unused display-unused",
|
||||||
|
"i18n:missed": "i18n-unused display-missed",
|
||||||
|
"i18n:check": "npm run i18n:extract && npm run i18n:unused"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@ -79,6 +85,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.2.3",
|
"@types/node": "^20.2.3",
|
||||||
"@types/react-toggle": "^4.0.3",
|
"@types/react-toggle": "^4.0.3",
|
||||||
"react-app-rewired": "^2.2.1"
|
"i18n-unused": "^0.19.0",
|
||||||
|
"i18next-parser": "^9.3.0",
|
||||||
|
"react-app-rewired": "^2.2.1",
|
||||||
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
public/locales/en/common.json
Normal file
28
public/locales/en/common.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"Admin": "Admin",
|
||||||
|
"AuditLog": "Audit Logs",
|
||||||
|
"AuditLogs": "Audit Logs",
|
||||||
|
"BlockedIPAddresses": "Blocked IP addresses",
|
||||||
|
"BlockedIPs": "Blocked IPs",
|
||||||
|
"ClientDomainManager": "Client Domain Manager",
|
||||||
|
"ClientDomains": "Client Domains",
|
||||||
|
"CustomFieldManager": "Custom Field Manager",
|
||||||
|
"CustomFields": "Custom Fields",
|
||||||
|
"e-print": "e-print",
|
||||||
|
"e-suite": "e-suite",
|
||||||
|
"ErrorLogs": "Error Logs",
|
||||||
|
"ExceptionLogs": "Exception Logs",
|
||||||
|
"Forms": "Forms",
|
||||||
|
"FormTemplateManager": "Form Template Manager",
|
||||||
|
"Glossaries": "Glossaries",
|
||||||
|
"GlossaryManager": "Glossary Manager",
|
||||||
|
"Home": "Home",
|
||||||
|
"Sequence": "Sequence",
|
||||||
|
"SequenceManager": "Sequence Manager",
|
||||||
|
"SiteManager": "Site Manager",
|
||||||
|
"SpecificationManager": "Specification Manager",
|
||||||
|
"SsoManager": "Sso Manager",
|
||||||
|
"Support": "Support",
|
||||||
|
"UserManager": "User Manager",
|
||||||
|
"Users": "Users"
|
||||||
|
}
|
||||||
537
src/App.tsx
537
src/App.tsx
@ -2,6 +2,7 @@ import React, { useEffect } from "react";
|
|||||||
import { Routes, Route, Navigate, useNavigate } from "react-router-dom";
|
import { Routes, Route, Navigate, useNavigate } from "react-router-dom";
|
||||||
import { Helmet, HelmetProvider, HtmlProps } from "react-helmet-async";
|
import { Helmet, HelmetProvider, HtmlProps } from "react-helmet-async";
|
||||||
import { ToastContainer } from "react-toastify";
|
import { ToastContainer } from "react-toastify";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import config from "./config.json";
|
import config from "./config.json";
|
||||||
import authentication from "./modules/frame/services/authenticationService";
|
import authentication from "./modules/frame/services/authenticationService";
|
||||||
import ForgotPassword from "./modules/frame/components/ForgotPassword";
|
import ForgotPassword from "./modules/frame/components/ForgotPassword";
|
||||||
@ -42,129 +43,459 @@ import BlockedIPs from "./modules/blockedIPs/blockedIPs";
|
|||||||
import ErrorLogs from "./modules/errorLogs/errorLogs";
|
import ErrorLogs from "./modules/errorLogs/errorLogs";
|
||||||
import SsoManager from "./modules/manager/ssoManager/ssoManager";
|
import SsoManager from "./modules/manager/ssoManager/ssoManager";
|
||||||
import SsoProviderDetails from "./modules/manager/ssoManager/SsoProviderDetails";
|
import SsoProviderDetails from "./modules/manager/ssoManager/SsoProviderDetails";
|
||||||
|
import { Namespaces } from "./i18n";
|
||||||
|
|
||||||
function GetSecureRoutes() {
|
function GetSecureRoutes() {
|
||||||
const profileRoute = window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ? <Route path="/profile" element={<Redirect to="/account/profile"/>}/>
|
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||||
: <Route path="/profile" element={<Mainframe><Profile /></Mainframe>}/>;
|
const profileRoute = window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ? (
|
||||||
|
<Route path="/profile" element={<Redirect to="/account/profile" />} />
|
||||||
|
) : (
|
||||||
|
<Route
|
||||||
|
path="/profile"
|
||||||
|
element={
|
||||||
|
<Mainframe>
|
||||||
|
<Profile />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Route path="/audit/:auditId" element={<Mainframe title="Audit logs"><HOCAudit /></Mainframe>}/>
|
<Route
|
||||||
<Route path="/audit" element={<Mainframe title="Audit logs"><HOCAudit /></Mainframe>} />
|
path="/audit/:auditId"
|
||||||
<Route path="/blockedIPs" element={<Mainframe title="Blocked IP addresses"><BlockedIPs /></Mainframe>} />
|
element={
|
||||||
<Route path="/exceptionlogs" element={<Mainframe title="Exception Logs"><ErrorLogs /></Mainframe>} />
|
<Mainframe title={t("AuditLogs")}>
|
||||||
|
<HOCAudit />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/audit"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("AuditLogs")}>
|
||||||
|
<HOCAudit />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/blockedIPs"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("BlockedIPAddresses")}>
|
||||||
|
<BlockedIPs />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/exceptionlogs"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("ExceptionLogs")}>
|
||||||
|
<ErrorLogs />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="/specifications/:organisationId/:siteId/add" element={<Mainframe title="Specification Manager"><SpecificationsDetails editMode={false}/></Mainframe>}/>
|
<Route
|
||||||
<Route path="/specifications/:organisationId/:siteId/:specificationId" element={<Mainframe title="Specification Manager"><SpecificationsDetails editMode={true}/></Mainframe>}/>
|
path="/specifications/:organisationId/:siteId/add"
|
||||||
<Route path="/specifications/:organisationId/:siteId" element={<Mainframe title="Specification Manager"><Specifications /></Mainframe>}/>
|
element={
|
||||||
|
<Mainframe title={t("SpecificationManager")}>
|
||||||
|
<SpecificationsDetails editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/specifications/:organisationId/:siteId/:specificationId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("SpecificationManager")}>
|
||||||
|
<SpecificationsDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/specifications/:organisationId/:siteId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("SpecificationManager")}>
|
||||||
|
<Specifications />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="/site/:organisationId/add" element={<Mainframe title="Site Manager"><SiteDetails editMode={false} /></Mainframe>}/>
|
<Route
|
||||||
<Route path="/site/:organisationId/:siteId" element={<Mainframe title="Site Manager"><SiteDetails editMode={true} /></Mainframe>}/>
|
path="/site/:organisationId/add"
|
||||||
<Route path="/site/:organisationId" element={<Mainframe title="Site Manager"><Sites/></Mainframe>}/>
|
element={
|
||||||
<Route path="/site/" element={<Navigate replace to="/404" />} />
|
<Mainframe title={t("SiteManager")}>
|
||||||
|
<SiteDetails editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/site/:organisationId/:siteId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("SiteManager")}>
|
||||||
|
<SiteDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/site/:organisationId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("SiteManager")}>
|
||||||
|
<Sites />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route path="/site/" element={<Navigate replace to="/404" />} />
|
||||||
|
|
||||||
<Route path="/organisations" element={<Mainframe title="e-print"><Organisations /></Mainframe>}/>
|
<Route
|
||||||
<Route path="/organisations/add" element={<Mainframe title="e-print"><HOCOrganisationsDetails editMode={false} /></Mainframe>}/>
|
path="/organisations"
|
||||||
<Route path="/organisations/:organisationId" element={<Mainframe title="e-print"><HOCOrganisationsDetails editMode={true}/></Mainframe>}/>
|
element={
|
||||||
|
<Mainframe title={t("e-print")}>
|
||||||
|
<Organisations />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/organisations/add"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("e-print")}>
|
||||||
|
<HOCOrganisationsDetails editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/organisations/:organisationId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("e-print")}>
|
||||||
|
<HOCOrganisationsDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="/glossaries/add/" element={<Navigate replace to="/404" />} />
|
<Route path="/glossaries/add/" element={<Navigate replace to="/404" />} />
|
||||||
<Route path="/glossaries/add/:glossaryId" element={<Mainframe title="Glossary Manager"><HOCGlossariesDetails editMode={false}/></Mainframe>}/>
|
<Route
|
||||||
<Route path="/glossaries/edit/" element={<Navigate replace to="/404" />}/>
|
path="/glossaries/add/:glossaryId"
|
||||||
<Route path="/glossaries/edit/:glossaryId" element={<Mainframe title="Glossary Manager"><HOCGlossariesDetails editMode={true}/></Mainframe>}/>
|
element={
|
||||||
<Route path="/glossaries" element={<Mainframe title="Glossary Manager"><HOCGlossaries /></Mainframe>}/>
|
<Mainframe title={t("GlossaryManager")}>
|
||||||
<Route path="/glossaries/:glossaryId" element={<Mainframe title="Glossary Manager"><HOCGlossaries /></Mainframe>}/>
|
<HOCGlossariesDetails editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/glossaries/edit/"
|
||||||
|
element={<Navigate replace to="/404" />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/glossaries/edit/:glossaryId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("GlossaryManager")}>
|
||||||
|
<HOCGlossariesDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/glossaries"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("GlossaryManager")}>
|
||||||
|
<HOCGlossaries />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/glossaries/:glossaryId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("GlossaryManager")}>
|
||||||
|
<HOCGlossaries />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="/forms/add" element={<Mainframe title="Form Template Manager"><HOCFormsDetails editMode={false}/></Mainframe>}/>
|
<Route
|
||||||
<Route path="/forms/edit/:formId" element={<Mainframe title="Form Template Manager"><HOCFormsDetails editMode={true} /></Mainframe>}/>
|
path="/forms/add"
|
||||||
<Route path="/forms" element={<Mainframe title="Form Template Manager"><Forms /></Mainframe>}/>
|
element={
|
||||||
|
<Mainframe title={t("FormTemplateManager")}>
|
||||||
|
<HOCFormsDetails editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/forms/edit/:formId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("FormTemplateManager")}>
|
||||||
|
<HOCFormsDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/forms"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("FormTemplateManager")}>
|
||||||
|
<Forms />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="/customfields/add" element={<Mainframe title="Custom Field Manager"><HOCCustomFieldDetails editMode={false}/></Mainframe>}/>
|
<Route
|
||||||
<Route path="/customfields/edit/:customFieldId" element={<Mainframe title="Custom Field Manager"><HOCCustomFieldDetails editMode={true}/></Mainframe>}/>
|
path="/customfields/add"
|
||||||
<Route path="/customfields" element={<Mainframe title="Custom Field Manager"><CustomFields /></Mainframe>}/>
|
element={
|
||||||
|
<Mainframe title={t("CustomFieldManager")}>
|
||||||
|
<HOCCustomFieldDetails editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/customfields/edit/:customFieldId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("CustomFieldManager")}>
|
||||||
|
<HOCCustomFieldDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/customfields"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("CustomFieldManager")}>
|
||||||
|
<CustomFields />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="/sequence/add" element={<Mainframe title="Sequence Manager"><HOCSequenceDetails editMode={false}/></Mainframe>}/>
|
<Route
|
||||||
<Route path="/sequence/edit/:sequenceId" element={<Mainframe title="Sequence Manager"><HOCSequenceDetails editMode={true}/></Mainframe>}/>
|
path="/sequence/add"
|
||||||
<Route path="/sequence" element={<Mainframe title="Sequence Manager"><Sequence /></Mainframe>}/>
|
element={
|
||||||
|
<Mainframe title={t("SequenceManager")}>
|
||||||
|
<HOCSequenceDetails editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/sequence/edit/:sequenceId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("SequenceManager")}>
|
||||||
|
<HOCSequenceDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/sequence"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("SequenceManager")}>
|
||||||
|
<Sequence />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="/domains/add" element={<Mainframe title="Client Domain Manager"><DomainsDetails editMode={false}/></Mainframe>}/>
|
<Route
|
||||||
<Route path="/domains/edit/:domainId/addRole" element={<Mainframe title="Client Domain Manager"><RolesDetails editMode={false} /></Mainframe>}/>
|
path="/domains/add"
|
||||||
<Route path="/domains/edit/:domainId/editRole/:roleId" element={<Mainframe title="Client Domain Manager"><RolesDetails editMode={true} /></Mainframe>}/>
|
element={
|
||||||
<Route path="/domains/edit/:domainId/editRole/:roleId/addUserToRole" element={<Mainframe title="Client Domain Manager"><AddUserToRole editMode={false} /></Mainframe>}/>
|
<Mainframe title={t("ClientDomainManager")}>
|
||||||
<Route path="/domains/edit/:domainId" element={<Mainframe title="Client Domain Manager"><DomainsDetails editMode={true} /></Mainframe>}/>
|
<DomainsDetails editMode={false} />
|
||||||
<Route path="/domains" element={<Mainframe title="Client Domain Manager"><Domains /></Mainframe>}/>
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/domains/edit/:domainId/addRole"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("ClientDomainManager")}>
|
||||||
|
<RolesDetails editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/domains/edit/:domainId/editRole/:roleId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("ClientDomainManager")}>
|
||||||
|
<RolesDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/domains/edit/:domainId/editRole/:roleId/addUserToRole"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("ClientDomainManager")}>
|
||||||
|
<AddUserToRole editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/domains/edit/:domainId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("ClientDomainManager")}>
|
||||||
|
<DomainsDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/domains"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("ClientDomainManager")}>
|
||||||
|
<Domains />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="/users/add" element={<Mainframe title="User Manager"><UserDetails editMode={false}/></Mainframe>}/>
|
<Route
|
||||||
<Route path="/users/edit/:userId" element={<Mainframe title="User Manager"><UserDetails editMode={true} /></Mainframe>}/>
|
path="/users/add"
|
||||||
<Route path="/users" element={<Mainframe title="User Manager"><Users /></Mainframe>}/>
|
element={
|
||||||
|
<Mainframe title={t("UserManager")}>
|
||||||
|
<UserDetails editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/users/edit/:userId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("UserManager")}>
|
||||||
|
<UserDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/users"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("UserManager")}>
|
||||||
|
<Users />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="/ssoManager" element={<Mainframe title="Sso Manager"><SsoManager /></Mainframe>}/>
|
<Route
|
||||||
<Route path="/ssoManager/add" element={<Mainframe title="Sso Manager"><SsoProviderDetails editMode={false}/></Mainframe>}/>
|
path="/ssoManager"
|
||||||
<Route path="/ssoManager/edit/:ssoProviderId" element={<Mainframe title="Sso Manager"><SsoProviderDetails editMode={true} /></Mainframe>}/>
|
element={
|
||||||
|
<Mainframe title={t("SsoManager")}>
|
||||||
|
<SsoManager />
|
||||||
{profileRoute}
|
</Mainframe>
|
||||||
<Route path="/logout" element={<Mainframe><Logout /></Mainframe>}/>
|
}
|
||||||
<Route path="/" element={<Mainframe title="e-suite"><HomePage /></Mainframe>}/>
|
/>
|
||||||
</>
|
<Route
|
||||||
);
|
path="/ssoManager/add"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("SsoManager")}>
|
||||||
|
<SsoProviderDetails editMode={false} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/ssoManager/edit/:ssoProviderId"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("SsoManager")}>
|
||||||
|
<SsoProviderDetails editMode={true} />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{profileRoute}
|
||||||
|
<Route
|
||||||
|
path="/logout"
|
||||||
|
element={
|
||||||
|
<Mainframe>
|
||||||
|
<Logout />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/"
|
||||||
|
element={
|
||||||
|
<Mainframe title={t("e-suite")}>
|
||||||
|
<HomePage />
|
||||||
|
</Mainframe>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setInterval(async () => {
|
const timer = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
if (authentication.hasToken()) {
|
if (authentication.hasToken()) {
|
||||||
await authentication.refreshToken();
|
await authentication.refreshToken();
|
||||||
|
|
||||||
if (authentication.tokenExpired()) {
|
if (authentication.tokenExpired()) {
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
authentication.logout()
|
authentication.logout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (e: any) {
|
||||||
catch (e: any) {
|
console.log(e);
|
||||||
console.log(e);
|
}
|
||||||
}
|
}, 10 * 1000);
|
||||||
}, 10 * 1000);
|
return () => clearInterval(timer);
|
||||||
return () => clearInterval(timer);
|
});
|
||||||
|
|
||||||
|
const isSignedIn = authentication.getCurrentUser() != null;
|
||||||
|
|
||||||
|
const secureRoutes = isSignedIn ? (
|
||||||
|
GetSecureRoutes()
|
||||||
|
) : (
|
||||||
|
<Route path="/" element={<Navigate to="/login" />} />
|
||||||
|
);
|
||||||
|
|
||||||
|
var htmlAttributes: HtmlProps = {
|
||||||
|
"data-bs-theme": theme.getPreferredTheme(),
|
||||||
|
};
|
||||||
|
|
||||||
|
window
|
||||||
|
.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
.addEventListener("change", () => {
|
||||||
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
const isSignedIn = authentication.getCurrentUser() != null;
|
const loginRoute = window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ? (
|
||||||
|
<Route path="/login" element={<Redirect to="/account/login" />} />
|
||||||
|
) : (
|
||||||
|
<Route
|
||||||
|
path="/login"
|
||||||
|
element={
|
||||||
|
<LoginFrame>
|
||||||
|
<LoginForm />
|
||||||
|
</LoginFrame>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
const secureRoutes = isSignedIn ? GetSecureRoutes() : <Route path="/" element={<Navigate to="/login" />} />;
|
return (
|
||||||
|
<HelmetProvider>
|
||||||
var htmlAttributes : HtmlProps = {
|
<Helmet htmlAttributes={htmlAttributes}>
|
||||||
'data-bs-theme' : theme.getPreferredTheme()
|
<title>{config.applicationName}</title>
|
||||||
}
|
</Helmet>
|
||||||
|
<main>
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
<Routes>
|
||||||
window.location.reload();
|
<Route path="/env" element={<EnvPage />} />
|
||||||
})
|
{loginRoute}
|
||||||
|
<Route
|
||||||
const loginRoute = window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ? <Route path="/login" element={<Redirect to="/account/login"/>}/>
|
path="/forgot-password"
|
||||||
: <Route path="/login" element={<LoginFrame><LoginForm /></LoginFrame>} />;
|
element={
|
||||||
|
<LoginFrame>
|
||||||
return (
|
<ForgotPassword />
|
||||||
<HelmetProvider>
|
</LoginFrame>
|
||||||
<Helmet htmlAttributes={htmlAttributes}>
|
}
|
||||||
<title>{config.applicationName}</title>
|
/>
|
||||||
</Helmet>
|
<Route
|
||||||
<main>
|
path="/404"
|
||||||
<Routes>
|
element={
|
||||||
<Route path="/env" element={<EnvPage />} />
|
<LoginFrame>
|
||||||
{loginRoute}
|
<NotFound />
|
||||||
<Route path="/forgot-password" element={<LoginFrame><ForgotPassword /></LoginFrame>} />
|
</LoginFrame>
|
||||||
<Route path="/404" element={<LoginFrame><NotFound /></LoginFrame>} />
|
}
|
||||||
<Route path="/emailuseraction/:token" element={<LoginFrame><EmailUserAction /></LoginFrame>} />
|
/>
|
||||||
{secureRoutes}
|
<Route
|
||||||
<Route path="*" element={<Navigate replace to="/404"/>} />
|
path="/emailuseraction/:token"
|
||||||
</Routes>
|
element={
|
||||||
<ToastContainer />
|
<LoginFrame>
|
||||||
</main>
|
<EmailUserAction />
|
||||||
</HelmetProvider>
|
</LoginFrame>
|
||||||
);
|
}
|
||||||
|
/>
|
||||||
|
{secureRoutes}
|
||||||
|
<Route path="*" element={<Navigate replace to="/404" />} />
|
||||||
|
</Routes>
|
||||||
|
<ToastContainer />
|
||||||
|
</main>
|
||||||
|
</HelmetProvider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@ -2,117 +2,125 @@ import * as React from "react";
|
|||||||
import Option from "./option";
|
import Option from "./option";
|
||||||
|
|
||||||
interface AutocompleteProps {
|
interface AutocompleteProps {
|
||||||
options?: Option[];
|
options?: Option[];
|
||||||
selectedOptions?: Option[];
|
selectedOptions?: Option[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
onSelect: (item: Option) => void;
|
onSelect: (item: Option) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AutocompleteState {
|
interface AutocompleteState {
|
||||||
filteredOptions: Option[];
|
filteredOptions: Option[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Autocomplete extends React.PureComponent<AutocompleteProps, AutocompleteState> {
|
export default class Autocomplete extends React.PureComponent<
|
||||||
private inputRef;
|
AutocompleteProps,
|
||||||
constructor(props: AutocompleteProps) {
|
AutocompleteState
|
||||||
super(props);
|
> {
|
||||||
this.state = { filteredOptions: [] }
|
private inputRef;
|
||||||
this.inputRef = React.createRef<HTMLDivElement>();
|
constructor(props: AutocompleteProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { filteredOptions: [] };
|
||||||
|
this.inputRef = React.createRef<HTMLDivElement>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterOptions(filterTerm: string) {
|
||||||
|
if (filterTerm !== "") {
|
||||||
|
let filtered =
|
||||||
|
this.props.options?.filter((x) =>
|
||||||
|
x.name.toLowerCase().includes(filterTerm.toLowerCase()),
|
||||||
|
) ?? [];
|
||||||
|
filtered = filtered.filter(
|
||||||
|
(x) => !this.props.selectedOptions?.some((y) => x._id === y._id),
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
filteredOptions: filtered,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
filteredOptions: [],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private showOptions() {
|
||||||
|
let filtered = this.props.options?.filter((x) => x.name) ?? [];
|
||||||
|
filtered = filtered.filter(
|
||||||
|
(x) => !this.props.selectedOptions?.some((y) => x._id === y._id),
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
filteredOptions: filtered,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private filterOptions(filterTerm: string) {
|
private hideAutocomplete = (event: any) => {
|
||||||
if (filterTerm !== "") {
|
if (event.target.classList.contains("autocomplete-text-input")) return;
|
||||||
let filtered = this.props.options?.filter(x => x.name.toLowerCase().includes(filterTerm.toLowerCase())) ?? [];
|
|
||||||
filtered = filtered.filter(x => !this.props.selectedOptions?.some(y => x._id === y._id));
|
|
||||||
this.setState({
|
|
||||||
filteredOptions: filtered
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.setState({
|
|
||||||
filteredOptions: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private showOptions() {
|
this.setState({
|
||||||
let filtered = this.props.options?.filter(x => x.name) ?? [];
|
filteredOptions: [],
|
||||||
filtered = filtered.filter(x => !this.props.selectedOptions?.some(y => x._id === y._id));
|
});
|
||||||
this.setState({
|
};
|
||||||
filteredOptions: filtered
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private hideAutocomplete = (event: any) => {
|
private handleBlur = (event: React.FocusEvent) => {
|
||||||
if (event.target.classList.contains('autocomplete-text-input'))
|
setTimeout(() => {
|
||||||
return;
|
if (
|
||||||
|
this.inputRef.current &&
|
||||||
|
this.inputRef.current.contains(document.activeElement)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({ filteredOptions: [] });
|
||||||
filteredOptions: []
|
}, 0);
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
private handleBlur = (event: React.FocusEvent) => {
|
componentDidMount() {
|
||||||
setTimeout(() => {
|
document.addEventListener("click", this.hideAutocomplete);
|
||||||
if (
|
}
|
||||||
this.inputRef.current &&
|
|
||||||
this.inputRef.current.contains(document.activeElement)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ filteredOptions: [] });
|
componentWillUnmount() {
|
||||||
}, 0);
|
document.removeEventListener("click", this.hideAutocomplete);
|
||||||
};
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
render() {
|
||||||
document.addEventListener('click', this.hideAutocomplete);
|
const { placeholder, onSelect } = this.props;
|
||||||
}
|
const { filteredOptions } = this.state;
|
||||||
|
|
||||||
componentWillUnmount() {
|
return (
|
||||||
document.removeEventListener('click', this.hideAutocomplete);
|
<div
|
||||||
}
|
className="autocomplete"
|
||||||
|
ref={this.inputRef}
|
||||||
render() {
|
onBlur={this.handleBlur}
|
||||||
const { placeholder, onSelect } = this.props;
|
tabIndex={-1}
|
||||||
const { filteredOptions } = this.state;
|
>
|
||||||
|
<input
|
||||||
return (
|
className="autocomplete-text-input"
|
||||||
<div className="autocomplete"
|
type="text"
|
||||||
ref={this.inputRef}
|
onChange={(e) => {
|
||||||
onBlur={this.handleBlur}
|
this.filterOptions(e.target.value);
|
||||||
tabIndex={-1}
|
}}
|
||||||
>
|
onFocus={(e) => {
|
||||||
<input
|
this.showOptions();
|
||||||
className="autocomplete-text-input"
|
}}
|
||||||
type="text"
|
placeholder={placeholder}
|
||||||
onChange={(e) => { this.filterOptions(e.target.value) }}
|
/>
|
||||||
onFocus={(e) => { this.showOptions() }}
|
{filteredOptions.length > 0 && (
|
||||||
placeholder={placeholder}
|
<ul className="autocomplete-options">
|
||||||
/>
|
{filteredOptions.map((x, i) => (
|
||||||
{filteredOptions.length > 0 && (
|
<li key={x._id} value={x._id} tabIndex={0}>
|
||||||
<ul className="autocomplete-options">
|
<button
|
||||||
{filteredOptions.map((x, i) =>
|
className="autocomplete-option text-left"
|
||||||
<li
|
onClick={(e) => {
|
||||||
key={x._id}
|
onSelect(x);
|
||||||
value={x._id}
|
this.setState({ filteredOptions: [] });
|
||||||
tabIndex={0}
|
}}
|
||||||
>
|
>
|
||||||
<button
|
{x.name}
|
||||||
className="autocomplete-option text-left"
|
</button>
|
||||||
onClick={(e) => {
|
</li>
|
||||||
onSelect(x);
|
))}
|
||||||
this.setState({ filteredOptions: [] })
|
</ul>
|
||||||
}}
|
)}
|
||||||
>
|
</div>
|
||||||
{x.name}
|
);
|
||||||
</button>
|
}
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,93 +1,110 @@
|
|||||||
import react, { SyntheticEvent } from 'react';
|
import React, { SyntheticEvent } from "react";
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
export enum ButtonType{
|
export enum ButtonType {
|
||||||
none,
|
none,
|
||||||
primary,
|
primary,
|
||||||
secondary,
|
secondary,
|
||||||
success,
|
success,
|
||||||
danger,
|
danger,
|
||||||
warning,
|
warning,
|
||||||
info,
|
info,
|
||||||
light,
|
light,
|
||||||
dark,
|
dark,
|
||||||
link
|
link,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ButtonProps<T>{
|
export interface ButtonProps<T> {
|
||||||
testid?: string;
|
testid?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
id?:string;
|
id?: string;
|
||||||
name?:string;
|
name?: string;
|
||||||
keyValue?: T;
|
keyValue?: T;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
buttonType : ButtonType;
|
buttonType: ButtonType;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
to?: string;
|
to?: string;
|
||||||
onClick?: ( keyValue : T | undefined ) => void;
|
onClick?: (keyValue: T | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Button<T> extends react.Component<ButtonProps<T>> {
|
function Button<T>({
|
||||||
Click = (e : SyntheticEvent) => {
|
testid,
|
||||||
const {keyValue, onClick} = this.props;
|
className,
|
||||||
|
id,
|
||||||
if (onClick)
|
name,
|
||||||
{
|
keyValue,
|
||||||
e.preventDefault();
|
children,
|
||||||
onClick(keyValue);
|
buttonType,
|
||||||
}
|
disabled,
|
||||||
|
to,
|
||||||
|
onClick,
|
||||||
|
}: ButtonProps<T>) {
|
||||||
|
const handleClick = (e: SyntheticEvent) => {
|
||||||
|
if (onClick) {
|
||||||
|
e.preventDefault();
|
||||||
|
onClick(keyValue);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
render() {
|
|
||||||
const { id, className, children, buttonType, disabled, name, to, testid } = this.props;
|
|
||||||
|
|
||||||
let classNames = "";
|
let classNames = "";
|
||||||
|
|
||||||
switch (buttonType)
|
switch (buttonType) {
|
||||||
{
|
case ButtonType.primary:
|
||||||
case ButtonType.primary:
|
classNames = "btn btn-primary";
|
||||||
classNames = "btn btn-primary";
|
break;
|
||||||
break;
|
case ButtonType.secondary:
|
||||||
case ButtonType.secondary:
|
classNames = "btn btn-secondary";
|
||||||
classNames = "btn btn-secondary";
|
break;
|
||||||
break;
|
case ButtonType.success:
|
||||||
case ButtonType.success:
|
classNames = "btn btn-success";
|
||||||
classNames = "btn btn-success";
|
break;
|
||||||
break;
|
case ButtonType.danger:
|
||||||
case ButtonType.danger:
|
classNames = "btn btn-danger";
|
||||||
classNames = "btn btn-danger";
|
break;
|
||||||
break;
|
case ButtonType.warning:
|
||||||
case ButtonType.warning:
|
classNames = "btn btn-warning";
|
||||||
classNames = "btn btn-warning";
|
break;
|
||||||
break;
|
case ButtonType.info:
|
||||||
case ButtonType.info:
|
classNames = "btn btn-info";
|
||||||
classNames = "btn btn-info";
|
break;
|
||||||
break;
|
case ButtonType.light:
|
||||||
case ButtonType.light:
|
classNames = "btn btn-light";
|
||||||
classNames = "btn btn-light";
|
break;
|
||||||
break;
|
case ButtonType.dark:
|
||||||
case ButtonType.dark:
|
classNames = "btn btn-dark";
|
||||||
classNames = "btn btn-dark";
|
break;
|
||||||
break;
|
case ButtonType.link:
|
||||||
case ButtonType.link:
|
classNames = "btn btn-link";
|
||||||
classNames = "btn btn-link";
|
break;
|
||||||
break;
|
case ButtonType.none:
|
||||||
case ButtonType.none:
|
classNames = "btn btn-default";
|
||||||
classNames = "btn btn-default"
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (className !== undefined)
|
if (className) {
|
||||||
{
|
classNames += " " + className;
|
||||||
classNames += " " + className;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (to !== undefined){
|
|
||||||
return <Link data-testid={testid} id={id} className={classNames} to={to} >{children}</Link>
|
|
||||||
}
|
|
||||||
|
|
||||||
return <button data-testid={testid} id={id} className={classNames} name={name} disabled={disabled} onClick={this.Click}>{children}</button>;
|
if (to) {
|
||||||
}
|
return (
|
||||||
|
<Link data-testid={testid} id={id} className={classNames} to={to}>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
data-testid={testid}
|
||||||
|
id={id}
|
||||||
|
className={classNames}
|
||||||
|
name={name}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Button;
|
export default Button;
|
||||||
|
|||||||
@ -1,57 +1,57 @@
|
|||||||
import react from 'react';
|
import React, { useState, useCallback } from "react";
|
||||||
import Button, { ButtonType } from './Button';
|
import Button, { ButtonType } from "./Button";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Namespaces } from "../../i18n";
|
||||||
|
|
||||||
export interface ConfirmButtonProps<T>{
|
export interface ConfirmButtonProps<T> {
|
||||||
delayMS? : number;
|
delayMS?: number;
|
||||||
buttonType : ButtonType;
|
buttonType: ButtonType;
|
||||||
keyValue: T;
|
keyValue: T;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
confirmMessage?: React.ReactNode;
|
confirmMessage?: React.ReactNode;
|
||||||
onClick?: ( keyValue? : T ) => void;
|
onClick?: (keyValue?: T) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfirmButtonState{
|
function ConfirmButton<T>({
|
||||||
firstClick : boolean
|
delayMS = 5000,
|
||||||
|
buttonType,
|
||||||
|
keyValue,
|
||||||
|
children,
|
||||||
|
confirmMessage,
|
||||||
|
onClick,
|
||||||
|
}: ConfirmButtonProps<T>) {
|
||||||
|
const [firstClick, setFirstClick] = useState(false);
|
||||||
|
const t = useTranslation<typeof Namespaces.Common>();
|
||||||
|
|
||||||
|
const handleFirstClick = useCallback(() => {
|
||||||
|
setFirstClick(true);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setFirstClick(false);
|
||||||
|
}, delayMS);
|
||||||
|
}, [delayMS]);
|
||||||
|
|
||||||
|
const handleSecondClick = useCallback(() => {
|
||||||
|
if (onClick) {
|
||||||
|
onClick(keyValue);
|
||||||
|
}
|
||||||
|
}, [onClick, keyValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!firstClick && (
|
||||||
|
<Button buttonType={buttonType} onClick={handleFirstClick}>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{firstClick && (
|
||||||
|
<Button buttonType={ButtonType.danger} onClick={handleSecondClick}>
|
||||||
|
{confirmMessage ?? <>t("Are you sure?")</>}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfirmButton<T> extends react.Component<ConfirmButtonProps<T>, ConfirmButtonState > {
|
export default ConfirmButton;
|
||||||
state : ConfirmButtonState = {
|
|
||||||
firstClick : false
|
|
||||||
}
|
|
||||||
|
|
||||||
FirstClick = () => {
|
|
||||||
const firstClick = true;
|
|
||||||
this.setState({firstClick});
|
|
||||||
|
|
||||||
let { delayMS } = this.props;
|
|
||||||
if (delayMS === undefined)
|
|
||||||
delayMS = 5000;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log(`updating state`)
|
|
||||||
const firstClick = false;
|
|
||||||
this.setState({firstClick});
|
|
||||||
}, delayMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
SecondClick = () => {
|
|
||||||
const {keyValue, onClick} = this.props;
|
|
||||||
|
|
||||||
if (onClick)
|
|
||||||
onClick(keyValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { buttonType, children, confirmMessage } = this.props;
|
|
||||||
const { firstClick } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!firstClick && <Button buttonType={buttonType} onClick={this.FirstClick}>{children}</Button>}
|
|
||||||
{firstClick && <Button buttonType={ButtonType.danger} onClick={this.SecondClick}>{confirmMessage!==undefined?confirmMessage:"Are you sure?"}</Button>}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ConfirmButton;
|
|
||||||
|
|||||||
29
src/i18n.ts
Normal file
29
src/i18n.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// /src/i18n.ts
|
||||||
|
import i18n from "i18next";
|
||||||
|
import { initReactI18next } from "react-i18next";
|
||||||
|
import HttpBackend from "i18next-http-backend";
|
||||||
|
import { determineInitialLanguage } from "./modules/frame/services/lanugageService";
|
||||||
|
|
||||||
|
export const Namespaces = {
|
||||||
|
Common: "common",
|
||||||
|
Frame: "frame",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type Namespace = (typeof Namespaces)[keyof typeof Namespaces];
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(HttpBackend) // load translations from /public/locales
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
lng: determineInitialLanguage(),
|
||||||
|
fallbackLng: "en",
|
||||||
|
defaultNS: "common",
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
backend: {
|
||||||
|
loadPath: "/locales/{{lng}}/{{ns}}.json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
@ -1,17 +1,19 @@
|
|||||||
|
import "./i18n";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import reportWebVitals from "./reportWebVitals";
|
import reportWebVitals from "./reportWebVitals";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(
|
||||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
document.getElementById("root") as HTMLElement,
|
||||||
|
);
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</React.StrictMode>
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
|||||||
@ -1,92 +1,120 @@
|
|||||||
import * as React from "react";
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
import authentication from "../services/authenticationService";
|
import authentication from "../services/authenticationService";
|
||||||
import '../../../Sass/_leftMenu.scss';
|
import "../../../Sass/_leftMenu.scss";
|
||||||
import { faCog, faCogs, faHome, faPrint } from "@fortawesome/pro-thin-svg-icons";
|
import {
|
||||||
|
faCog,
|
||||||
|
faCogs,
|
||||||
|
faHome,
|
||||||
|
faPrint,
|
||||||
|
} from "@fortawesome/pro-thin-svg-icons";
|
||||||
import LeftMenuItem from "./LeftMenuItem";
|
import LeftMenuItem from "./LeftMenuItem";
|
||||||
import LeftMenuSubMenu, { LOCLeftMenuSubMenu } from "./LeftMenuSubMenu";
|
import LeftMenuSubMenu, { LOCLeftMenuSubMenu } from "./LeftMenuSubMenu";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Namespaces } from "../../../i18n";
|
||||||
|
|
||||||
interface LeftMenuProps {
|
const LeftMenu: React.FC = () => {
|
||||||
|
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||||
}
|
|
||||||
|
|
||||||
interface LeftMenuState {
|
|
||||||
openMenuItem? : LOCLeftMenuSubMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LeftMenu extends React.Component<LeftMenuProps, LeftMenuState> {
|
|
||||||
state : LeftMenuState = {
|
|
||||||
openMenuItem : undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
const [openMenuItem, setOpenMenuItem] = useState<LOCLeftMenuSubMenu>();
|
||||||
document.body.addEventListener('click', () => {
|
|
||||||
this.setState( { openMenuItem : undefined })
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<LeftMenuProps>, prevState: Readonly<LeftMenuState>, snapshot?: any): void {
|
// Close menus when clicking outside
|
||||||
if (prevState === this.state)
|
useEffect(() => {
|
||||||
{
|
const handleClick = () => setOpenMenuItem(undefined);
|
||||||
this.setState( { openMenuItem : undefined })
|
document.body.addEventListener("click", handleClick, true);
|
||||||
}
|
return () => document.body.removeEventListener("click", handleClick, true);
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
handleClick = (menuItem: LOCLeftMenuSubMenu) => {
|
const handleClick = useCallback((menuItem: LOCLeftMenuSubMenu) => {
|
||||||
const { openMenuItem } = this.state;
|
setOpenMenuItem((current) => (current === menuItem ? undefined : menuItem));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const newMenuItem = openMenuItem === menuItem ? undefined : menuItem;
|
// Access checks
|
||||||
|
const viewOrganisation = authentication.hasAccess("ViewOrganisation");
|
||||||
|
const viewUser = authentication.hasAccess("ViewUser");
|
||||||
|
const viewDomain = authentication.hasAccess("ViewDomain");
|
||||||
|
const viewGlossary = authentication.hasAccess("ViewGlossary");
|
||||||
|
const viewFormTemplate = authentication.hasAccess("ViewFormTemplate");
|
||||||
|
const viewField = authentication.hasAccess("ViewField");
|
||||||
|
const viewSequence = authentication.hasAccess("ViewSequence");
|
||||||
|
const viewSsoManager = authentication.hasAccess("ViewSsoProviders");
|
||||||
|
|
||||||
this.setState( { openMenuItem : newMenuItem })
|
const viewAdmin =
|
||||||
};
|
viewUser ||
|
||||||
|
viewDomain ||
|
||||||
|
viewGlossary ||
|
||||||
|
viewFormTemplate ||
|
||||||
|
viewField ||
|
||||||
|
viewSequence;
|
||||||
|
|
||||||
render() {
|
const viewAuditLog = authentication.hasAccess("ViewAuditLog");
|
||||||
const viewOrganisation = authentication.hasAccess("ViewOrganisation");
|
const viewBlockedIPAddresses = authentication.hasAccess(
|
||||||
|
"ViewBlockedIPAddresses",
|
||||||
const viewUser = authentication.hasAccess("ViewUser" );
|
);
|
||||||
const viewDomain = authentication.hasAccess("ViewDomain" );
|
const viewErrorLogs = authentication.hasAccess("ViewErrorLogs");
|
||||||
const viewGlossary = authentication.hasAccess("ViewGlossary");
|
|
||||||
const viewFormTemplate = authentication.hasAccess("ViewFormTemplate");
|
|
||||||
const viewField = authentication.hasAccess("ViewField");
|
|
||||||
const viewSequence = authentication.hasAccess("ViewSequence");
|
|
||||||
const viewSsoManager = authentication.hasAccess("ViewSsoProviders");
|
|
||||||
|
|
||||||
const viewAdmin = viewUser || viewDomain || viewGlossary || viewFormTemplate || viewField || viewSequence;
|
|
||||||
|
|
||||||
const viewAuditLog = authentication.hasAccess("ViewAuditLog");
|
|
||||||
const viewBlockedIPAddresses = authentication.hasAccess("ViewBlockedIPAddresses");
|
|
||||||
const viewErrorLogs = authentication.hasAccess("ViewErrorLogs");
|
|
||||||
|
|
||||||
const viewSupport = viewAuditLog || viewBlockedIPAddresses || viewErrorLogs;
|
const viewSupport = viewAuditLog || viewBlockedIPAddresses || viewErrorLogs;
|
||||||
|
|
||||||
const { openMenuItem } = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="leftMenu">
|
<div className="leftMenu">
|
||||||
<LeftMenuItem to="/" icon={faHome} label="Home" />
|
<LeftMenuItem to="/" icon={faHome} label={t("Home")} />
|
||||||
{viewOrganisation && <LeftMenuItem to="/organisations" icon={faPrint} label="e-print" />}
|
|
||||||
{viewAdmin && <LeftMenuSubMenu openMenu={openMenuItem} icon={faCog} label="Admin" onClick={this.handleClick} >
|
|
||||||
{viewUser && <LeftMenuItem to="/users" label="Users"/>}
|
|
||||||
{viewDomain && <LeftMenuItem to="/domains" label="Client Domains"/>}
|
|
||||||
{viewGlossary && <LeftMenuItem to="/glossaries" label="Glossaries"/>}
|
|
||||||
{viewFormTemplate && <LeftMenuItem to="/forms" label="Forms"/>}
|
|
||||||
{viewField && <LeftMenuItem to="/customfields" label="Custom Fields"/>}
|
|
||||||
{viewSequence && <LeftMenuItem to="/sequence" label="Sequence"/>}
|
|
||||||
{viewSsoManager && <LeftMenuItem to="/ssoManager" label="Sso Manager"/>}
|
|
||||||
</LeftMenuSubMenu>
|
|
||||||
}
|
|
||||||
{viewSupport && <LeftMenuSubMenu openMenu={openMenuItem} icon={faCogs} label="Support" onClick={this.handleClick} >
|
|
||||||
{viewAuditLog && <LeftMenuItem to="/audit" label="Audit Log"/>}
|
|
||||||
{viewBlockedIPAddresses && <LeftMenuItem to="/blockedips" label="Blocked IPs"/>}
|
|
||||||
{viewErrorLogs && <LeftMenuItem to="/exceptionlogs" label="Error Logs"/>}
|
|
||||||
</LeftMenuSubMenu>}
|
|
||||||
</div>
|
|
||||||
{openMenuItem && <div className="subbar">{openMenuItem.props.children}</div>}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
{viewOrganisation && (
|
||||||
|
<LeftMenuItem to="/organisations" icon={faPrint} label="e-print" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{viewAdmin && (
|
||||||
|
<LeftMenuSubMenu
|
||||||
|
openMenu={openMenuItem}
|
||||||
|
icon={faCog}
|
||||||
|
label={t("Admin")}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{viewUser && <LeftMenuItem to="/users" label={t("Users")} />}
|
||||||
|
{viewDomain && (
|
||||||
|
<LeftMenuItem to="/domains" label={t("ClientDomains")} />
|
||||||
|
)}
|
||||||
|
{viewGlossary && (
|
||||||
|
<LeftMenuItem to="/glossaries" label={t("Glossaries")} />
|
||||||
|
)}
|
||||||
|
{viewFormTemplate && (
|
||||||
|
<LeftMenuItem to="/forms" label={t("Forms")} />
|
||||||
|
)}
|
||||||
|
{viewField && (
|
||||||
|
<LeftMenuItem to="/customfields" label={t("CustomFields")} />
|
||||||
|
)}
|
||||||
|
{viewSequence && (
|
||||||
|
<LeftMenuItem to="/sequence" label={t("Sequence")} />
|
||||||
|
)}
|
||||||
|
{viewSsoManager && (
|
||||||
|
<LeftMenuItem to="/ssoManager" label={t("SsoManager")} />
|
||||||
|
)}
|
||||||
|
</LeftMenuSubMenu>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{viewSupport && (
|
||||||
|
<LeftMenuSubMenu
|
||||||
|
openMenu={openMenuItem}
|
||||||
|
icon={faCogs}
|
||||||
|
label={t("Support")}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{viewAuditLog && <LeftMenuItem to="/audit" label={t("AuditLog")} />}
|
||||||
|
{viewBlockedIPAddresses && (
|
||||||
|
<LeftMenuItem to="/blockedips" label={t("BlockedIPs")} />
|
||||||
|
)}
|
||||||
|
{viewErrorLogs && (
|
||||||
|
<LeftMenuItem to="/exceptionlogs" label={t("ErrorLogs")} />
|
||||||
|
)}
|
||||||
|
</LeftMenuSubMenu>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{openMenuItem && (
|
||||||
|
<div className="subbar">{openMenuItem.props.children}</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default LeftMenu;
|
export default LeftMenu;
|
||||||
|
|||||||
@ -4,26 +4,23 @@ import LeftMenu from "./LeftMenu";
|
|||||||
|
|
||||||
import "../../../Sass/_frame.scss";
|
import "../../../Sass/_frame.scss";
|
||||||
|
|
||||||
|
|
||||||
type MainFrameProps = {
|
type MainFrameProps = {
|
||||||
title?: string;
|
title?: string | undefined | null;
|
||||||
children?: React.ReactNode; // 👈️ type children
|
children?: React.ReactNode; // 👈️ type children
|
||||||
};
|
};
|
||||||
|
|
||||||
const Mainframe = (props: MainFrameProps): JSX.Element => {
|
const Mainframe = (props: MainFrameProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div className="frame">
|
<div className="frame">
|
||||||
<TopMenu title={props.title} />
|
<TopMenu title={props.title ? props.title : undefined} />
|
||||||
<div className="frame-row">
|
<div className="frame-row">
|
||||||
<div className="frame-leftMenu">
|
<div className="frame-leftMenu">
|
||||||
<LeftMenu />
|
<LeftMenu />
|
||||||
</div>
|
|
||||||
<div className="frame-workArea">
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="frame-workArea">{props.children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Mainframe;
|
export default Mainframe;
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
export default interface JwtToken {
|
export default interface JwtToken {
|
||||||
expiry: Date;
|
expiry: Date;
|
||||||
primarysid: bigint;
|
primarysid: bigint;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
domainid: bigint;
|
domainid: bigint;
|
||||||
securityPrivileges: [];
|
securityPrivileges: [];
|
||||||
|
language?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/modules/frame/services/lanugageService.ts
Normal file
21
src/modules/frame/services/lanugageService.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { getCurrentUser } from "./authenticationService";
|
||||||
|
|
||||||
|
export function determineInitialLanguage() {
|
||||||
|
// 1. JWT preference
|
||||||
|
const currentUser = getCurrentUser();
|
||||||
|
if (currentUser !== null) {
|
||||||
|
const jwtLang = currentUser.language || null;
|
||||||
|
if (jwtLang) return jwtLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. LocalStorage
|
||||||
|
//const storedLang = localStorage.getItem("appLanguage");
|
||||||
|
//if (storedLang) return storedLang;
|
||||||
|
|
||||||
|
// 3. Browser
|
||||||
|
const browserLang = navigator.language?.split("-")[0];
|
||||||
|
if (browserLang) return browserLang;
|
||||||
|
|
||||||
|
// 4. Default
|
||||||
|
return "en";
|
||||||
|
}
|
||||||
@ -1,11 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
@ -20,8 +16,5 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "preserve"
|
"jsx": "preserve"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src", "src/types", "i18next-parser.config.js"]
|
||||||
"src",
|
|
||||||
"src/types"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user