Skip to content
Closed
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
69ae0e0
fix(admin-ui): unable to add Custom and Module properties in Scripts …
faisalsiddique4400 Oct 30, 2025
38a4851
code rabbit suggestions
faisalsiddique4400 Oct 30, 2025
a7c8332
code rabbit suggestions
faisalsiddique4400 Oct 30, 2025
5a84bf4
code rabbit suggestions
faisalsiddique4400 Oct 30, 2025
4809e95
pagination count issues fixed
faisalsiddique4400 Oct 30, 2025
eae25f9
fix(admin-ui): unable to map permission to a role using GUI (#2400)
faisalsiddique4400 Oct 30, 2025
94b105b
fix(admin-ui): adding uniformity in cancel and back buttons present …
faisalsiddique4400 Oct 30, 2025
4317ea8
Merge branch 'main' of github-faisal:GluuFederation/flex into admin-u…
faisalsiddique4400 Oct 30, 2025
b9fd682
Code rabbit suggestions
faisalsiddique4400 Oct 30, 2025
9bc53ff
Code rabbit suggestions
faisalsiddique4400 Oct 30, 2025
847a607
Code rabbit suggestions
faisalsiddique4400 Oct 30, 2025
793a56a
Code rabbit suggestions
faisalsiddique4400 Oct 30, 2025
7181998
Code rabbit suggestions
faisalsiddique4400 Oct 30, 2025
241e9c6
Code rabbit suggestions
faisalsiddique4400 Oct 30, 2025
5dc0919
Code rabbit suggestions
faisalsiddique4400 Oct 30, 2025
68f742d
buttons swapping
faisalsiddique4400 Oct 31, 2025
ea5aa6b
Code Rabbit fixes
faisalsiddique4400 Oct 31, 2025
0cd0df3
Code Rabbit fixes
faisalsiddique4400 Oct 31, 2025
7af2074
Code Rabbit fixes
faisalsiddique4400 Oct 31, 2025
4e86e97
Code Rabbit fixes
faisalsiddique4400 Oct 31, 2025
1bff777
Code Rabbit fixes
faisalsiddique4400 Oct 31, 2025
a288f10
Code Rabbit fixes
faisalsiddique4400 Oct 31, 2025
cb7b80e
feat(admin): adding uniformity in cancel and back buttons present in …
faisalsiddique4400 Oct 31, 2025
4c5e6d3
Merge branch 'main' into admin-ui-issue-2361-scim
faisalsiddique4400 Oct 31, 2025
89271ec
Saperating footer components
faisalsiddique4400 Nov 3, 2025
75b0358
Code rabbit changes
faisalsiddique4400 Nov 3, 2025
c90eec1
Code rabbit changes
faisalsiddique4400 Nov 3, 2025
b2b52b0
Code rabbit changes
faisalsiddique4400 Nov 3, 2025
8aba204
Code rabbit changes
faisalsiddique4400 Nov 3, 2025
f5fc2be
file notations fixes
faisalsiddique4400 Nov 3, 2025
00b2d45
file notations fixes
faisalsiddique4400 Nov 3, 2025
b9dad66
Rename Gluuformfooter.tsx to GluuFormFooter.tsx
faisalsiddique4400 Nov 3, 2025
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
1 change: 1 addition & 0 deletions admin-ui/app/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"configuration_copied": "Configuration copied",
"copy_configuration": "Copy configuration",
"apply": "Apply",
"back": "Back",
"back_home": "Back Home",
"cancel": "Cancel",
"currentMonth": "Current Month",
Expand Down
1 change: 1 addition & 0 deletions admin-ui/app/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"configuration_copied": "Configuración copiada",
"copy_configuration": "Copiar configuración",
"apply": "Aplicar",
"back": "Atrás",
"back_home": "Volver al inicio",
"cancel": "Cancelar",
"currentMonth": "Mes actual",
Expand Down
1 change: 1 addition & 0 deletions admin-ui/app/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
"reject": "Rejeter",
"add_header": "Ajouter un en-tête",
"apply": "Appliquer",
"back": "Retour",
"back_home": "Retour à la maison",
"cancel": "Annuler",
"clear": "Effacer",
Expand Down
1 change: 1 addition & 0 deletions admin-ui/app/locales/pt/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"add_mapping": "Add Mapping",
"add_property": "Adicionar propriedade",
"apply": "Aplicar",
"back": "Voltar",
"back_home": "Voltar para casa",
"add_server": "Adicionar servidor",
"add_base_dn": "Adicionar DN básico",
Expand Down
5 changes: 2 additions & 3 deletions admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,8 @@ function GluuProperties({
}
}
const removeProperty = (position: any) => {
let data = [...properties]
delete data[position]
data = data.filter((element: Property | undefined) => element != null)
const data = [...properties]
data.splice(position, 1)
setProperties(data)

// Sync with formik
Expand Down
22 changes: 15 additions & 7 deletions admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const theme = createTheme({

interface GluuTypeAheadProps {
label: string
labelKey?: string
labelKey?: string | ((option: Option) => string)
name: string
value?: Option[]
options: Option[]
Expand Down Expand Up @@ -70,10 +70,13 @@ const GluuTypeAhead = memo(function GluuTypeAhead({
}: GluuTypeAheadProps) {
const { t } = useTranslation()

const selectedValue = useMemo(
() => (value !== undefined ? value : (formik?.values?.[name] as Option[]) || []),
[value, formik, name],
)
const selectedValue = useMemo(() => {
if (value !== undefined) {
return value
}
const fieldValue = formik?.values?.[name]
return Array.isArray(fieldValue) ? (fieldValue as Option[]) : []
}, [value, formik?.values?.[name], name])
Comment on lines +73 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix dependency array to ensure proper memoization.

The dependency formik?.values?.[name] won't trigger re-computation correctly because React's dependency comparison doesn't evaluate the optional chain expression—it compares the reference to formik?.values?.[name] itself, not the value. When formik.values changes, this memo may return stale data.

Apply this diff:

   const selectedValue = useMemo(() => {
     if (value !== undefined) {
       return value
     }
     const fieldValue = formik?.values?.[name]
     return Array.isArray(fieldValue) ? (fieldValue as Option[]) : []
-  }, [value, formik?.values?.[name], name])
+  }, [value, formik?.values, name])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const selectedValue = useMemo(() => {
if (value !== undefined) {
return value
}
const fieldValue = formik?.values?.[name]
return Array.isArray(fieldValue) ? (fieldValue as Option[]) : []
}, [value, formik?.values?.[name], name])
const selectedValue = useMemo(() => {
if (value !== undefined) {
return value
}
const fieldValue = formik?.values?.[name]
return Array.isArray(fieldValue) ? (fieldValue as Option[]) : []
}, [value, formik?.values, name])
🤖 Prompt for AI Agents
In admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx around lines 73–79, the
useMemo dependency list uses the optional-chained expression
formik?.values?.[name], which can miss updates; change the dependencies to
[value, formik?.values, name] (i.e., depend on the whole formik.values object
plus name and value) so the memo recalculates when form values change, and
ensure types remain correct when reading formik.values[name] inside the memo.


const handleChange = useCallback(
(selected: Option[]) => {
Expand All @@ -86,10 +89,15 @@ const GluuTypeAhead = memo(function GluuTypeAhead({
onChange(selected)
}
},
[formik, name, onChange],
[formik?.setFieldValue, name, onChange],
)

const resolvedLabelKey = useMemo(() => labelKey || name, [labelKey, name])
const resolvedLabelKey = useMemo(() => {
if (typeof labelKey === 'function' || typeof labelKey === 'string') {
return labelKey
}
return 'name'
}, [labelKey])

return (
<FormGroup row>
Expand Down
226 changes: 226 additions & 0 deletions admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { useContext, useMemo, useCallback, memo } from 'react'
import { useNavigate } from 'react-router-dom'
import { Button, Divider } from 'Components'
import { useTranslation } from 'react-i18next'
import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle'
import { ThemeContext } from 'Context/theme/themeContext'
import { Box } from '@mui/material'

interface ButtonLabelProps {
isLoading: boolean
iconClass: string
label: string
loadingIconClass?: string
}

interface GluuformfooterProps {
showBack?: boolean
backButtonLabel?: string
onBack?: () => void
disableBack?: boolean
showCancel?: boolean
cancelButtonLabel?: string
onCancel?: () => void
disableCancel?: boolean
showApply?: boolean
onApply?: () => void
disableApply?: boolean
applyButtonType?: 'button' | 'submit'
applyButtonLabel?: string
isLoading?: boolean
className?: string
}

const ButtonLabel = memo((props: ButtonLabelProps) => {
const { isLoading, iconClass, label, loadingIconClass = 'fa fa-spinner fa-spin' } = props
return (
<>
<i className={`${isLoading ? loadingIconClass : iconClass} me-2`} />
{label}
</>
)
})

ButtonLabel.displayName = 'ButtonLabel'

const BUTTON_STYLE = { ...applicationStyle.buttonStyle, ...applicationStyle.buttonFlexIconStyles }

const GluuFormFooter = ({
showBack,
backButtonLabel,
onBack,
disableBack,
showCancel,
cancelButtonLabel,
onCancel,
disableCancel,
showApply,
onApply,
disableApply,
applyButtonType = 'submit',
applyButtonLabel,
isLoading = false,
className = '',
}: GluuformfooterProps) => {
const { t } = useTranslation()
const theme = useContext(ThemeContext)
const selectedTheme = useMemo(() => theme?.state.theme || 'darkBlack', [theme?.state.theme])
const navigate = useNavigate()

const handleBackClick = useCallback(() => {
if (onBack) {
onBack()
return
} else {
navigate('/home/dashboard')
}
}, [onBack, navigate])

const handleCancelClick = useCallback(() => {
if (onCancel) {
onCancel()
}
}, [onCancel])

const buttonStates = useMemo(() => {
const hasAnyButton = Boolean(showBack) || Boolean(showCancel) || Boolean(showApply)
const hasAllThreeButtons = Boolean(showBack) && Boolean(showCancel) && Boolean(showApply)
const hasBackAndCancel = Boolean(showBack) && Boolean(showCancel) && !showApply

return {
showBack: Boolean(showBack),
showCancel: Boolean(showCancel),
showApply: Boolean(showApply),
hasAnyButton,
hasAllThreeButtons,
hasBackAndCancel,
}
}, [showBack, showCancel, showApply])

const buttonColor = useMemo(() => `primary-${selectedTheme}`, [selectedTheme])

const backLabel = useMemo(() => backButtonLabel || t('actions.back'), [backButtonLabel, t])
const cancelLabel = useMemo(
() => cancelButtonLabel || t('actions.cancel'),
[cancelButtonLabel, t],
)
const applyLabel = useMemo(() => applyButtonLabel || t('actions.apply'), [applyButtonLabel, t])

const buttonLayout = useMemo(() => {
if (!buttonStates.hasAnyButton) {
return { back: '', cancel: '', apply: '' }
}

const layout = {
back: buttonStates.showBack ? 'd-flex' : '',
cancel: buttonStates.showCancel ? 'd-flex' : '',
apply: buttonStates.showApply ? 'd-flex' : '',
}

if (buttonStates.showApply) {
layout.apply += ' ms-auto'
if (buttonStates.hasAllThreeButtons) {
layout.apply += ' me-0'
}
} else if (buttonStates.showCancel) {
layout.cancel += ' ms-auto'
}

return layout
}, [buttonStates])

if (!buttonStates.hasAnyButton) {
return null
}

return (
<>
<Divider />
<Box
display="flex"
my={2}
justifyContent="space-between"
alignItems="center"
gap={1}
className={className}
>
{buttonStates.showBack && (
<Button
color={buttonColor}
style={BUTTON_STYLE}
type="button"
onClick={handleBackClick}
className={buttonLayout.back}
disabled={disableBack}
>
<ButtonLabel isLoading={false} iconClass="fa fa-arrow-circle-left" label={backLabel} />
</Button>
)}

{buttonStates.showApply && (
<Box className={buttonLayout.apply}>
{applyButtonType === 'submit' ? (
<Button
type="submit"
color={buttonColor}
style={BUTTON_STYLE}
disabled={disableApply || isLoading}
>
<ButtonLabel
isLoading={isLoading}
iconClass="fa fa-check-circle"
label={applyLabel}
/>
</Button>
) : (
<Button
type="button"
color={buttonColor}
style={BUTTON_STYLE}
onClick={onApply}
disabled={disableApply || isLoading || !onApply}
>
<ButtonLabel
isLoading={isLoading}
iconClass="fa fa-check-circle"
label={applyLabel}
/>
</Button>
)}
</Box>
)}

{buttonStates.hasAllThreeButtons && (
<Button
color={buttonColor}
style={BUTTON_STYLE}
type="button"
onClick={handleCancelClick}
className={`${buttonLayout.cancel} ms-4`}
disabled={disableCancel || isLoading}
>
<ButtonLabel isLoading={false} iconClass="fa fa-undo" label={cancelLabel} />
</Button>
)}

{!buttonStates.hasAllThreeButtons && buttonStates.showCancel && (
<Button
color={buttonColor}
style={BUTTON_STYLE}
type="button"
onClick={handleCancelClick}
className={buttonLayout.cancel}
disabled={disableCancel || isLoading}
>
<ButtonLabel isLoading={false} iconClass="fa fa-undo" label={cancelLabel} />
</Button>
)}
</Box>
</>
)
}

const GluuFormFooterMemoized = memo(GluuFormFooter)
GluuFormFooterMemoized.displayName = 'GluuFormFooter'

export default GluuFormFooterMemoized
23 changes: 12 additions & 11 deletions admin-ui/app/utils/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,17 @@ export function formatDate(date?: string): string {
return '-'
}

export const trimObjectStrings = <T extends Record<string, unknown>>(obj: T): T => {
export const trimObjectStrings = <T extends object>(obj: T): T => {
const source = obj as unknown as Record<string, unknown>
const trimmed: Record<string, unknown> = {}
for (const key in obj) {
if (typeof obj[key] === 'string') {
trimmed[key] = (obj[key] as string).trim()
} else if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
trimmed[key] = trimObjectStrings(obj[key] as Record<string, unknown>)
for (const key in source) {
const value = source[key]
if (typeof value === 'string') {
trimmed[key] = value.trim()
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
trimmed[key] = trimObjectStrings(value as Record<string, unknown>)
} else {
trimmed[key] = obj[key]
trimmed[key] = value
}
}
return trimmed as T
Expand All @@ -87,8 +89,7 @@ export const mapPropertyToKeyValue = (prop: {
value1?: string
value2?: string
}): { key: string; value: string } => {
return {
key: prop.key || prop.value1 || '',
value: prop.value || prop.value2 || '',
}
const key = (prop.key ?? prop.value1 ?? '').trim()
const value = (prop.value ?? prop.value2 ?? '').trim()
return { key, value }
}
41 changes: 41 additions & 0 deletions admin-ui/app/utils/formUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { isEqual } from 'lodash'

type Primitive = string | number | boolean | null | undefined

export const isObjectEqual = <T extends Record<string, Primitive | object>>(
obj1: T,
obj2: T,
): boolean => {
if (obj1 === obj2) return true
if (obj1 == null || obj2 == null) return false
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2

return isEqual(obj1, obj2)
}

export const hasFormChanges = <
T extends Record<string, Primitive | object | Array<Primitive | object>>,
>(
currentValues: T,
initialValues: T,
excludeKeys: Array<keyof T> = [],
): boolean => {
const keys = (Object.keys(currentValues) as Array<keyof T>).filter(
(key) => !excludeKeys.includes(key),
)

for (const key of keys) {
const current = currentValues[key]
const initial = initialValues[key]

if (typeof current === 'object' && typeof initial === 'object') {
if (!isEqual(current, initial)) {
return true
}
} else if (current !== initial) {
return true
}
}

return false
}
Loading
Loading