Format Terms and Conditions accepted timestamp (#49031)

Closes #44591

Signed-off-by: Palash Thakur <palash@LAPTOP-8OJ5UPT8.localdomain>
Co-authored-by: Palash Thakur <palash@LAPTOP-8OJ5UPT8.localdomain>
This commit is contained in:
Palash Thakur 2026-05-28 01:59:43 -04:00 committed by GitHub
parent dddf24e3ed
commit 07d30b650a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 109 additions and 1 deletions

View file

@ -47,6 +47,8 @@ import { useNavigate } from "react-router-dom";
import { CopyToClipboardButton } from "../components/copy-to-clipboard-button/CopyToClipboardButton";
import { GroupResourceContext } from "../context/group-resource/GroupResourceContext";
const TERMS_AND_CONDITIONS_ATTRIBUTE = "terms_and_conditions";
export type BruteForced = {
isBruteForceProtected?: boolean;
isLocked?: boolean;
@ -86,6 +88,10 @@ export const UserForm = ({
const canViewFederationLink = hasAccess("view-realm");
const { whoAmI } = useWhoAmI();
const termsAndConditionsAcceptedDate = toTermsAndConditionsAcceptedDate(
user?.attributes?.[TERMS_AND_CONDITIONS_ATTRIBUTE],
);
const { handleSubmit, setValue, control, reset, formState } = form;
const { errors } = formState;
@ -270,6 +276,19 @@ export const UserForm = ({
label={t("emailVerified")}
labelIcon={t("emailVerifiedHelp")}
/>
{termsAndConditionsAcceptedDate && (
<FormGroup
label={t("termsAndConditionsUserAttribute")}
fieldId={TERMS_AND_CONDITIONS_ATTRIBUTE}
>
<span
id={TERMS_AND_CONDITIONS_ATTRIBUTE}
data-testid={TERMS_AND_CONDITIONS_ATTRIBUTE}
>
{formatDate(termsAndConditionsAcceptedDate)}
</span>
</FormGroup>
)}
{user?.attributes?.["kc.email.pending"] && (
<Alert
variant={AlertVariant.warning}
@ -298,7 +317,11 @@ export const UserForm = ({
...userProfileMetadata,
attributes: userProfileMetadata.attributes?.filter(
(attribute: UserProfileAttributeMetadata) => {
return attribute.name !== "kc.email.pending";
return (
attribute.name !== "kc.email.pending" &&
(attribute.name !== TERMS_AND_CONDITIONS_ATTRIBUTE ||
!termsAndConditionsAcceptedDate)
);
},
),
}}
@ -430,3 +453,15 @@ export const UserForm = ({
</FormAccess>
);
};
function toTermsAndConditionsAcceptedDate(value: unknown): Date | undefined {
const timestamp = Number(Array.isArray(value) ? value[0] : value);
if (!Number.isFinite(timestamp) || timestamp <= 0) {
return undefined;
}
const date = new Date(timestamp * 1000);
return Number.isNaN(date.getTime()) ? undefined : date;
}

View file

@ -160,6 +160,79 @@ test.describe("Existing users", () => {
await assertNotificationMessage(page, "The user has been saved");
});
test("formats Terms and Conditions accepted timestamp", async ({ page }) => {
const termsAcceptedTimestamp = "1700000000";
const email = "existing-user@example.com";
const firstName = "Existing";
const lastName = "User";
await using testBed = await createTestBed({
attributes: {
userProfileEnabled: "true",
},
requiredActions: [
{
alias: "TERMS_AND_CONDITIONS",
name: "Terms and Conditions",
providerId: "TERMS_AND_CONDITIONS",
enabled: true,
defaultAction: false,
priority: 20,
config: {},
},
],
users: [
{
username: existingUserName,
email,
firstName,
lastName,
attributes: {
terms_and_conditions: [termsAcceptedTimestamp],
},
},
],
});
const user = await adminClient.findUserByUsername(
testBed.realm,
existingUserName,
);
await login(page, {
to: toUser({ realm: testBed.realm, id: user.id!, tab: "settings" }),
});
const termsAcceptedField = page.getByTestId("terms_and_conditions");
await expect(termsAcceptedField).toBeVisible();
await expect(termsAcceptedField).not.toHaveText(termsAcceptedTimestamp);
await expect(
page.getByText(termsAcceptedTimestamp, { exact: true }),
).toHaveCount(0);
await expect(
page.locator(`input[value="${termsAcceptedTimestamp}"]`),
).toHaveCount(0);
const displayedTimestamp = await termsAcceptedField.innerText();
expect(displayedTimestamp).toContain("2023");
expect(displayedTimestamp).toMatch(/\D/);
expect(displayedTimestamp).toMatch(/\d{1,2}:\d{2}/);
await expect(page.getByTestId("email")).toHaveValue(email);
await expect(page.getByTestId("firstName")).toHaveValue(firstName);
await expect(page.getByTestId("lastName")).toHaveValue(lastName);
await clickSaveButton(page);
await assertNotificationMessage(page, "The user has been saved");
const updatedUser = await adminClient.findUserByUsername(
testBed.realm,
existingUserName,
);
expect(updatedUser.attributes?.terms_and_conditions).toEqual([
termsAcceptedTimestamp,
]);
});
test("shows validation error for empty required attribute", async ({
page,
}) => {