keycloak-scim/js/apps/admin-ui/src/realm-settings/EmailTab.tsx
Erik Jan de Wit 89abc094d1
userprofile shared (#23600)
* move account ui user profile to shared

* use ui-shared on admin same error handling

also introduce optional renderer for added component

* move scroll form to ui-shared

* merged with main

* fix lock file

* fixed merge error

* fixed merge errors

* fixed tests

* moved user profile types to admin client

* fixed more types

* pr comments

* fixed some types
2023-11-14 08:04:55 -03:00

397 lines
13 KiB
TypeScript

import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import {
ActionGroup,
ActionListItem,
Alert,
AlertActionLink,
AlertVariant,
Button,
Checkbox,
FormGroup,
PageSection,
Switch,
} from "@patternfly/react-core";
import { Controller, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { FormPanel, HelpItem } from "ui-shared";
import { adminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { FormAccess } from "../components/form/FormAccess";
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
import { PasswordInput } from "../components/password-input/PasswordInput";
import { useRealm } from "../context/realm-context/RealmContext";
import { toUser } from "../user/routes/User";
import { emailRegexPattern } from "../util";
import { useCurrentUser } from "../utils/useCurrentUser";
import useToggle from "../utils/useToggle";
import "./realm-settings-section.css";
type RealmSettingsEmailTabProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void;
};
type FormFields = Omit<RealmRepresentation, "users" | "federatedUsers">;
export const RealmSettingsEmailTab = ({
realm,
save,
}: RealmSettingsEmailTabProps) => {
const { t } = useTranslation();
const { realm: realmName } = useRealm();
const { addAlert, addError } = useAlerts();
const currentUser = useCurrentUser();
const {
register,
control,
handleSubmit,
watch,
reset: resetForm,
getValues,
formState: { errors },
} = useForm<FormFields>({ defaultValues: realm });
const reset = () => resetForm(realm);
const watchFromValue = watch("smtpServer.from", "");
const watchHostValue = watch("smtpServer.host", "");
const [isTesting, toggleTest] = useToggle();
const authenticationEnabled = useWatch({
control,
name: "smtpServer.auth",
defaultValue: "",
});
const testConnection = async () => {
const toNumber = (value: string) => Number(value);
const toBoolean = (value: string) => value === true.toString();
const valueMapper = new Map<string, (value: string) => unknown>([
["port", toNumber],
["ssl", toBoolean],
["starttls", toBoolean],
["auth", toBoolean],
]);
const serverSettings = { ...getValues()["smtpServer"] };
for (const [key, mapperFn] of valueMapper.entries()) {
serverSettings[key] = mapperFn(serverSettings[key]);
}
// For default value, back end is expecting null instead of 0
if (serverSettings.port === 0) serverSettings.port = null;
try {
toggleTest();
await adminClient.realms.testSMTPConnection(
{ realm: realm.realm! },
serverSettings,
);
addAlert(t("testConnectionSuccess"), AlertVariant.success);
} catch (error) {
addError("testConnectionError", error);
}
toggleTest();
};
return (
<PageSection variant="light">
<FormPanel title={t("template")} className="kc-email-template">
<FormAccess
isHorizontal
role="manage-realm"
className="pf-u-mt-lg"
onSubmit={handleSubmit(save)}
>
<FormGroup
label={t("from")}
fieldId="kc-display-name"
isRequired
validated={errors.smtpServer?.from ? "error" : "default"}
helperTextInvalid={t("emailInvalid")}
>
<KeycloakTextInput
type="email"
id="kc-sender-email-address"
data-testid="sender-email-address"
placeholder="Sender email address"
validated={errors.smtpServer?.from ? "error" : "default"}
{...register("smtpServer.from", {
pattern: emailRegexPattern,
required: true,
})}
/>
</FormGroup>
<FormGroup
label={t("fromDisplayName")}
fieldId="kc-from-display-name"
labelIcon={
<HelpItem
helpText={t("fromDisplayNameHelp")}
fieldLabelId="authentication"
/>
}
>
<KeycloakTextInput
id="kc-from-display-name"
data-testid="from-display-name"
placeholder="Display name for Sender email address"
{...register("smtpServer.fromDisplayName")}
/>
</FormGroup>
<FormGroup
label={t("replyTo")}
fieldId="kc-reply-to"
validated={errors.smtpServer?.replyTo ? "error" : "default"}
helperTextInvalid={t("emailInvalid")}
>
<KeycloakTextInput
type="email"
id="kc-reply-to"
placeholder="Reply to email address"
validated={errors.smtpServer?.replyTo ? "error" : "default"}
{...register("smtpServer.replyTo", {
pattern: emailRegexPattern,
})}
/>
</FormGroup>
<FormGroup
label={t("replyToDisplayName")}
fieldId="kc-reply-to-display-name"
labelIcon={
<HelpItem
helpText={t("replyToDisplayNameHelp")}
fieldLabelId="replyToDisplayName"
/>
}
>
<KeycloakTextInput
id="kc-reply-to-display-name"
placeholder='Display name for "reply to" email address'
{...register("smtpServer.replyToDisplayName")}
/>
</FormGroup>
<FormGroup
label={t("envelopeFrom")}
fieldId="kc-envelope-from"
labelIcon={
<HelpItem
helpText={t("envelopeFromHelp")}
fieldLabelId="envelopeFrom"
/>
}
>
<KeycloakTextInput
id="kc-envelope-from"
placeholder="Sender envelope email address"
{...register("smtpServer.envelopeFrom")}
/>
</FormGroup>
</FormAccess>
</FormPanel>
<FormPanel
className="kc-email-connection"
title={t("connectionAndAuthentication")}
>
<FormAccess
isHorizontal
role="manage-realm"
className="pf-u-mt-lg"
onSubmit={handleSubmit(save)}
>
<FormGroup
label={t("host")}
fieldId="kc-host"
isRequired
validated={errors.smtpServer?.host ? "error" : "default"}
helperTextInvalid={t("required")}
>
<KeycloakTextInput
id="kc-host"
placeholder="SMTP host"
validated={errors.smtpServer?.host ? "error" : "default"}
{...register("smtpServer.host", { required: true })}
/>
</FormGroup>
<FormGroup label={t("port")} fieldId="kc-port">
<KeycloakTextInput
id="kc-port"
placeholder="SMTP port (defaults to 25)"
{...register("smtpServer.port")}
/>
</FormGroup>
<FormGroup label={t("encryption")} fieldId="kc-html-display-name">
<Controller
name="smtpServer.ssl"
control={control}
defaultValue="false"
render={({ field }) => (
<Checkbox
id="kc-enable-ssl"
data-testid="enable-ssl"
label={t("enableSSL")}
isChecked={field.value === "true"}
onChange={(value) => field.onChange("" + value)}
/>
)}
/>
<Controller
name="smtpServer.starttls"
control={control}
defaultValue="false"
render={({ field }) => (
<Checkbox
id="kc-enable-start-tls"
data-testid="enable-start-tls"
label={t("enableStartTLS")}
isChecked={field.value === "true"}
onChange={(value) => field.onChange("" + value)}
/>
)}
/>
</FormGroup>
<FormGroup
hasNoPaddingTop
label={t("authentication")}
fieldId="kc-authentication"
>
<Controller
name="smtpServer.auth"
control={control}
defaultValue=""
render={({ field }) => (
<Switch
id="kc-authentication-switch"
data-testid="email-authentication-switch"
label={t("enabled")}
labelOff={t("disabled")}
isChecked={field.value === "true"}
onChange={(value) => {
field.onChange("" + value);
}}
aria-label={t("authentication")}
/>
)}
/>
</FormGroup>
{authenticationEnabled === "true" && (
<>
<FormGroup
label={t("username")}
fieldId="kc-username"
isRequired
validated={errors.smtpServer?.user ? "error" : "default"}
helperTextInvalid={t("required")}
>
<KeycloakTextInput
id="kc-username"
data-testid="username-input"
placeholder="Login username"
validated={errors.smtpServer?.user ? "error" : "default"}
{...register("smtpServer.user", { required: true })}
/>
</FormGroup>
<FormGroup
label={t("password")}
fieldId="kc-username"
isRequired
validated={errors.smtpServer?.password ? "error" : "default"}
helperTextInvalid={t("required")}
labelIcon={
<HelpItem
helpText={t("passwordHelp")}
fieldLabelId="password"
/>
}
>
<PasswordInput
id="kc-password"
data-testid="password-input"
aria-label={t("password")}
validated={errors.smtpServer?.password ? "error" : "default"}
{...register("smtpServer.password", { required: true })}
/>
</FormGroup>
</>
)}
{currentUser && (
<FormGroup id="descriptionTestConnection">
{currentUser.email ? (
<Alert
variant="info"
isInline
title={t("testConnectionHint.withEmail", {
email: currentUser.email,
})}
/>
) : (
<Alert
variant="warning"
component="h2"
isInline
title={t("testConnectionHint.withoutEmail", {
userName: currentUser.username,
})}
actionLinks={
<AlertActionLink
component={(props) => (
<Link
{...props}
to={toUser({
realm: realmName,
id: currentUser.id!,
tab: "settings",
})}
/>
)}
>
{t("testConnectionHint.withoutEmailAction")}
</AlertActionLink>
}
/>
)}
</FormGroup>
)}
<ActionGroup>
<ActionListItem>
<Button
variant="primary"
type="submit"
data-testid="email-tab-save"
>
{t("save")}
</Button>
</ActionListItem>
<ActionListItem>
<Button
variant="secondary"
onClick={() => testConnection()}
data-testid="test-connection-button"
isDisabled={
!(emailRegexPattern.test(watchFromValue) && watchHostValue) ||
!currentUser?.email
}
aria-describedby="descriptionTestConnection"
isLoading={isTesting}
spinnerAriaValueText={t("testingConnection")}
>
{t("testConnection")}
</Button>
</ActionListItem>
<ActionListItem>
<Button
variant="link"
onClick={reset}
data-testid="email-tab-revert"
>
{t("revert")}
</Button>
</ActionListItem>
</ActionGroup>
</FormAccess>
</FormPanel>
</PageSection>
);
};