Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions admin-ui/plugins/fido/components/DynamicConfiguration.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useCallback, useMemo } from 'react'
import React, { useState, useCallback, useEffect, useMemo } from 'react'
import { useFormik } from 'formik'
import { Row, Col, Form, FormGroup } from 'Components'
import GluuInputRow from 'Routes/Apps/Gluu/GluuInputRow'
Expand All @@ -23,17 +23,32 @@ const DynamicConfiguration: React.FC<DynamicConfigurationProps> = ({
setModal((prev) => !prev)
}, [])

const initialValues = useMemo<DynamicConfigFormValues>(
() =>
transformToFormValues(fidoConfiguration, fidoConstants.DYNAMIC) as DynamicConfigFormValues,
[fidoConfiguration],
)

const formik = useFormik<DynamicConfigFormValues>({
initialValues: transformToFormValues(
fidoConfiguration,
fidoConstants.DYNAMIC,
) as DynamicConfigFormValues,
initialValues,
onSubmit: readOnly ? () => undefined : toggle,
validationSchema: validationSchema[fidoConstants.VALIDATION_SCHEMAS.DYNAMIC_CONFIG],
enableReinitialize: true,
validateOnMount: true,
})

// formik intentionally excluded - it has a new reference each render, causing infinite reset loop
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
if (fidoConfiguration) {
formik.resetForm({
values: transformToFormValues(
fidoConfiguration,
fidoConstants.DYNAMIC,
) as DynamicConfigFormValues,
})
}
}, [fidoConfiguration])

