htmlIsland support added, so the profile and login forms are now embedded into react controls.
This commit is contained in:
parent
62e0f966b8
commit
5fb966996a
@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@ -28,8 +28,12 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<title>e-suite</title>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@ -134,6 +134,7 @@
|
||||
"PressAgainToUnblock": "Press again to unblock",
|
||||
"PrintSpecification": "Print Specification",
|
||||
"Profile": "Profile",
|
||||
"ProfileSaved": "Profile updated.",
|
||||
"ResendConfirm": "Resend Confirm",
|
||||
"Required": "Required",
|
||||
"ResetPassword": "Reset Password",
|
||||
|
||||
8
public/locales/en/htmlIsland.json
Normal file
8
public/locales/en/htmlIsland.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"island": {
|
||||
"loadError": "Failed to load this section.",
|
||||
"networkError": "Network error while saving.",
|
||||
"serverError": "Server error while saving.",
|
||||
"saveSuccess": "Saved successfully."
|
||||
}
|
||||
}
|
||||
@ -134,6 +134,7 @@
|
||||
"PressAgainToUnblock": "Appuyez de nouveau pour débloquer",
|
||||
"PrintSpecification": "Imprimer la spécification",
|
||||
"Profile": "Profil",
|
||||
"ProfileSaved": "Profil mis à jour.",
|
||||
"ResendConfirm": "Renvoyer la confirmation",
|
||||
"Required": "Requis",
|
||||
"ResetPassword": "Réinitialiser le mot de passe",
|
||||
|
||||
8
public/locales/fr/htmlIsland.json
Normal file
8
public/locales/fr/htmlIsland.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"island": {
|
||||
"loadError": "Échec du chargement de cette section.",
|
||||
"networkError": "Erreur réseau lors de l’enregistrement.",
|
||||
"serverError": "Erreur du serveur lors de l’enregistrement.",
|
||||
"saveSuccess": "Enregistré avec succès."
|
||||
}
|
||||
}
|
||||
27
src/App.tsx
27
src/App.tsx
@ -9,7 +9,6 @@ import ForgotPassword from "./modules/frame/components/ForgotPassword";
|
||||
import NotFound from "./modules/frame/components/NotFound";
|
||||
import Logout from "./modules/frame/components/Logout";
|
||||
import LoginForm from "./modules/frame/components/LoginForm";
|
||||
import Redirect from "./components/common/Redirect";
|
||||
import Mainframe from "./modules/frame/components/Mainframe";
|
||||
import EmailUserAction from "./modules/frame/components/EmailUserAction";
|
||||
import { HashNavigationProvider } from "./utils/HashNavigationContext";
|
||||
@ -48,9 +47,7 @@ import { Namespaces } from "./i18n/i18n";
|
||||
|
||||
function GetSecureRoutes() {
|
||||
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||
const profileRoute = window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ? (
|
||||
<Route path="/profile" element={<Redirect to="/account/profile" />} />
|
||||
) : (
|
||||
const profileRoute = (
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
@ -444,19 +441,6 @@ function App() {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
const loginRoute = window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ? (
|
||||
<Route path="/login" element={<Redirect to="/account/login" />} />
|
||||
) : (
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<LoginFrame>
|
||||
<LoginForm />
|
||||
</LoginFrame>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<HashNavigationProvider>
|
||||
@ -466,7 +450,14 @@ function App() {
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="/env" element={<EnvPage />} />
|
||||
{loginRoute}
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<LoginFrame>
|
||||
<LoginForm />
|
||||
</LoginFrame>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/forgot-password"
|
||||
element={
|
||||
|
||||
181
src/components/common/HtmlIsland.tsx
Normal file
181
src/components/common/HtmlIsland.tsx
Normal file
@ -0,0 +1,181 @@
|
||||
import React, { useEffect, useState, useRef, useCallback } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Namespaces } from "../../i18n/i18n";
|
||||
|
||||
export interface HtmlIslandProps {
|
||||
url: string;
|
||||
islandId?: string;
|
||||
successMessage?: string;
|
||||
}
|
||||
|
||||
const HtmlIsland: React.FC<HtmlIslandProps> = ({
|
||||
url,
|
||||
islandId,
|
||||
successMessage,
|
||||
}) => {
|
||||
const [html, setHtml] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { t: tIsland } = useTranslation(Namespaces.HtmlIsland);
|
||||
|
||||
//
|
||||
// Load the island HTML
|
||||
//
|
||||
const loadIsland = useCallback(() => {
|
||||
setError(null);
|
||||
|
||||
fetch(url, { credentials: "include" })
|
||||
.then((r) => {
|
||||
if (!r.ok) throw new Error(`Failed to load island: ${r.status}`);
|
||||
return r.text();
|
||||
})
|
||||
.then((text) => setHtml(text))
|
||||
.catch(() => {
|
||||
setError(tIsland("island.loadError"));
|
||||
toast.error(tIsland("island.loadError"));
|
||||
});
|
||||
}, [url, tIsland]);
|
||||
|
||||
//
|
||||
// Initial load + reload when URL changes
|
||||
//
|
||||
useEffect(() => {
|
||||
loadIsland();
|
||||
}, [loadIsland]);
|
||||
|
||||
//
|
||||
// After HTML is injected, run scripts + bind form handling
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!html || !containerRef.current) return;
|
||||
|
||||
const container = containerRef.current;
|
||||
|
||||
//
|
||||
// 1. Extract and execute <script> tags
|
||||
//
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, "text/html");
|
||||
const scripts = Array.from(doc.querySelectorAll("script"));
|
||||
|
||||
scripts.forEach((script) => {
|
||||
const newScript = document.createElement("script");
|
||||
|
||||
if (script.src) {
|
||||
newScript.src = script.src;
|
||||
} else {
|
||||
newScript.textContent = script.textContent;
|
||||
}
|
||||
|
||||
if (islandId) {
|
||||
newScript.dataset.island = islandId;
|
||||
}
|
||||
|
||||
document.body.appendChild(newScript);
|
||||
});
|
||||
|
||||
//
|
||||
// 2. Rebind jQuery Unobtrusive Validation (if available)
|
||||
//
|
||||
const w = window as any;
|
||||
if (
|
||||
w.jQuery &&
|
||||
w.jQuery.validator &&
|
||||
w.jQuery.validator.unobtrusive &&
|
||||
container
|
||||
) {
|
||||
w.jQuery.validator.unobtrusive.parse(container);
|
||||
}
|
||||
|
||||
//
|
||||
// 3. Intercept form submissions (unless opted out)
|
||||
//
|
||||
const form = container.querySelector("form");
|
||||
if (!form) return;
|
||||
|
||||
const shouldFullPageRedirect =
|
||||
form.getAttribute("data-full-page-redirect") === "true";
|
||||
|
||||
if (shouldFullPageRedirect) {
|
||||
// Do NOT intercept this form – let the browser handle POST + redirect
|
||||
return;
|
||||
}
|
||||
|
||||
const submitHandler = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
let response: Response;
|
||||
|
||||
try {
|
||||
response = await fetch(form.action, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
credentials: "include",
|
||||
redirect: "manual",
|
||||
});
|
||||
} catch {
|
||||
setError(tIsland("island.networkError"));
|
||||
toast.error(tIsland("island.networkError"));
|
||||
return;
|
||||
}
|
||||
|
||||
const isHttpRedirect = [301, 302, 303, 307, 308].includes(
|
||||
response.status,
|
||||
);
|
||||
|
||||
if (isHttpRedirect) {
|
||||
toast.success(
|
||||
successMessage ? successMessage : tIsland("island.saveSuccess"),
|
||||
);
|
||||
loadIsland();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
setError(tIsland("island.serverError"));
|
||||
toast.error(tIsland("island.serverError"));
|
||||
return;
|
||||
}
|
||||
|
||||
const newHtml = await response.text();
|
||||
setHtml(newHtml);
|
||||
toast.success(
|
||||
successMessage ? successMessage : tIsland("island.saveSuccess"),
|
||||
);
|
||||
};
|
||||
|
||||
form.addEventListener("submit", submitHandler);
|
||||
|
||||
// Cleanup on re-render
|
||||
return () => {
|
||||
form.removeEventListener("submit", submitHandler);
|
||||
};
|
||||
}, [html, islandId, successMessage, tIsland, loadIsland]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{error && (
|
||||
<div
|
||||
style={{
|
||||
background: "#fee",
|
||||
border: "1px solid #c00",
|
||||
padding: "8px",
|
||||
marginBottom: "10px",
|
||||
borderRadius: "4px",
|
||||
color: "#900",
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={containerRef} dangerouslySetInnerHTML={{ __html: html }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HtmlIsland;
|
||||
@ -8,6 +8,7 @@ import { fallbackLng } from "./generatedLocales";
|
||||
export const Namespaces = {
|
||||
Common: "common",
|
||||
MailTypes: "mailTypes",
|
||||
HtmlIsland: "htmlIsland",
|
||||
} as const;
|
||||
|
||||
export type Namespace = (typeof Namespaces)[keyof typeof Namespaces];
|
||||
|
||||
21
src/modules/frame/components/ExternalLoginForm.tsx
Normal file
21
src/modules/frame/components/ExternalLoginForm.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import HtmlIsland from "../../../components/common/HtmlIsland";
|
||||
import { Namespaces } from "../../../i18n/i18n";
|
||||
|
||||
export interface ExternalLoginFormProps {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const ExternalLoginForm: React.FC<ExternalLoginFormProps> = ({ url }) => {
|
||||
const { t } = useTranslation(Namespaces.Common);
|
||||
|
||||
return (
|
||||
<HtmlIsland
|
||||
url={url}
|
||||
islandId="loginform"
|
||||
successMessage={t("ProfileSaved")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExternalLoginForm;
|
||||
245
src/modules/frame/components/InternalLoginForm.tsx
Normal file
245
src/modules/frame/components/InternalLoginForm.tsx
Normal file
@ -0,0 +1,245 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Link, Navigate } from "react-router-dom";
|
||||
import Joi from "joi";
|
||||
import authentication from "../services/authenticationService";
|
||||
import { InputType } from "../../../components/common/Input";
|
||||
import { ButtonType } from "../../../components/common/Button";
|
||||
import { useForm } from "../../../components/common/useForm";
|
||||
import {
|
||||
renderButton,
|
||||
renderError,
|
||||
renderInput,
|
||||
} from "../../../components/common/formHelpers";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Namespaces } from "../../../i18n/i18n";
|
||||
|
||||
const InternalLoginForm: React.FC = () => {
|
||||
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||
const [isInNextStage, setIsInNextStage] = useState(false);
|
||||
const [emailSent, setEmailSent] = useState(false);
|
||||
const passwordMaxLength = 255;
|
||||
|
||||
const form = useForm({
|
||||
loaded: true,
|
||||
data: {
|
||||
username: "",
|
||||
password: "",
|
||||
tfaNeeded: false,
|
||||
requestTfaRemoval: false,
|
||||
securityCode: "",
|
||||
},
|
||||
errors: {},
|
||||
redirect: "",
|
||||
});
|
||||
|
||||
const schema = {
|
||||
username: Joi.string()
|
||||
.required()
|
||||
.email({ tlds: { allow: false } })
|
||||
.label(t("Email")),
|
||||
password: Joi.string().required().label(t("Password")),
|
||||
tfaNeeded: Joi.boolean().required(),
|
||||
requestTfaRemoval: Joi.boolean().required(),
|
||||
securityCode: Joi.string().allow("").label(t("Authenticate")),
|
||||
};
|
||||
|
||||
form.schema = schema;
|
||||
|
||||
useEffect(() => {
|
||||
window.location.replace("/login");
|
||||
}, []);
|
||||
|
||||
const performLogin = useCallback(
|
||||
async (data: any) => {
|
||||
try {
|
||||
const result = await authentication.login(
|
||||
data.username,
|
||||
data.password,
|
||||
data.securityCode,
|
||||
data.requestTfaRemoval,
|
||||
);
|
||||
|
||||
switch (result) {
|
||||
case 1: {
|
||||
const nextData = { ...form.state.data };
|
||||
|
||||
if (!nextData.tfaNeeded) {
|
||||
nextData.tfaNeeded = true;
|
||||
form.setState({ data: nextData });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
window.location.href = "/";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (ex: any) {
|
||||
form.handleGeneralError(ex);
|
||||
}
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
const doSubmit = async () => {
|
||||
const { data } = form.state;
|
||||
await performLogin(data);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
form.handleSubmit(e, async () => doSubmit());
|
||||
};
|
||||
|
||||
const handleNextClick = async () => {
|
||||
const data = { ...form.state.data };
|
||||
const validationResult = schema.username.validate(data.username);
|
||||
if (validationResult.error === undefined) {
|
||||
setIsInNextStage(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleForgetPassword = async () => {
|
||||
try {
|
||||
await authentication.forgotPassword(form.state.data.username as string);
|
||||
setEmailSent(true);
|
||||
const nextData = { ...form.state.data, username: "", password: "" };
|
||||
form.setState({ data: nextData });
|
||||
} catch (ex: any) {
|
||||
form.handleGeneralError(ex);
|
||||
}
|
||||
};
|
||||
|
||||
const authenticationWorkAround = async () => {
|
||||
const data = { ...form.state.data, requestTfaRemoval: true };
|
||||
await performLogin(data);
|
||||
form.setState({ data });
|
||||
};
|
||||
|
||||
const { tfaNeeded, requestTfaRemoval } = form.state.data as any;
|
||||
const result = schema.username.validate(form.state.data.username);
|
||||
const validEmail = result.error === undefined;
|
||||
|
||||
if (authentication.getCurrentUser()) return <Navigate to="/" />;
|
||||
|
||||
const requestTfaRemovalPanel = (
|
||||
<div>
|
||||
An email has been sent to you so that you can regain control of your
|
||||
account.
|
||||
</div>
|
||||
);
|
||||
|
||||
const loginPanel = (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{renderInput(
|
||||
"username",
|
||||
"",
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
isInNextStage,
|
||||
"",
|
||||
t("Email") as string,
|
||||
0,
|
||||
true,
|
||||
"username",
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderInput(
|
||||
"password",
|
||||
"",
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.password,
|
||||
emailSent,
|
||||
"",
|
||||
t("Password") as string,
|
||||
passwordMaxLength,
|
||||
isInNextStage,
|
||||
"current-password",
|
||||
form.handleChange,
|
||||
)}
|
||||
{!isInNextStage &&
|
||||
renderButton(
|
||||
t("Next"),
|
||||
form.state.errors,
|
||||
"next",
|
||||
handleNextClick,
|
||||
"login",
|
||||
validEmail,
|
||||
ButtonType.primary,
|
||||
true,
|
||||
)}
|
||||
{isInNextStage && (
|
||||
<div className="clickables">
|
||||
{renderButton(
|
||||
"Login",
|
||||
form.state.errors,
|
||||
"login",
|
||||
undefined,
|
||||
"login",
|
||||
!emailSent,
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
{isInNextStage && (
|
||||
<div className="forgottenLink">
|
||||
{renderButton(
|
||||
t("ForgottenPassword") as string,
|
||||
form.state.errors,
|
||||
"forgot-password",
|
||||
handleForgetPassword,
|
||||
"forgot-password",
|
||||
validEmail,
|
||||
ButtonType.secondary,
|
||||
true,
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{emailSent && (
|
||||
<div className="alert alert-info emailSent">
|
||||
If you have a registered account, you will receive an email.
|
||||
</div>
|
||||
)}
|
||||
{renderError("_general", form.state.errors)}
|
||||
</>
|
||||
);
|
||||
|
||||
const tfaPanel = (
|
||||
<form onSubmit={handleSubmit}>
|
||||
{renderError("_general", form.state.errors)}
|
||||
{renderInput(
|
||||
"securityCode",
|
||||
t("Authenticate") as string,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderButton(t("Authenticate"), form.state.errors, "authenticate")}
|
||||
<Link to="#" onClick={authenticationWorkAround}>
|
||||
My Authenticator is not working
|
||||
</Link>
|
||||
</form>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{requestTfaRemoval
|
||||
? requestTfaRemovalPanel
|
||||
: tfaNeeded
|
||||
? tfaPanel
|
||||
: loginPanel}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InternalLoginForm;
|
||||
@ -1,244 +1,19 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Link, Navigate } from "react-router-dom";
|
||||
import Joi from "joi";
|
||||
import authentication from "../services/authenticationService";
|
||||
import { InputType } from "../../../components/common/Input";
|
||||
import { ButtonType } from "../../../components/common/Button";
|
||||
import { useForm } from "../../../components/common/useForm";
|
||||
import {
|
||||
renderButton,
|
||||
renderError,
|
||||
renderInput,
|
||||
} from "../../../components/common/formHelpers";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Namespaces } from "../../../i18n/i18n";
|
||||
import ExternalLoginForm from "./ExternalLoginForm";
|
||||
import InternalLoginForm from "./InternalLoginForm";
|
||||
|
||||
const LoginForm: React.FC = () => {
|
||||
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||
const [isInNextStage, setIsInNextStage] = useState(false);
|
||||
const [emailSent, setEmailSent] = useState(false);
|
||||
const passwordMaxLength = 255;
|
||||
|
||||
const form = useForm({
|
||||
loaded: true,
|
||||
data: {
|
||||
username: "",
|
||||
password: "",
|
||||
tfaNeeded: false,
|
||||
requestTfaRemoval: false,
|
||||
securityCode: "",
|
||||
},
|
||||
errors: {},
|
||||
redirect: "",
|
||||
});
|
||||
|
||||
const schema = {
|
||||
username: Joi.string()
|
||||
.required()
|
||||
.email({ tlds: { allow: false } })
|
||||
.label(t("Email")),
|
||||
password: Joi.string().required().label(t("Password")),
|
||||
tfaNeeded: Joi.boolean().required(),
|
||||
requestTfaRemoval: Joi.boolean().required(),
|
||||
securityCode: Joi.string().allow("").label(t("Authenticate")),
|
||||
};
|
||||
|
||||
form.schema = schema;
|
||||
|
||||
useEffect(() => {
|
||||
window.location.replace("/login");
|
||||
}, []);
|
||||
|
||||
const performLogin = useCallback(
|
||||
async (data: any) => {
|
||||
try {
|
||||
const result = await authentication.login(
|
||||
data.username,
|
||||
data.password,
|
||||
data.securityCode,
|
||||
data.requestTfaRemoval,
|
||||
);
|
||||
|
||||
switch (result) {
|
||||
case 1: {
|
||||
const nextData = { ...form.state.data };
|
||||
|
||||
if (!nextData.tfaNeeded) {
|
||||
nextData.tfaNeeded = true;
|
||||
form.setState({ data: nextData });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
window.location.href = "/";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (ex: any) {
|
||||
form.handleGeneralError(ex);
|
||||
}
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
const doSubmit = async () => {
|
||||
const { data } = form.state;
|
||||
await performLogin(data);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
form.handleSubmit(e, async () => doSubmit());
|
||||
};
|
||||
|
||||
const handleNextClick = async () => {
|
||||
const data = { ...form.state.data };
|
||||
const validationResult = schema.username.validate(data.username);
|
||||
if (validationResult.error === undefined) {
|
||||
setIsInNextStage(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleForgetPassword = async () => {
|
||||
try {
|
||||
await authentication.forgotPassword(form.state.data.username as string);
|
||||
setEmailSent(true);
|
||||
const nextData = { ...form.state.data, username: "", password: "" };
|
||||
form.setState({ data: nextData });
|
||||
} catch (ex: any) {
|
||||
form.handleGeneralError(ex);
|
||||
}
|
||||
};
|
||||
|
||||
const authenticationWorkAround = async () => {
|
||||
const data = { ...form.state.data, requestTfaRemoval: true };
|
||||
await performLogin(data);
|
||||
form.setState({ data });
|
||||
};
|
||||
|
||||
const { tfaNeeded, requestTfaRemoval } = form.state.data as any;
|
||||
const result = schema.username.validate(form.state.data.username);
|
||||
const validEmail = result.error === undefined;
|
||||
|
||||
if (authentication.getCurrentUser()) return <Navigate to="/" />;
|
||||
|
||||
const requestTfaRemovalPanel = (
|
||||
<div>
|
||||
An email has been sent to you so that you can regain control of your
|
||||
account.
|
||||
</div>
|
||||
);
|
||||
|
||||
const loginPanel = (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{renderInput(
|
||||
"username",
|
||||
"",
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
isInNextStage,
|
||||
"",
|
||||
t("Email") as string,
|
||||
0,
|
||||
true,
|
||||
"username",
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderInput(
|
||||
"password",
|
||||
"",
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.password,
|
||||
emailSent,
|
||||
"",
|
||||
t("Password") as string,
|
||||
passwordMaxLength,
|
||||
isInNextStage,
|
||||
"current-password",
|
||||
form.handleChange,
|
||||
)}
|
||||
{!isInNextStage &&
|
||||
renderButton(
|
||||
t("Next"),
|
||||
form.state.errors,
|
||||
"next",
|
||||
handleNextClick,
|
||||
"login",
|
||||
validEmail,
|
||||
ButtonType.primary,
|
||||
true,
|
||||
)}
|
||||
{isInNextStage && (
|
||||
<div className="clickables">
|
||||
{renderButton(
|
||||
"Login",
|
||||
form.state.errors,
|
||||
"login",
|
||||
undefined,
|
||||
"login",
|
||||
!emailSent,
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
{isInNextStage && (
|
||||
<div className="forgottenLink">
|
||||
{renderButton(
|
||||
t("ForgottenPassword") as string,
|
||||
form.state.errors,
|
||||
"forgot-password",
|
||||
handleForgetPassword,
|
||||
"forgot-password",
|
||||
validEmail,
|
||||
ButtonType.secondary,
|
||||
true,
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{emailSent && (
|
||||
<div className="alert alert-info emailSent">
|
||||
If you have a registered account, you will receive an email.
|
||||
</div>
|
||||
)}
|
||||
{renderError("_general", form.state.errors)}
|
||||
</>
|
||||
);
|
||||
|
||||
const tfaPanel = (
|
||||
<form onSubmit={handleSubmit}>
|
||||
{renderError("_general", form.state.errors)}
|
||||
{renderInput(
|
||||
"securityCode",
|
||||
t("Authenticate") as string,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderButton(t("Authenticate"), form.state.errors, "authenticate")}
|
||||
<Link to="#" onClick={authenticationWorkAround}>
|
||||
My Authenticator is not working
|
||||
</Link>
|
||||
</form>
|
||||
);
|
||||
if (window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN) {
|
||||
return (
|
||||
<>
|
||||
<ExternalLoginForm url="/account/login" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{requestTfaRemoval
|
||||
? requestTfaRemovalPanel
|
||||
: tfaNeeded
|
||||
? tfaPanel
|
||||
: loginPanel}
|
||||
</div>
|
||||
<>
|
||||
<InternalLoginForm />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,352 +1,12 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Joi from "joi";
|
||||
import { toast } from "react-toastify";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Namespaces } from "../../i18n/i18n";
|
||||
import { useForm } from "../../components/common/useForm";
|
||||
import profileService from "./services/profileService";
|
||||
import { InputType } from "../../components/common/Input";
|
||||
import { TwoFactorAuthenticationSettings } from "./models/TwoFactorAuthenticationSettings";
|
||||
import Loading from "../../components/common/Loading";
|
||||
import {
|
||||
renderError,
|
||||
renderButton,
|
||||
renderInput,
|
||||
renderToggle,
|
||||
renderDropSection,
|
||||
} from "../../components/common/formHelpers";
|
||||
import ExternalProfile from "./components/ExternalProfile";
|
||||
import InternalProfile from "./components/InternalProfile";
|
||||
|
||||
const Profile: React.FC = () => {
|
||||
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||
if (window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN) {
|
||||
return <ExternalProfile url="/account/profile" />;
|
||||
}
|
||||
|
||||
const labelFirstName = t("FirstName");
|
||||
const labelMiddleNames = t("MiddleNames");
|
||||
const labelLastName = t("LastName");
|
||||
const labelEmail = t("Email");
|
||||
const labelNewPassword = t("NewPassword");
|
||||
const labelConfirmPassword = t("ConfirmPassword");
|
||||
const labelUsingTwoFactorAuthentication = t("TwoFactorAuthentication");
|
||||
const labelTfaCode = t("AuthenticationCode");
|
||||
const labelApply = t("Save");
|
||||
|
||||
const [twoFactorAuthenticationSettings, setTwoFactorAuthenticationSettings] =
|
||||
useState<TwoFactorAuthenticationSettings>({
|
||||
manualEntrySetupCode: "",
|
||||
qrCodeImageUrl: "",
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
loaded: false,
|
||||
data: {
|
||||
firstName: "",
|
||||
middleNames: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
originalUsingTwoFactorAuthentication: false,
|
||||
usingTwoFactorAuthentication: false,
|
||||
tfaCode: "",
|
||||
},
|
||||
errors: {},
|
||||
redirect: "",
|
||||
});
|
||||
|
||||
form.schema = {
|
||||
firstName: Joi.string().required().label(labelFirstName),
|
||||
middleNames: Joi.string().allow("").required().label(labelMiddleNames),
|
||||
lastName: Joi.string().required().label(labelLastName),
|
||||
email: Joi.string()
|
||||
.required()
|
||||
.email({ tlds: { allow: false } })
|
||||
.label(labelEmail),
|
||||
newPassword: Joi.string().allow("").min(5).label(labelNewPassword),
|
||||
confirmPassword: Joi.string()
|
||||
.when("newPassword", {
|
||||
is: "",
|
||||
then: Joi.allow("").optional(),
|
||||
otherwise: Joi.valid(Joi.ref("newPassword")).error(() => {
|
||||
const e = new Error("Passwords must match");
|
||||
e.name = "confirmPassword";
|
||||
return e;
|
||||
}),
|
||||
})
|
||||
.label(labelConfirmPassword),
|
||||
originalUsingTwoFactorAuthentication: Joi.boolean().required(),
|
||||
usingTwoFactorAuthentication: Joi.boolean()
|
||||
.required()
|
||||
.label(labelUsingTwoFactorAuthentication),
|
||||
tfaCode: Joi.string()
|
||||
.when("originalUsingTwoFactorAuthentication", {
|
||||
is: Joi.ref("usingTwoFactorAuthentication"),
|
||||
then: Joi.allow("").optional(),
|
||||
otherwise: Joi.when("usingTwoFactorAuthentication", {
|
||||
is: true,
|
||||
then: Joi.string()
|
||||
.length(6)
|
||||
.required()
|
||||
.error(() => {
|
||||
const e = new Error(
|
||||
"You must enter the code from the authenicator",
|
||||
);
|
||||
e.name = "tfaCode";
|
||||
return e;
|
||||
}),
|
||||
otherwise: Joi.allow("").optional(),
|
||||
}),
|
||||
})
|
||||
.label(labelTfaCode),
|
||||
};
|
||||
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
const profile = await profileService.getMyProfile();
|
||||
if (profile) {
|
||||
const {
|
||||
firstName,
|
||||
middleNames,
|
||||
lastName,
|
||||
email,
|
||||
usingTwoFactorAuthentication,
|
||||
twoFactorAuthenticationSettings,
|
||||
} = profile;
|
||||
|
||||
const data = {
|
||||
firstName,
|
||||
middleNames,
|
||||
lastName,
|
||||
email,
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
originalUsingTwoFactorAuthentication: usingTwoFactorAuthentication,
|
||||
usingTwoFactorAuthentication,
|
||||
tfaCode: "",
|
||||
};
|
||||
form.setState({ loaded: true, data });
|
||||
setTwoFactorAuthenticationSettings(twoFactorAuthenticationSettings);
|
||||
}
|
||||
} catch (ex: any) {
|
||||
form.handleGeneralError(ex);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void loadProfile();
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const doSubmit = async (buttonName: string) => {
|
||||
try {
|
||||
const {
|
||||
firstName,
|
||||
middleNames,
|
||||
lastName,
|
||||
email,
|
||||
usingTwoFactorAuthentication,
|
||||
tfaCode,
|
||||
newPassword,
|
||||
confirmPassword,
|
||||
} = form.state.data;
|
||||
|
||||
let password = "";
|
||||
if (newPassword === confirmPassword) password = newPassword as string;
|
||||
|
||||
const response = await profileService.putMyProfile(
|
||||
firstName as string,
|
||||
middleNames as string,
|
||||
lastName as string,
|
||||
email as string,
|
||||
usingTwoFactorAuthentication as boolean,
|
||||
tfaCode as string,
|
||||
password,
|
||||
);
|
||||
if (response) {
|
||||
await loadProfile();
|
||||
toast.info(t("YourProfileSettingsHaveBeenSaved"));
|
||||
}
|
||||
} catch (ex: any) {
|
||||
form.handleGeneralError(ex);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
form.handleSubmit(e, doSubmit);
|
||||
};
|
||||
|
||||
const { loaded } = form.state;
|
||||
const { usingTwoFactorAuthentication, newPassword } = form.state.data;
|
||||
const passwordMaxLength = 255;
|
||||
|
||||
const tfaEnabled = usingTwoFactorAuthentication
|
||||
? t("Enabled")
|
||||
: t("Disabled");
|
||||
|
||||
let tfaImageBlock = null;
|
||||
if (twoFactorAuthenticationSettings)
|
||||
tfaImageBlock = (
|
||||
<React.Fragment>
|
||||
<label>{twoFactorAuthenticationSettings.manualEntrySetupCode}</label>
|
||||
<img
|
||||
src={twoFactorAuthenticationSettings.qrCodeImageUrl}
|
||||
alt={twoFactorAuthenticationSettings.manualEntrySetupCode}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const tfaSection = (
|
||||
<div>
|
||||
{renderToggle(
|
||||
"usingTwoFactorAuthentication",
|
||||
labelUsingTwoFactorAuthentication,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
form.handleToggleChange,
|
||||
)}
|
||||
{tfaImageBlock}
|
||||
{renderInput(
|
||||
"tfaCode",
|
||||
labelTfaCode,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
let passwordSection = (
|
||||
<React.Fragment>
|
||||
{renderInput(
|
||||
"newPassword",
|
||||
labelNewPassword,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.password,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
if (newPassword !== "")
|
||||
passwordSection = (
|
||||
<React.Fragment>
|
||||
{renderInput(
|
||||
"newPassword",
|
||||
labelNewPassword,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.password,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
passwordMaxLength,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderInput(
|
||||
"confirmPassword",
|
||||
labelConfirmPassword,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.password,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
passwordMaxLength,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<Loading loaded={loaded}>
|
||||
<h1>{t("Profile")}</h1>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{renderError("_general", form.state.errors)}
|
||||
{renderInput(
|
||||
"email",
|
||||
labelEmail,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
true,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderInput(
|
||||
"firstName",
|
||||
labelFirstName,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderInput(
|
||||
"middleNames",
|
||||
labelMiddleNames,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderInput(
|
||||
"lastName",
|
||||
labelLastName,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
|
||||
{passwordSection}
|
||||
|
||||
{renderDropSection(
|
||||
"turnOnTfa",
|
||||
<label>Two Factor Authentication {tfaEnabled}</label>,
|
||||
tfaSection,
|
||||
form.state.errors,
|
||||
)}
|
||||
<br />
|
||||
{renderButton(labelApply, form.state.errors, "apply")}
|
||||
</form>
|
||||
</Loading>
|
||||
);
|
||||
return <InternalProfile />;
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
|
||||
21
src/modules/profile/components/ExternalProfile.tsx
Normal file
21
src/modules/profile/components/ExternalProfile.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import HtmlIsland from "../../../components/common/HtmlIsland";
|
||||
import { Namespaces } from "../../../i18n/i18n";
|
||||
|
||||
export interface ExternalProfileProps {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const ExternalProfile: React.FC<ExternalProfileProps> = ({ url }) => {
|
||||
const { t } = useTranslation(Namespaces.Common);
|
||||
|
||||
return (
|
||||
<HtmlIsland
|
||||
url={url}
|
||||
islandId="profile"
|
||||
successMessage={t("ProfileSaved")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExternalProfile;
|
||||
352
src/modules/profile/components/InternalProfile.tsx
Normal file
352
src/modules/profile/components/InternalProfile.tsx
Normal file
@ -0,0 +1,352 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Joi from "joi";
|
||||
import { toast } from "react-toastify";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Namespaces } from "../../../i18n/i18n";
|
||||
import { useForm } from "../../../components/common/useForm";
|
||||
import profileService from "../services/profileService";
|
||||
import { InputType } from "../../../components/common/Input";
|
||||
import { TwoFactorAuthenticationSettings } from "../models/TwoFactorAuthenticationSettings";
|
||||
import Loading from "../../../components/common/Loading";
|
||||
import {
|
||||
renderError,
|
||||
renderButton,
|
||||
renderInput,
|
||||
renderToggle,
|
||||
renderDropSection,
|
||||
} from "../../../components/common/formHelpers";
|
||||
|
||||
const InternalProfile: React.FC = () => {
|
||||
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||
//Internal login version
|
||||
const labelFirstName = t("FirstName");
|
||||
const labelMiddleNames = t("MiddleNames");
|
||||
const labelLastName = t("LastName");
|
||||
const labelEmail = t("Email");
|
||||
const labelNewPassword = t("NewPassword");
|
||||
const labelConfirmPassword = t("ConfirmPassword");
|
||||
const labelUsingTwoFactorAuthentication = t("TwoFactorAuthentication");
|
||||
const labelTfaCode = t("AuthenticationCode");
|
||||
const labelApply = t("Save");
|
||||
|
||||
const [twoFactorAuthenticationSettings, setTwoFactorAuthenticationSettings] =
|
||||
useState<TwoFactorAuthenticationSettings>({
|
||||
manualEntrySetupCode: "",
|
||||
qrCodeImageUrl: "",
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
loaded: false,
|
||||
data: {
|
||||
firstName: "",
|
||||
middleNames: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
originalUsingTwoFactorAuthentication: false,
|
||||
usingTwoFactorAuthentication: false,
|
||||
tfaCode: "",
|
||||
},
|
||||
errors: {},
|
||||
redirect: "",
|
||||
});
|
||||
|
||||
form.schema = {
|
||||
firstName: Joi.string().required().label(labelFirstName),
|
||||
middleNames: Joi.string().allow("").required().label(labelMiddleNames),
|
||||
lastName: Joi.string().required().label(labelLastName),
|
||||
email: Joi.string()
|
||||
.required()
|
||||
.email({ tlds: { allow: false } })
|
||||
.label(labelEmail),
|
||||
newPassword: Joi.string().allow("").min(5).label(labelNewPassword),
|
||||
confirmPassword: Joi.string()
|
||||
.when("newPassword", {
|
||||
is: "",
|
||||
then: Joi.allow("").optional(),
|
||||
otherwise: Joi.valid(Joi.ref("newPassword")).error(() => {
|
||||
const e = new Error("Passwords must match");
|
||||
e.name = "confirmPassword";
|
||||
return e;
|
||||
}),
|
||||
})
|
||||
.label(labelConfirmPassword),
|
||||
originalUsingTwoFactorAuthentication: Joi.boolean().required(),
|
||||
usingTwoFactorAuthentication: Joi.boolean()
|
||||
.required()
|
||||
.label(labelUsingTwoFactorAuthentication),
|
||||
tfaCode: Joi.string()
|
||||
.when("originalUsingTwoFactorAuthentication", {
|
||||
is: Joi.ref("usingTwoFactorAuthentication"),
|
||||
then: Joi.allow("").optional(),
|
||||
otherwise: Joi.when("usingTwoFactorAuthentication", {
|
||||
is: true,
|
||||
then: Joi.string()
|
||||
.length(6)
|
||||
.required()
|
||||
.error(() => {
|
||||
const e = new Error(
|
||||
"You must enter the code from the authenicator",
|
||||
);
|
||||
e.name = "tfaCode";
|
||||
return e;
|
||||
}),
|
||||
otherwise: Joi.allow("").optional(),
|
||||
}),
|
||||
})
|
||||
.label(labelTfaCode),
|
||||
};
|
||||
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
const profile = await profileService.getMyProfile();
|
||||
if (profile) {
|
||||
const {
|
||||
firstName,
|
||||
middleNames,
|
||||
lastName,
|
||||
email,
|
||||
usingTwoFactorAuthentication,
|
||||
twoFactorAuthenticationSettings,
|
||||
} = profile;
|
||||
|
||||
const data = {
|
||||
firstName,
|
||||
middleNames,
|
||||
lastName,
|
||||
email,
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
originalUsingTwoFactorAuthentication: usingTwoFactorAuthentication,
|
||||
usingTwoFactorAuthentication,
|
||||
tfaCode: "",
|
||||
};
|
||||
form.setState({ loaded: true, data });
|
||||
setTwoFactorAuthenticationSettings(twoFactorAuthenticationSettings);
|
||||
}
|
||||
} catch (ex: any) {
|
||||
form.handleGeneralError(ex);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void loadProfile();
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const doSubmit = async (buttonName: string) => {
|
||||
try {
|
||||
const {
|
||||
firstName,
|
||||
middleNames,
|
||||
lastName,
|
||||
email,
|
||||
usingTwoFactorAuthentication,
|
||||
tfaCode,
|
||||
newPassword,
|
||||
confirmPassword,
|
||||
} = form.state.data;
|
||||
|
||||
let password = "";
|
||||
if (newPassword === confirmPassword) password = newPassword as string;
|
||||
|
||||
const response = await profileService.putMyProfile(
|
||||
firstName as string,
|
||||
middleNames as string,
|
||||
lastName as string,
|
||||
email as string,
|
||||
usingTwoFactorAuthentication as boolean,
|
||||
tfaCode as string,
|
||||
password,
|
||||
);
|
||||
if (response) {
|
||||
await loadProfile();
|
||||
toast.info(t("YourProfileSettingsHaveBeenSaved"));
|
||||
}
|
||||
} catch (ex: any) {
|
||||
form.handleGeneralError(ex);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
form.handleSubmit(e, doSubmit);
|
||||
};
|
||||
|
||||
const { loaded } = form.state;
|
||||
const { usingTwoFactorAuthentication, newPassword } = form.state.data;
|
||||
const passwordMaxLength = 255;
|
||||
|
||||
const tfaEnabled = usingTwoFactorAuthentication
|
||||
? t("Enabled")
|
||||
: t("Disabled");
|
||||
|
||||
let tfaImageBlock = null;
|
||||
if (twoFactorAuthenticationSettings)
|
||||
tfaImageBlock = (
|
||||
<React.Fragment>
|
||||
<label>{twoFactorAuthenticationSettings.manualEntrySetupCode}</label>
|
||||
<img
|
||||
src={twoFactorAuthenticationSettings.qrCodeImageUrl}
|
||||
alt={twoFactorAuthenticationSettings.manualEntrySetupCode}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const tfaSection = (
|
||||
<div>
|
||||
{renderToggle(
|
||||
"usingTwoFactorAuthentication",
|
||||
labelUsingTwoFactorAuthentication,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
form.handleToggleChange,
|
||||
)}
|
||||
{tfaImageBlock}
|
||||
{renderInput(
|
||||
"tfaCode",
|
||||
labelTfaCode,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
let passwordSection = (
|
||||
<React.Fragment>
|
||||
{renderInput(
|
||||
"newPassword",
|
||||
labelNewPassword,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.password,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
if (newPassword !== "")
|
||||
passwordSection = (
|
||||
<React.Fragment>
|
||||
{renderInput(
|
||||
"newPassword",
|
||||
labelNewPassword,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.password,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
passwordMaxLength,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderInput(
|
||||
"confirmPassword",
|
||||
labelConfirmPassword,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.password,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
passwordMaxLength,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<Loading loaded={loaded}>
|
||||
<h1>{t("Profile")}</h1>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{renderError("_general", form.state.errors)}
|
||||
{renderInput(
|
||||
"email",
|
||||
labelEmail,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
true,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderInput(
|
||||
"firstName",
|
||||
labelFirstName,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderInput(
|
||||
"middleNames",
|
||||
labelMiddleNames,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
{renderInput(
|
||||
"lastName",
|
||||
labelLastName,
|
||||
form.state.data,
|
||||
form.state.errors,
|
||||
InputType.text,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
undefined,
|
||||
form.handleChange,
|
||||
)}
|
||||
|
||||
{passwordSection}
|
||||
|
||||
{renderDropSection(
|
||||
"turnOnTfa",
|
||||
<label>Two Factor Authentication {tfaEnabled}</label>,
|
||||
tfaSection,
|
||||
form.state.errors,
|
||||
)}
|
||||
<br />
|
||||
{renderButton(labelApply, form.state.errors, "apply")}
|
||||
</form>
|
||||
</Loading>
|
||||
);
|
||||
};
|
||||
|
||||
export default InternalProfile;
|
||||
Loading…
Reference in New Issue
Block a user