Skip to content

Commit dae647a

Browse files
fix(admin-ui): Scope update issues, missing toasts, and disappearing action buttons (#2479)
* fix(admin): description missing in client view details and alignment issues in fields (#2471) * fix(admin): description missing in client view details and alignment issues in fields (#2471) * Minor styling enhancements * Code Rabbit AI suggestions * Code Rabbit AI suggestions * Code Rabbit AI suggestions * fix(admin-ui): fix scope update issues, missing toasts, and disappearing action buttons (#2476) * minor adjustments * minor adjustments * minor adjustments
1 parent 604ddd2 commit dae647a

File tree

10 files changed

+787
-550
lines changed

10 files changed

+787
-550
lines changed

admin-ui/plugins/auth-server/components/Scopes/ScopeAddPage.tsx

Lines changed: 63 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import React, { useEffect, useState, useMemo, useCallback } from 'react'
1+
import React, { useEffect, useState, useCallback } from 'react'
22
import { useSelector, useDispatch } from 'react-redux'
3+
import { useQueryClient } from '@tanstack/react-query'
34
import { CardBody, Card } from 'Components'
45
import ScopeForm from './ScopeForm'
56
import GluuLoader from 'Routes/Apps/Gluu/GluuLoader'
67
import { getAttributes, getScripts } from 'Redux/features/initSlice'
78
import { buildPayload } from 'Utils/PermChecker'
89
import GluuAlert from 'Routes/Apps/Gluu/GluuAlert'
10+
import { updateToast } from 'Redux/features/toastSlice'
911
import { useTranslation } from 'react-i18next'
1012
import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle'
11-
import { usePostOauthScopes } from 'JansConfigApi'
13+
import { usePostOauthScopes, getGetOauthScopesQueryKey } from 'JansConfigApi'
1214
import type { Scope } from 'JansConfigApi'
13-
import type { ExtendedScope, ScopeScript, ScopeClaim, ModifiedFields } from './types'
14-
import { EMPTY_SCOPE } from './types'
15+
import type { ScopeScript, ScopeClaim, ModifiedFields } from './types'
1516
import { useScopeActions } from './hooks'
17+
import { ScopeWithMessage, INITIAL_SCOPE } from './constants'
1618

1719
interface InitState {
1820
scripts: ScopeScript[]
@@ -25,17 +27,72 @@ interface RootState {
2527

2628
const ScopeAddPage: React.FC = () => {
2729
const { t } = useTranslation()
30+
2831
const dispatch = useDispatch()
29-
const { logScopeCreation, navigateToScopeList } = useScopeActions()
32+
const queryClient = useQueryClient()
3033

3134
const scripts = useSelector((state: RootState) => state.initReducer.scripts)
3235
const attributes = useSelector((state: RootState) => state.initReducer.attributes)
3336

37+
const { logScopeCreation, navigateToScopeList } = useScopeActions()
38+
3439
const [modifiedFields, setModifiedFields] = useState<ModifiedFields>({})
3540
const [errorMessage, setErrorMessage] = useState<string | null>(null)
3641

3742
const createScope = usePostOauthScopes()
3843

44+
const handleSubmit = useCallback(
45+
async (data: string) => {
46+
if (!data) return
47+
48+
setErrorMessage(null)
49+
50+
let parsedData: ScopeWithMessage
51+
try {
52+
parsedData = JSON.parse(data) as ScopeWithMessage
53+
} catch (error) {
54+
console.error('Error parsing scope data:', error)
55+
setErrorMessage(t('messages.error_in_parsing_data'))
56+
return
57+
}
58+
59+
try {
60+
const message = parsedData.action_message || ''
61+
delete parsedData.action_message
62+
63+
const response = await createScope.mutateAsync({ data: parsedData as Scope })
64+
65+
queryClient.invalidateQueries({
66+
predicate: (query) => {
67+
const queryKey = query.queryKey[0] as string
68+
return (
69+
queryKey === getGetOauthScopesQueryKey()[0] || queryKey === 'getOauthScopesByInum'
70+
)
71+
},
72+
})
73+
74+
const successMessage =
75+
response?.id || response?.displayName
76+
? `Scope '${response.id || response.displayName}' created successfully`
77+
: t('messages.scope_created_successfully')
78+
79+
dispatch(updateToast(true, 'success', successMessage))
80+
81+
try {
82+
await logScopeCreation(parsedData as Scope, message, modifiedFields)
83+
} catch (auditError) {
84+
console.error('Error logging audit:', auditError)
85+
}
86+
87+
navigateToScopeList()
88+
} catch (error) {
89+
console.error('Error creating scope:', error)
90+
setErrorMessage(error instanceof Error ? error.message : t('messages.error_in_saving'))
91+
}
92+
},
93+
[createScope, logScopeCreation, modifiedFields, navigateToScopeList, t, queryClient, dispatch],
94+
)
95+
3996
useEffect(() => {
4097
if (attributes.length === 0) {
4198
const attributeOptions: Record<string, unknown> = {}
@@ -55,39 +112,6 @@ const ScopeAddPage: React.FC = () => {
55112
}
56113
}, [dispatch, attributes.length, scripts.length])
57114

58-
async function handleSubmit(data: string) {
59-
if (!data) return
60-
61-
setErrorMessage(null)
62-
63-
let parsedData: Scope
64-
try {
65-
parsedData = JSON.parse(data) as Scope
66-
} catch (error) {
67-
console.error('Error parsing scope data:', error)
68-
setErrorMessage(t('messages.error_in_parsing_data'))
69-
return
70-
}
71-
72-
try {
73-
const message = (parsedData as Record<string, unknown>).action_message as string
74-
delete (parsedData as Record<string, unknown>).action_message
75-
76-
await createScope.mutateAsync({ data: parsedData })
77-
78-
try {
79-
await logScopeCreation(parsedData, message, modifiedFields)
80-
} catch (auditError) {
81-
console.error('Error logging audit:', auditError)
82-
}
83-
84-
navigateToScopeList()
85-
} catch (error) {
86-
console.error('Error creating scope:', error)
87-
setErrorMessage(error instanceof Error ? error.message : t('messages.error_in_saving'))
88-
}
89-
}
90-
91115
const handleSearch = useCallback(
92116
(value: string) => {
93117
dispatch({
@@ -98,21 +122,6 @@ const ScopeAddPage: React.FC = () => {
98122
[dispatch],
99123
)
100124

101-
const scope: ExtendedScope = useMemo(
102-
() => ({
103-
...EMPTY_SCOPE,
104-
claims: [],
105-
dynamicScopeScripts: [],
106-
defaultScope: false,
107-
attributes: {
108-
spontaneousClientId: undefined,
109-
spontaneousClientScopes: [],
110-
showInConfigurationEndpoint: false,
111-
},
112-
}),
113-
[],
114-
)
115-
116125
return (
117126
<GluuLoader blocking={createScope.isPending}>
118127
<GluuAlert
@@ -123,7 +132,7 @@ const ScopeAddPage: React.FC = () => {
123132
<Card className="mb-3" style={applicationStyle.mainCard}>
124133
<CardBody>
125134
<ScopeForm
126-
scope={scope}
135+
scope={INITIAL_SCOPE}
127136
scripts={scripts}
128137
attributes={attributes}
129138
handleSubmit={handleSubmit}

admin-ui/plugins/auth-server/components/Scopes/ScopeDetailPage.tsx

Lines changed: 89 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext } from 'react'
1+
import React, { useContext, useMemo, useCallback } from 'react'
22
import { Container, Row, Col } from 'Components'
33
import GluuFormDetailRow from 'Routes/Apps/Gluu/GluuFormDetailRow'
44
import { SCOPE } from 'Utils/ApiResources'
@@ -7,98 +7,100 @@ import { ThemeContext } from 'Context/theme/themeContext'
77
import customColors from '@/customColors'
88
import type { ScopeDetailPageProps } from './types'
99

10+
const CONTAINER_STYLES = { backgroundColor: customColors.whiteSmoke } as const
11+
1012
const ScopeDetailPage: React.FC<ScopeDetailPageProps> = ({ row }) => {
1113
const { t } = useTranslation()
14+
1215
const theme = useContext(ThemeContext)
13-
const selectedTheme = theme?.state?.theme || 'light'
1416

15-
function getBadgeTheme(status: boolean | undefined): string {
16-
if (status) {
17-
return `primary-${selectedTheme}`
18-
} else {
19-
return 'dimmed'
20-
}
21-
}
17+
const selectedTheme = useMemo(() => theme?.state?.theme || 'light', [theme?.state?.theme])
18+
19+
const defaultScopeValue = useMemo(
20+
() => (row.defaultScope ? t('options.yes') : t('options.no')),
21+
[row.defaultScope, t],
22+
)
23+
24+
const attributeKeys = useMemo(() => Object.keys(row.attributes || {}), [row.attributes])
25+
26+
const defaultScopeBadgeColor = useMemo(
27+
() => (row.defaultScope ? `primary-${selectedTheme}` : 'dimmed'),
28+
[row.defaultScope, selectedTheme],
29+
)
30+
31+
const renderAttributeRow = useCallback(
32+
(item: string, key: number) => (
33+
<GluuFormDetailRow
34+
key={key}
35+
label={item}
36+
isBadge={true}
37+
value={String(row.attributes?.[item as keyof typeof row.attributes])}
38+
doc_category={SCOPE}
39+
doc_entry={`attributes.${item}`}
40+
/>
41+
),
42+
[row.attributes],
43+
)
2244

2345
return (
24-
<React.Fragment>
25-
<Container style={{ backgroundColor: customColors.whiteSmoke }}>
26-
<Row>
27-
<Col sm={6}>
28-
<GluuFormDetailRow
29-
label="fields.inum"
30-
value={row.inum}
31-
doc_category={SCOPE}
32-
doc_entry="inum"
33-
/>
34-
</Col>
35-
<Col sm={6}>
36-
<GluuFormDetailRow
37-
label="fields.id"
38-
value={row.id}
39-
doc_category={SCOPE}
40-
doc_entry="id"
41-
/>
42-
</Col>
43-
</Row>
44-
<Row>
45-
<Col sm={6}>
46-
<GluuFormDetailRow
47-
label="fields.description"
48-
value={row.description}
49-
doc_category={SCOPE}
50-
doc_entry="description"
51-
/>
52-
</Col>
53-
<Col sm={6}>
54-
<GluuFormDetailRow
55-
label="fields.displayname"
56-
value={row.displayName}
57-
doc_category={SCOPE}
58-
doc_entry="displayName"
59-
/>
60-
</Col>
61-
</Row>
62-
<Row>
63-
<Col sm={6}>
64-
<GluuFormDetailRow
65-
label="fields.scope_type"
66-
value={row.scopeType}
67-
doc_category={SCOPE}
68-
doc_entry="scopeType"
69-
isBadge
70-
/>
71-
</Col>
72-
<Col sm={6}>
73-
<GluuFormDetailRow
74-
label="fields.default_scope"
75-
isBadge
76-
badgeColor={getBadgeTheme(row.defaultScope)}
77-
value={row.defaultScope ? t('options.yes') : t('options.no')}
78-
doc_category={SCOPE}
79-
doc_entry="defaultScope"
80-
/>
81-
</Col>
82-
</Row>
83-
<Row>
84-
<Col sm={3}>{t('fields.attributes')}:</Col>
85-
<Col sm={9}>
86-
{Object.keys(row.attributes || {}).map((item, key) => {
87-
return (
88-
<GluuFormDetailRow
89-
key={key}
90-
label={item}
91-
isBadge={true}
92-
value={String(row.attributes?.[item as keyof typeof row.attributes])}
93-
doc_category={SCOPE}
94-
doc_entry={`attributes.${item}`}
95-
/>
96-
)
97-
})}
98-
</Col>
99-
</Row>
100-
</Container>
101-
</React.Fragment>
46+
<Container style={CONTAINER_STYLES}>
47+
<Row>
48+
<Col sm={6}>
49+
<GluuFormDetailRow
50+
label="fields.inum"
51+
value={row.inum}
52+
doc_category={SCOPE}
53+
doc_entry="inum"
54+
/>
55+
</Col>
56+
<Col sm={6}>
57+
<GluuFormDetailRow label="fields.id" value={row.id} doc_category={SCOPE} doc_entry="id" />
58+
</Col>
59+
</Row>
60+
<Row>
61+
<Col sm={6}>
62+
<GluuFormDetailRow
63+
label="fields.description"
64+
value={row.description}
65+
doc_category={SCOPE}
66+
doc_entry="description"
67+
/>
68+
</Col>
69+
<Col sm={6}>
70+
<GluuFormDetailRow
71+
label="fields.displayname"
72+
value={row.displayName}
73+
doc_category={SCOPE}
74+
doc_entry="displayName"
75+
/>
76+
</Col>
77+
</Row>
78+
<Row>
79+
<Col sm={6}>
80+
<GluuFormDetailRow
81+
label="fields.scope_type"
82+
value={row.scopeType}
83+
doc_category={SCOPE}
84+
doc_entry="scopeType"
85+
isBadge
86+
/>
87+
</Col>
88+
<Col sm={6}>
89+
<GluuFormDetailRow
90+
label="fields.default_scope"
91+
isBadge
92+
badgeColor={defaultScopeBadgeColor}
93+
value={defaultScopeValue}
94+
doc_category={SCOPE}
95+
doc_entry="defaultScope"
96+
/>
97+
</Col>
98+
</Row>
99+
<Row>
100+
<Col sm={3}>{t('fields.attributes')}:</Col>
101+
<Col sm={9}>{attributeKeys.map(renderAttributeRow)}</Col>
102+
</Row>
103+
</Container>
102104
)
103105
}
104106

0 commit comments

Comments
 (0)