const submitForm = useCallback(
(userMessage: string) => {
if (readOnly) {
Expand All @@ -46,12 +61,8 @@ const DynamicConfiguration: React.FC<DynamicConfigurationProps> = ({
)

const handleCancel = useCallback(() => {
const initialValues = transformToFormValues(
fidoConfiguration,
fidoConstants.DYNAMIC,
) as DynamicConfigFormValues
formik.resetForm({ values: initialValues })
}, [formik, fidoConfiguration])
formik.resetForm()
}, [formik])

const handleFormSubmit = useCallback(
(e: React.FormEvent<HTMLFormElement>) => {
Expand Down
36 changes: 21 additions & 15 deletions admin-ui/plugins/fido/components/StaticConfiguration.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useCallback, useMemo } from 'react'
import React, { useState, useCallback, useEffect, useMemo } from 'react'
import { useFormik } from 'formik'
import { useTranslation } from 'react-i18next'

Expand Down Expand Up @@ -34,8 +34,6 @@ const StaticConfiguration: React.FC<StaticConfigurationProps> = ({
isSubmitting,
readOnly,
}) => {
const staticConfiguration = fidoConfiguration?.fido2Configuration

const { t } = useTranslation()

const [modal, setModal] = useState(false)
Expand All @@ -44,17 +42,29 @@ const StaticConfiguration: React.FC<StaticConfigurationProps> = ({
setModal((prev) => !prev)
}, [])

const initialValues: StaticConfigFormValues = transformToFormValues(
fidoConfiguration?.fido2Configuration,
fidoConstants.STATIC,
) as StaticConfigFormValues

const formik = useFormik<StaticConfigFormValues>({
initialValues: transformToFormValues(
staticConfiguration,
fidoConstants.STATIC,
) as StaticConfigFormValues,
initialValues,
onSubmit: readOnly ? () => undefined : toggle,
validationSchema: validationSchema[fidoConstants.VALIDATION_SCHEMAS.STATIC_CONFIG],
enableReinitialize: true,
validateOnMount: true,
})

useEffect(() => {
if (fidoConfiguration?.fido2Configuration) {
formik.resetForm({
values: transformToFormValues(
fidoConfiguration.fido2Configuration,
fidoConstants.STATIC,
) as StaticConfigFormValues,
})
}
}, [fidoConfiguration])

const submitForm = useCallback(
(userMessage: string) => {
if (readOnly) {
Expand All @@ -67,12 +77,8 @@ const StaticConfiguration: React.FC<StaticConfigurationProps> = ({
)

const handleCancel = useCallback(() => {
const initialValues = transformToFormValues(
staticConfiguration,
fidoConstants.STATIC,
) as StaticConfigFormValues
formik.resetForm({ values: initialValues })
}, [formik, staticConfiguration])
formik.resetForm()
}, [formik])

const requestedPartiesOptions = useMemo(() => {
return (formik.values.requestedParties || []).map((item) => ({
Expand Down Expand Up @@ -364,7 +370,7 @@ const StaticConfiguration: React.FC<StaticConfigurationProps> = ({
showError={!!(formik.errors.attestationMode && formik.touched.attestationMode)}
errorMessage={formik.errors.attestationMode}
handleChange={(_e) => {
formik.setFieldTouched(fidoConstants.FORM_FIELDS.ATTESTATION_MODE, true)
formik.setFieldTouched(fidoConstants.FORM_FIELDS.ATTESTATION_MODE, true, false)
}}
/>
</Col>
Expand Down
123 changes: 83 additions & 40 deletions admin-ui/plugins/user-management/components/UserForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function UserForm({ onSubmitData, userDetails, isSubmitting = false }: Readonly<

const theme = useContext(ThemeContext) as ThemeContextType
const selectedTheme = theme.state.theme
const options = useMemo<Partial<GetAttributesParams>>(() => ({}), [])
const options: Partial<GetAttributesParams> = {}
const initialValues = useMemo(
() => initializeCustomAttributes(userDetails || null, memoizedPersonAttributes),
[userDetails, memoizedPersonAttributes],
Expand All @@ -80,9 +80,7 @@ function UserForm({ onSubmitData, userDetails, isSubmitting = false }: Readonly<
validateOnChange: true,
validateOnBlur: true,
validateOnMount: false,
onSubmit: () => {
// Form submission is handled by GluuCommitDialog onAccept
},
onSubmit: () => {},
})

const toggle = useCallback(() => {
Expand All @@ -93,25 +91,21 @@ function UserForm({ onSubmitData, userDetails, isSubmitting = false }: Readonly<
const hasModifiedFields = Object.keys(modifiedFields).length > 0
const isFormChanged = formik.dirty || hasModifiedFields

if (isSubmitting || !isFormChanged || !formik.isValid) {
return
}

const anyKeyPresent = revokeSessionWhenFieldsModifiedInUserForm.some((key) =>
Object.prototype.hasOwnProperty.call(modifiedFields, key),
key in modifiedFields,
)
if (anyKeyPresent) {
setAlertMessage(t('messages.revokeUserSession'))
setAlertSeverity('warning')
}

if (isSubmitting || !isFormChanged) {
return
}

if (!hasModifiedFields && !formik.isValid) {
return
}

setOperations(buildFormOperations(modifiedFields))
toggle()
}, [isSubmitting, formik.dirty, formik.isValid, modifiedFields, toggle])
}, [isSubmitting, formik.dirty, formik.isValid, modifiedFields, toggle, t])

const handleNavigateBack = useCallback(() => {
navigateBack(ROUTES.USER_MANAGEMENT)
Expand All @@ -121,15 +115,14 @@ function UserForm({ onSubmitData, userDetails, isSubmitting = false }: Readonly<
const resetValues = initializeCustomAttributes(userDetails || null, memoizedPersonAttributes)
formik.resetForm({ values: resetValues })
setModifiedFields({})
setSelectedClaims([])
if (userDetails) {
setSelectedClaims([])
setupCustomAttributes(userDetails, memoizedPersonAttributes, [], setSelectedClaims)
initializedRef.current = userDetails.inum || 'new'
} else {
setSelectedClaims([])
initializedRef.current = 'new'
}
}, [formik, userDetails, memoizedPersonAttributes, selectedClaims, setSelectedClaims])
}, [formik, userDetails, memoizedPersonAttributes, setSelectedClaims])

const toggleChangePasswordModal = useCallback(() => {
setChangePasswordModal((prev) => !prev)
Expand Down Expand Up @@ -170,7 +163,53 @@ function UserForm({ onSubmitData, userDetails, isSubmitting = false }: Readonly<
}

initializedRef.current = `${userKey}-${attrsKey}`
}, [userDetails?.inum, personAttributesKey, memoizedPersonAttributes, setSelectedClaims])
}, [userDetails?.inum, personAttributesKey, memoizedPersonAttributes, selectedClaims, setSelectedClaims])

const isEmptyValue = useCallback((value: unknown): boolean => {
if (value === null || value === undefined) return true
if (typeof value === 'string') return value.trim() === ''
if (Array.isArray(value)) return value.length === 0
return false
}, [])

const resetFormToInitialCustomAttributes = useCallback(() => {
const resetValues = initializeCustomAttributes(
userDetails || null,
memoizedPersonAttributes,
)
formik.resetForm({ values: resetValues })
}, [formik, memoizedPersonAttributes, userDetails])

const prevModifiedFieldsLengthRef = useRef<number>(0)

useEffect(() => {
const currentLength = Object.keys(modifiedFields).length
const prevLength = prevModifiedFieldsLengthRef.current

if (prevLength > 0 && currentLength === 0) {
resetFormToInitialCustomAttributes()
}

prevModifiedFieldsLengthRef.current = currentLength
}, [modifiedFields, resetFormToInitialCustomAttributes])

const updateModifiedFields = useCallback(
(name: string, value: unknown) => {
setModifiedFields((prev) => {
if (isEmptyValue(value)) {
const { [name]: _removed, ...rest } = prev
void _removed
return rest
} else {
return {
...prev,
[name]: value as string | string[] | boolean,
}
}
})
},
[isEmptyValue],
)

const removeSelectedClaimsFromState = useCallback(
(id: string) => {
Expand All @@ -180,28 +219,33 @@ function UserForm({ onSubmitData, userDetails, isSubmitting = false }: Readonly<

if (isMultiValued) {
formik.setFieldValue(id, [])
setModifiedFields((prev) => ({
...prev,
[id]: [],
}))
} else if (isBoolean) {
formik.setFieldValue(id, false)
setModifiedFields((prev) => ({
...prev,
[id]: false,
}))
} else {
formik.setFieldValue(id, '')
setModifiedFields((prev) => ({
...prev,
[id]: '',
}))
}

updateModifiedFields(id, isMultiValued ? [] : '')
setSelectedClaims((prev) => prev.filter((data) => data.name !== id))
formik.setFieldTouched(id, true)
},
[formik, memoizedPersonAttributes],
[formik, memoizedPersonAttributes, updateModifiedFields],
)

const setModifiedFieldsWrapper = useCallback(
(fields: React.SetStateAction<Record<string, string | string[] | boolean>>) => {
setModifiedFields((prev) => {
const newFields = typeof fields === 'function' ? fields(prev) : fields
const cleanedFields: Record<string, string | string[] | boolean> = {}

for (const [key, value] of Object.entries(newFields)) {
if (!isEmptyValue(value)) {
cleanedFields[key] = value
}
}

return cleanedFields
})
},
[isEmptyValue],
)

const handleChange = useCallback(
Expand All @@ -213,12 +257,9 @@ function UserForm({ onSubmitData, userDetails, isSubmitting = false }: Readonly<
const { name, value } = e.target
formik.setFieldValue(name, value)
formik.setFieldTouched(name, true, false)
setModifiedFields((prev) => ({
...prev,
[name]: value as string | string[],
}))
updateModifiedFields(name, value)
},
[formik],
[formik, updateModifiedFields],
)

useEffect(() => {
Expand Down Expand Up @@ -422,7 +463,7 @@ function UserForm({ onSubmitData, userDetails, isSubmitting = false }: Readonly<
data={data}
formik={formik}
handler={removeSelectedClaimsFromState}
setModifiedFields={setModifiedFields}
setModifiedFields={setModifiedFieldsWrapper}
modifiedFields={modifiedFields}
/>
))}
Expand Down Expand Up @@ -467,7 +508,9 @@ function UserForm({ onSubmitData, userDetails, isSubmitting = false }: Readonly<
onBack={handleNavigateBack}
showCancel={true}
onCancel={handleCancel}
disableCancel={isSubmitting || !formik.dirty}
disableCancel={
isSubmitting || (!formik.dirty && Object.keys(modifiedFields).length === 0)
}
showApply={true}
onApply={handleApply}
disableApply={shouldDisableApplyButton(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const shouldDisableApplyButton = (
const isFormChanged = isDirty || hasModifiedFields

if (isSubmitting || !isFormChanged) return true
if (!hasModifiedFields && !isValid) return true
if (!isValid) return true
return false
}

Expand Down
Loading