From 8d6461b01d4afbbd39ba9adadbcd0f532381fe1f Mon Sep 17 00:00:00 2001 From: x032205 Date: Wed, 27 Aug 2025 04:47:21 -0400 Subject: [PATCH] - Swap to using ms in some frontend areas - Rename button from "Clear All Lockouts" to "Reset All Lockouts" - Add a tooltip to the red lock icon on auth row - Make the red lock icon go away after resetting all lockouts --- frontend/src/helpers/datetime.ts | 23 ------------------- .../IdentityUniversalAuthForm.tsx | 22 +++++++----------- .../IdentityDetailsByIDPage.tsx | 1 + .../IdentityAuthenticationSection.tsx | 11 +++++---- .../ViewIdentityAuthModal.tsx | 14 ++++++++--- .../ViewIdentityUniversalAuthContent.tsx | 6 +++-- .../ViewIdentityAuthModal/types/index.ts | 1 + .../OrgSecretShareLimitSection.tsx | 11 ++++----- 8 files changed, 37 insertions(+), 52 deletions(-) diff --git a/frontend/src/helpers/datetime.ts b/frontend/src/helpers/datetime.ts index 3dd57cf52..d8a5e90c6 100644 --- a/frontend/src/helpers/datetime.ts +++ b/frontend/src/helpers/datetime.ts @@ -23,29 +23,6 @@ export const formatDateTime = ({ return format(date, dateFormat); }; -// Helper function to convert duration to seconds -export const durationToSeconds = ( - value: number, - unit: "s" | "m" | "h" | "d" | "w" | "y" -): number => { - switch (unit) { - case "s": - return value; - case "m": - return value * 60; - case "h": - return value * 60 * 60; - case "d": - return value * 60 * 60 * 24; - case "w": - return value * 60 * 60 * 24 * 7; - case "y": - return value * 60 * 60 * 24 * 365; - default: - return 0; - } -}; - // Helper function to convert seconds to value and unit export const getObjectFromSeconds = ( totalSeconds: number, diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx index 9681461e6..3fb7a3acc 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx @@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form"; import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; +import ms from "ms"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; @@ -20,7 +21,7 @@ import { Tabs } from "@app/components/v2"; import { useOrganization, useSubscription } from "@app/context"; -import { durationToSeconds, getObjectFromSeconds } from "@app/helpers/datetime"; +import { getObjectFromSeconds } from "@app/helpers/datetime"; import { useAddIdentityUniversalAuth, useGetIdentityUniversalAuth, @@ -117,11 +118,9 @@ const schema = z if (isAnyParseError) return; - const lockoutDurationInSeconds = durationToSeconds(parsedLockoutDuration, lockoutDurationUnit); - const lockoutCounterResetInSeconds = durationToSeconds( - parsedLockoutCounterReset, - lockoutCounterResetUnit - ); + const lockoutDurationInSeconds = ms(`${parsedLockoutDuration}${lockoutDurationUnit}`) / 1000; + const lockoutCounterResetInSeconds = + ms(`${parsedLockoutCounterReset}${lockoutCounterResetUnit}`) / 1000; if (lockoutDurationInSeconds > 86400 || lockoutDurationInSeconds < 30) { ctx.addIssue({ @@ -279,14 +278,9 @@ export const IdentityUniversalAuthForm = ({ try { if (!identityId) return; - const lockoutDurationSeconds = durationToSeconds( - Number(lockoutDurationValue), - lockoutDurationUnit - ); - const lockoutCounterResetSeconds = durationToSeconds( - Number(lockoutCounterResetValue), - lockoutCounterResetUnit - ); + const lockoutDurationSeconds = ms(`${lockoutDurationValue}${lockoutDurationUnit}`) / 1000; + const lockoutCounterResetSeconds = + ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000; if (data) { // update universal auth configuration diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index 1b7cb956b..446e54885 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -118,6 +118,7 @@ const Page = () => { authMethod={popUp.viewAuthMethod.data?.authMethod} lockedOut={popUp.viewAuthMethod.data?.lockedOut || false} identityId={identityId} + onResetAllLockouts={popUp.viewAuthMethod.data?.refetchIdentity} /> ); diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx index 61268070b..a1051afb8 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx @@ -2,7 +2,7 @@ import { faCog, faLock, faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { OrgPermissionCan } from "@app/components/permissions"; -import { Button } from "@app/components/v2"; +import { Button, Tooltip } from "@app/components/v2"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context"; import { IdentityAuthMethod, identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -16,7 +16,7 @@ type Props = { }; export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: Props) => { - const { data } = useGetIdentityById(identityId); + const { data, refetch } = useGetIdentityById(identityId); return data ? (
@@ -31,7 +31,8 @@ export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: P onClick={() => handlePopUpOpen("viewAuthMethod", { authMethod, - lockedOut: data.identity.activeLockoutAuthMethods.includes(authMethod) + lockedOut: data.identity.activeLockoutAuthMethods.includes(authMethod), + refetchIdentity: refetch }) } type="button" @@ -40,7 +41,9 @@ export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: P {identityAuthToNameMap[authMethod]}
{data.identity.activeLockoutAuthMethods.includes(authMethod) && ( - + + + )}
diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx index 7236d4c27..f95a20789 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx @@ -41,6 +41,7 @@ type Props = { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; onDeleteAuthMethod: () => void; + onResetAllLockouts: () => void; }; type TRevokeOptions = { @@ -52,8 +53,12 @@ export const Content = ({ identityId, authMethod, lockedOut, - onDeleteAuthMethod -}: Pick) => { + onDeleteAuthMethod, + onResetAllLockouts +}: Pick< + Props, + "authMethod" | "lockedOut" | "identityId" | "onDeleteAuthMethod" | "onResetAllLockouts" +>) => { const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; @@ -161,6 +166,7 @@ export const Content = ({ ) => { if (!identityId || !authMethod) return null; @@ -200,6 +207,7 @@ export const ViewIdentityAuthModal = ({ authMethod={authMethod} lockedOut={lockedOut} onDeleteAuthMethod={() => onOpenChange(false)} + onResetAllLockouts={() => onResetAllLockouts()} /> diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx index 70f7e6948..a9cb5a1d1 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx @@ -26,7 +26,8 @@ export const ViewIdentityUniversalAuthContent = ({ handlePopUpOpen, onDelete, popUp, - lockedOut + lockedOut, + onResetAllLockouts }: ViewAuthMethodProps) => { const { data, isPending } = useGetIdentityUniversalAuth(identityId); const { data: clientSecrets = [], isPending: clientSecretsPending } = @@ -48,6 +49,7 @@ export const ViewIdentityUniversalAuthContent = ({ type: "success" }); setLockedOutState(false); + onResetAllLockouts(); } catch (error) { console.error(error); createNotification({ @@ -124,7 +126,7 @@ export const ViewIdentityUniversalAuthContent = ({ isLoading={isClearLockoutsPending} colorSchema="secondary" > - Clear All Lockouts + Reset All Lockouts )} diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/types/index.ts b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/types/index.ts index c566040b4..da31a233c 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/types/index.ts +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/types/index.ts @@ -10,4 +10,5 @@ export type ViewAuthMethodProps = { ) => void; popUp: UsePopUpState<["revokeAuthMethod", "upgradePlan", "identityAuthMethod"]>; lockedOut: boolean; + onResetAllLockouts: () => void; }; diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/components/OrgSecretShareLimitSection/OrgSecretShareLimitSection.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/components/OrgSecretShareLimitSection/OrgSecretShareLimitSection.tsx index b3e4976f3..cc90cd43f 100644 --- a/frontend/src/pages/organization/SecretSharingSettingsPage/components/OrgSecretShareLimitSection/OrgSecretShareLimitSection.tsx +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/components/OrgSecretShareLimitSection/OrgSecretShareLimitSection.tsx @@ -1,13 +1,14 @@ import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; +import ms from "ms"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; -import { durationToSeconds, getObjectFromSeconds } from "@app/helpers/datetime"; +import { getObjectFromSeconds } from "@app/helpers/datetime"; import { useUpdateOrg } from "@app/hooks/api"; const MAX_SHARED_SECRET_LIFETIME_SECONDS = 30 * 24 * 60 * 60; // 30 days in seconds @@ -25,7 +26,7 @@ const formSchema = z .superRefine((data, ctx) => { const { maxLifetimeValue, maxLifetimeUnit } = data; - const durationInSeconds = durationToSeconds(maxLifetimeValue, maxLifetimeUnit); + const durationInSeconds = ms(`${maxLifetimeValue}${maxLifetimeUnit}`) / 1000; if (durationInSeconds > MAX_SHARED_SECRET_LIFETIME_SECONDS) { ctx.addIssue({ @@ -90,10 +91,8 @@ export const OrgSecretShareLimitSection = () => { const handleFormSubmit = async (formData: TForm) => { try { - const maxSharedSecretLifetimeSeconds = durationToSeconds( - formData.maxLifetimeValue, - formData.maxLifetimeUnit - ); + const maxSharedSecretLifetimeSeconds = + ms(`${formData.maxLifetimeValue}${formData.maxLifetimeUnit}`) / 1000; await mutateAsync({ orgId: currentOrg.id,