UI polish: Add better time inputs and tooltips

This commit is contained in:
x032205
2025-08-19 15:24:20 +08:00
parent 5136dbc543
commit ebd3b5c9d1
4 changed files with 353 additions and 158 deletions
+86
View File
@@ -22,3 +22,89 @@ export const formatDateTime = ({
} }
return format(date, dateFormat); 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,
activeUnits?: Array<"s" | "m" | "h" | "d" | "w" | "y">
): { value: number; unit: "s" | "m" | "h" | "d" | "w" | "y" } => {
const SECONDS_IN_MINUTE = 60;
const SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60;
const SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
const SECONDS_IN_WEEK = SECONDS_IN_DAY * 7;
const SECONDS_IN_YEAR = SECONDS_IN_DAY * 365;
const activeUnitsSet = activeUnits ? new Set(activeUnits) : null;
const isUnitActive = (unit: "s" | "m" | "h" | "d" | "w" | "y"): boolean => {
return activeUnitsSet ? activeUnitsSet.has(unit) : true;
};
if (
isUnitActive("y") &&
totalSeconds >= SECONDS_IN_YEAR &&
totalSeconds % SECONDS_IN_YEAR === 0
) {
return { value: totalSeconds / SECONDS_IN_YEAR, unit: "y" };
}
if (
isUnitActive("w") &&
totalSeconds >= SECONDS_IN_WEEK &&
totalSeconds % SECONDS_IN_WEEK === 0
) {
return { value: totalSeconds / SECONDS_IN_WEEK, unit: "w" };
}
if (isUnitActive("d") && totalSeconds >= SECONDS_IN_DAY && totalSeconds % SECONDS_IN_DAY === 0) {
return { value: totalSeconds / SECONDS_IN_DAY, unit: "d" };
}
if (
isUnitActive("h") &&
totalSeconds >= SECONDS_IN_HOUR &&
totalSeconds % SECONDS_IN_HOUR === 0
) {
return { value: totalSeconds / SECONDS_IN_HOUR, unit: "h" };
}
if (
isUnitActive("m") &&
totalSeconds >= SECONDS_IN_MINUTE &&
totalSeconds % SECONDS_IN_MINUTE === 0
) {
return { value: totalSeconds / SECONDS_IN_MINUTE, unit: "m" };
}
if (isUnitActive("s") && totalSeconds >= 1) {
return { value: totalSeconds, unit: "s" };
}
return {
value: 0,
unit: "s"
};
};
@@ -11,6 +11,8 @@ import {
FormControl, FormControl,
IconButton, IconButton,
Input, Input,
Select,
SelectItem,
Switch, Switch,
Tab, Tab,
TabList, TabList,
@@ -27,6 +29,7 @@ import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types"; import { IdentityFormTab } from "./types";
import { durationToSeconds, getObjectFromSeconds } from "@app/helpers/datetime";
const schema = z const schema = z
.object({ .object({
@@ -69,20 +72,73 @@ const schema = z
(value) => Number(value) <= 30 && Number(value) >= 1, (value) => Number(value) <= 30 && Number(value) >= 1,
"Lockout threshold must be between 1 and 30" "Lockout threshold must be between 1 and 30"
), ),
lockoutDuration: z lockoutDurationValue: z.string(),
.string() lockoutDurationUnit: z.enum(["s", "m", "h", "d"], {
.refine( invalid_type_error: "Please select a valid time unit"
(value) => Number(value) <= 86400 && Number(value) >= 30, }),
"Lockout duration must be between 30 seconds and 1 day" lockoutCounterResetValue: z.string(),
), lockoutCounterResetUnit: z.enum(["s", "m", "h"], {
lockoutCounterReset: z invalid_type_error: "Please select a valid time unit"
.string() })
.refine(
(value) => Number(value) <= 3600 && Number(value) >= 5,
"Lockout counter reset must be between 5 seconds and 1 hour"
)
}) })
.required(); .required()
.superRefine((data, ctx) => {
const {
lockoutDurationValue,
lockoutCounterResetValue,
lockoutDurationUnit,
lockoutCounterResetUnit,
lockoutEnabled
} = data;
if (!lockoutEnabled) return;
let isAnyParseError = false;
const parsedLockoutDuration = parseInt(lockoutDurationValue, 10);
if (isNaN(parsedLockoutDuration)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Lockout duration must be a number",
path: ["lockoutDurationValue"]
});
isAnyParseError = true;
}
const parsedLockoutCounterReset = parseInt(lockoutCounterResetValue, 10);
if (isNaN(parsedLockoutCounterReset)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Lockout counter reset must be a number",
path: ["lockoutCounterResetValue"]
});
isAnyParseError = true;
}
if (isAnyParseError) return;
const lockoutDurationInSeconds = durationToSeconds(parsedLockoutDuration, lockoutDurationUnit);
const lockoutCounterResetInSeconds = durationToSeconds(
parsedLockoutCounterReset,
lockoutCounterResetUnit
);
if (lockoutDurationInSeconds > 86400 || lockoutDurationInSeconds < 30) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Lockout duration must be between 30 seconds and 1 day",
path: ["lockoutDurationValue"]
});
}
if (lockoutCounterResetInSeconds > 3600 || lockoutCounterResetInSeconds < 5) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Lockout counter reset must be between 5 seconds and 1 hour",
path: ["lockoutCounterResetValue"]
});
}
});
export type FormData = z.infer<typeof schema>; export type FormData = z.infer<typeof schema>;
@@ -130,8 +186,10 @@ export const IdentityUniversalAuthForm = ({
accessTokenPeriod: "0", accessTokenPeriod: "0",
lockoutEnabled: true, lockoutEnabled: true,
lockoutThreshold: "3", lockoutThreshold: "3",
lockoutDuration: "300", lockoutDurationValue: "5",
lockoutCounterReset: "30" lockoutDurationUnit: "m",
lockoutCounterResetValue: "30",
lockoutCounterResetUnit: "s"
} }
}); });
@@ -139,8 +197,10 @@ export const IdentityUniversalAuthForm = ({
const lockoutEnabled = watch("lockoutEnabled"); const lockoutEnabled = watch("lockoutEnabled");
const lockoutThreshold = watch("lockoutThreshold"); const lockoutThreshold = watch("lockoutThreshold");
const lockoutDuration = watch("lockoutDuration"); const lockoutDurationValue = watch("lockoutDurationValue");
const lockoutCounterReset = watch("lockoutCounterReset"); const lockoutDurationUnit = watch("lockoutDurationUnit");
const lockoutCounterResetValue = watch("lockoutCounterResetValue");
const lockoutCounterResetUnit = watch("lockoutCounterResetUnit");
const { const {
fields: clientSecretTrustedIpsFields, fields: clientSecretTrustedIpsFields,
@@ -155,6 +215,9 @@ export const IdentityUniversalAuthForm = ({
useEffect(() => { useEffect(() => {
if (data) { if (data) {
const lockoutDurationObj = getObjectFromSeconds(data.lockoutDuration);
const lockoutCounterResetObj = getObjectFromSeconds(data.lockoutCounterReset);
reset({ reset({
accessTokenTTL: String(data.accessTokenTTL), accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL), accessTokenMaxTTL: String(data.accessTokenMaxTTL),
@@ -176,8 +239,10 @@ export const IdentityUniversalAuthForm = ({
), ),
lockoutEnabled: data.lockoutEnabled, lockoutEnabled: data.lockoutEnabled,
lockoutThreshold: String(data.lockoutThreshold), lockoutThreshold: String(data.lockoutThreshold),
lockoutDuration: String(data.lockoutDuration), lockoutDurationValue: String(lockoutDurationObj.value),
lockoutCounterReset: String(data.lockoutCounterReset) lockoutDurationUnit: lockoutDurationObj.unit as "s" | "m" | "h" | "d",
lockoutCounterResetValue: String(lockoutCounterResetObj.value),
lockoutCounterResetUnit: lockoutCounterResetObj.unit as "s" | "m" | "h"
}); });
} else { } else {
reset({ reset({
@@ -189,8 +254,10 @@ export const IdentityUniversalAuthForm = ({
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }],
lockoutEnabled: true, lockoutEnabled: true,
lockoutThreshold: "3", lockoutThreshold: "3",
lockoutDuration: "300", lockoutDurationValue: "5",
lockoutCounterReset: "30" lockoutDurationUnit: "m",
lockoutCounterResetValue: "30",
lockoutCounterResetUnit: "s"
}); });
} }
}, [data]); }, [data]);
@@ -204,12 +271,20 @@ export const IdentityUniversalAuthForm = ({
accessTokenPeriod, accessTokenPeriod,
lockoutEnabled, lockoutEnabled,
lockoutThreshold, lockoutThreshold,
lockoutDuration, lockoutDurationValue,
lockoutCounterReset lockoutDurationUnit,
lockoutCounterResetValue,
lockoutCounterResetUnit
}: FormData) => { }: FormData) => {
try { try {
if (!identityId) return; if (!identityId) return;
const lockoutDuration = durationToSeconds(Number(lockoutDurationValue), lockoutDurationUnit);
const lockoutCounterReset = durationToSeconds(
Number(lockoutCounterResetValue),
lockoutCounterResetUnit
);
if (data) { if (data) {
// update universal auth configuration // update universal auth configuration
await updateMutateAsync({ await updateMutateAsync({
@@ -223,8 +298,8 @@ export const IdentityUniversalAuthForm = ({
accessTokenPeriod: Number(accessTokenPeriod), accessTokenPeriod: Number(accessTokenPeriod),
lockoutEnabled, lockoutEnabled,
lockoutThreshold: Number(lockoutThreshold), lockoutThreshold: Number(lockoutThreshold),
lockoutDuration: Number(lockoutDuration), lockoutDuration,
lockoutCounterReset: Number(lockoutCounterReset) lockoutCounterReset
}); });
} else { } else {
// create new universal auth configuration // create new universal auth configuration
@@ -272,8 +347,10 @@ export const IdentityUniversalAuthForm = ({
: [ : [
"lockoutEnabled", "lockoutEnabled",
"lockoutThreshold", "lockoutThreshold",
"lockoutDuration", "lockoutDurationValue",
"lockoutCounterReset" "lockoutDurationUnit",
"lockoutCounterResetValue",
"lockoutCounterResetUnit"
].includes(Object.keys(fields)[0]) ].includes(Object.keys(fields)[0])
? IdentityFormTab.Lockout ? IdentityFormTab.Lockout
: IdentityFormTab.Configuration : IdentityFormTab.Configuration
@@ -362,7 +439,7 @@ export const IdentityUniversalAuthForm = ({
render={({ field: { value, onChange }, fieldState: { error } }) => { render={({ field: { value, onChange }, fieldState: { error } }) => {
return ( return (
<FormControl <FormControl
helperText={`The lockout feature will prevent login attempts for ${lockoutDuration || 300} seconds after ${lockoutThreshold || 3} consecutive login failures. If ${lockoutCounterReset || 30} seconds pass after the most recent failure, the lockout counter resets.`} helperText={`The lockout feature will prevent login attempts for ${lockoutDurationValue}${lockoutDurationUnit} after ${lockoutThreshold} consecutive login failures. If ${lockoutCounterResetValue}${lockoutCounterResetUnit} pass after the most recent failure, the lockout counter resets.`}
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
> >
@@ -380,57 +457,157 @@ export const IdentityUniversalAuthForm = ({
); );
}} }}
/> />
<Controller <div className="flex flex-col gap-2">
control={control} <Controller
name="lockoutThreshold" control={control}
render={({ field, fieldState: { error } }) => { name="lockoutThreshold"
return ( render={({ field, fieldState: { error } }) => {
<FormControl return (
className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`} <FormControl
label="Lockout Threshold" className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`}
isError={Boolean(error)} label="Lockout Threshold"
errorText={error?.message} isError={Boolean(error)}
tooltipText="The amount of times login must fail before locking the identity auth method" errorText={error?.message}
> tooltipText="The amount of times login must fail before locking the identity auth method"
<Input {...field} placeholder="3" isDisabled={!lockoutEnabled} /> >
</FormControl> <Input
); {...field}
}} placeholder="Enter lockout threshold..."
/> isDisabled={!lockoutEnabled}
<Controller />
control={control} </FormControl>
name="lockoutDuration" );
render={({ field, fieldState: { error } }) => { }}
return ( />
<FormControl <div className="flex items-end gap-2">
className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`} <Controller
label="Lockout Duration (seconds)" control={control}
isError={Boolean(error)} name="lockoutDurationValue"
errorText={error?.message} render={({ field, fieldState: { error } }) => {
tooltipText="How long an identity auth method lockout lasts" return (
> <FormControl
<Input {...field} placeholder="300" isDisabled={!lockoutEnabled} /> className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`}
</FormControl> label="Lockout Duration"
); isError={Boolean(error)}
}} errorText={error?.message}
/> tooltipText="How long an identity auth method lockout lasts"
<Controller >
control={control} <Input
name="lockoutCounterReset" {...field}
render={({ field, fieldState: { error } }) => { placeholder="Enter lockout duration..."
return ( isDisabled={!lockoutEnabled}
<FormControl />
className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`} </FormControl>
label="Lockout Counter Reset (seconds)" );
isError={Boolean(error)} }}
errorText={error?.message} />
tooltipText="How long to wait from the most recent failed login until resetting the lockout counter" <Controller
> control={control}
<Input {...field} placeholder="30" isDisabled={!lockoutEnabled} /> name="lockoutDurationUnit"
</FormControl> render={({ field, fieldState: { error } }) => (
); <FormControl
}} className={`mb-0 ${lockoutEnabled ? "" : "opacity-70"}`}
/> isError={Boolean(error)}
errorText={error?.message}
>
<Select
isDisabled={!lockoutEnabled}
value={field.value}
className="min-w-32 pr-2"
onValueChange={field.onChange}
position="popper"
>
<SelectItem
value="s"
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
>
<div className="ml-3 font-medium">Seconds</div>
</SelectItem>
<SelectItem
value="m"
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
>
<div className="ml-3 font-medium">Minutes</div>
</SelectItem>
<SelectItem
value="h"
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
>
<div className="ml-3 font-medium">Hours</div>
</SelectItem>
<SelectItem
value="d"
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
>
<div className="ml-3 font-medium">Days</div>
</SelectItem>
</Select>
</FormControl>
)}
/>
</div>
<div className="flex items-end gap-2">
<Controller
control={control}
name="lockoutCounterResetValue"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`}
label="Lockout Counter Reset"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="How long to wait from the most recent failed login until resetting the lockout counter"
>
<Input
{...field}
placeholder="Enter lockout counter reset..."
isDisabled={!lockoutEnabled}
/>
</FormControl>
);
}}
/>
<Controller
control={control}
name="lockoutCounterResetUnit"
render={({ field, fieldState: { error } }) => (
<FormControl
className={`mb-0 ${lockoutEnabled ? "" : "opacity-70"}`}
isError={Boolean(error)}
errorText={error?.message}
>
<Select
isDisabled={!lockoutEnabled}
value={field.value}
className="min-w-32 pr-2"
onValueChange={field.onChange}
position="popper"
>
<SelectItem
value="s"
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
>
<div className="ml-3 font-medium">Seconds</div>
</SelectItem>
<SelectItem
value="m"
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
>
<div className="ml-3 font-medium">Minutes</div>
</SelectItem>
<SelectItem
value="h"
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
>
<div className="ml-3 font-medium">Hours</div>
</SelectItem>
</Select>
</FormControl>
)}
/>
</div>
</div>
</div> </div>
</TabPanel> </TabPanel>
@@ -120,13 +120,7 @@ export const ViewIdentityUniversalAuthContent = ({
isDisabled={!isAllowed || !lockedOutState || isClearLockoutsPending} isDisabled={!isAllowed || !lockedOutState || isClearLockoutsPending}
size="xs" size="xs"
onClick={clearLockouts} onClick={clearLockouts}
leftIcon={ isLoading={isClearLockoutsPending}
isClearLockoutsPending ? (
<FontAwesomeIcon icon={faArrowsRotate} className="animate-spin" />
) : (
<FontAwesomeIcon icon={faFire} />
)
}
colorSchema="secondary" colorSchema="secondary"
> >
Clear All Lockouts Clear All Lockouts
@@ -8,63 +8,11 @@ import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2"; import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useUpdateOrg } from "@app/hooks/api"; import { useUpdateOrg } from "@app/hooks/api";
import { durationToSeconds, getObjectFromSeconds } from "@app/helpers/datetime";
const MAX_SHARED_SECRET_LIFETIME_SECONDS = 30 * 24 * 60 * 60; // 30 days in seconds const MAX_SHARED_SECRET_LIFETIME_SECONDS = 30 * 24 * 60 * 60; // 30 days in seconds
const MIN_SHARED_SECRET_LIFETIME_SECONDS = 5 * 60; // 5 minutes in seconds const MIN_SHARED_SECRET_LIFETIME_SECONDS = 5 * 60; // 5 minutes in seconds
// Helper function to convert duration to seconds
const durationToSeconds = (value: number, unit: "m" | "h" | "d"): number => {
switch (unit) {
case "m":
return value * 60;
case "h":
return value * 60 * 60;
case "d":
return value * 60 * 60 * 24;
default:
return 0;
}
};
// Helper function to convert seconds to form lifetime value and unit
const getFormLifetimeFromSeconds = (
totalSeconds: number | null | undefined
): { maxLifetimeValue: number; maxLifetimeUnit: "m" | "h" | "d" } => {
const DEFAULT_LIFETIME_VALUE = 30;
const DEFAULT_LIFETIME_UNIT = "d" as "m" | "h" | "d";
if (totalSeconds == null || totalSeconds <= 0) {
return {
maxLifetimeValue: DEFAULT_LIFETIME_VALUE,
maxLifetimeUnit: DEFAULT_LIFETIME_UNIT
};
}
const secondsInDay = 24 * 60 * 60;
const secondsInHour = 60 * 60;
const secondsInMinute = 60;
if (totalSeconds % secondsInDay === 0) {
const value = totalSeconds / secondsInDay;
if (value >= 1) return { maxLifetimeValue: value, maxLifetimeUnit: "d" };
}
if (totalSeconds % secondsInHour === 0) {
const value = totalSeconds / secondsInHour;
if (value >= 1) return { maxLifetimeValue: value, maxLifetimeUnit: "h" };
}
if (totalSeconds % secondsInMinute === 0) {
const value = totalSeconds / secondsInMinute;
if (value >= 1) return { maxLifetimeValue: value, maxLifetimeUnit: "m" };
}
return {
maxLifetimeValue: DEFAULT_LIFETIME_VALUE,
maxLifetimeUnit: DEFAULT_LIFETIME_UNIT
};
};
const formSchema = z const formSchema = z
.object({ .object({
maxLifetimeValue: z.number().min(1, "Value must be at least 1"), maxLifetimeValue: z.number().min(1, "Value must be at least 1"),
@@ -79,32 +27,18 @@ const formSchema = z
const durationInSeconds = durationToSeconds(maxLifetimeValue, maxLifetimeUnit); const durationInSeconds = durationToSeconds(maxLifetimeValue, maxLifetimeUnit);
// Check max limit
if (durationInSeconds > MAX_SHARED_SECRET_LIFETIME_SECONDS) { if (durationInSeconds > MAX_SHARED_SECRET_LIFETIME_SECONDS) {
let message = "Duration exceeds maximum allowed limit";
if (maxLifetimeUnit === "m") {
message = `Maximum allowed minutes is ${MAX_SHARED_SECRET_LIFETIME_SECONDS / 60} (30 days)`;
} else if (maxLifetimeUnit === "h") {
message = `Maximum allowed hours is ${MAX_SHARED_SECRET_LIFETIME_SECONDS / (60 * 60)} (30 days)`;
} else if (maxLifetimeUnit === "d") {
message = `Maximum allowed days is ${MAX_SHARED_SECRET_LIFETIME_SECONDS / (24 * 60 * 60)}`;
}
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message, message: "Duration exceeds a maximum of 30 days",
path: ["maxLifetimeValue"] path: ["maxLifetimeValue"]
}); });
} }
// Check min limit
if (durationInSeconds < MIN_SHARED_SECRET_LIFETIME_SECONDS) { if (durationInSeconds < MIN_SHARED_SECRET_LIFETIME_SECONDS) {
const message = `Duration must be at least ${MIN_SHARED_SECRET_LIFETIME_SECONDS / 60} minutes`; // 5 minutes
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message, message: "Duration must be at least 5 minutes",
path: ["maxLifetimeValue"] path: ["maxLifetimeValue"]
}); });
} }
@@ -122,10 +56,14 @@ export const OrgSecretShareLimitSection = () => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const getDefaultFormValues = () => { const getDefaultFormValues = () => {
const initialLifetime = getFormLifetimeFromSeconds(currentOrg?.maxSharedSecretLifetime); const initialLifetime = getObjectFromSeconds(currentOrg?.maxSharedSecretLifetime, [
"m",
"h",
"d"
]);
return { return {
maxLifetimeValue: initialLifetime.maxLifetimeValue, maxLifetimeValue: initialLifetime.value,
maxLifetimeUnit: initialLifetime.maxLifetimeUnit, maxLifetimeUnit: initialLifetime.unit as "m" | "h" | "d",
maxViewLimit: currentOrg?.maxSharedSecretViewLimit?.toString() || "1", maxViewLimit: currentOrg?.maxSharedSecretViewLimit?.toString() || "1",
shouldLimitView: Boolean(currentOrg?.maxSharedSecretViewLimit) shouldLimitView: Boolean(currentOrg?.maxSharedSecretViewLimit)
}; };