@@ -2,9 +2,9 @@ import i18n from '@dhis2/d2-i18n'
22import { Button , Modal , ModalTitle , ModalContent } from '@dhis2/ui'
33import { getInstance as getD2 } from 'd2'
44import FormBuilder from 'd2-ui/lib/forms/FormBuilder.component.js'
5- import { isUrlArray , isRequired } from 'd2-ui/lib/forms/Validators.js'
5+ import { isRequired } from 'd2-ui/lib/forms/Validators.js'
66import PropTypes from 'prop-types'
7- import React from 'react'
7+ import React , { useState } from 'react'
88import MultiToggle from '../form-fields/multi-toggle.js'
99import TextField from '../form-fields/text-field.js'
1010import styles from './ClientForm.module.css'
@@ -17,47 +17,107 @@ const validateClientID = async (v) => {
1717 const d2 = await getD2 ( )
1818 const list = await d2 . models . oAuth2Clients . list ( {
1919 paging : false ,
20- filter : [ `cid :eq:${ v } ` ] ,
20+ filter : [ `clientId :eq:${ v } ` ] ,
2121 } )
2222 if ( list . size > 0 ) {
2323 throw i18n . t ( 'This client ID is already taken' )
2424 }
2525}
2626
2727const ClientForm = ( { clientModel, onUpdate, onSave, onCancel } ) => {
28- const grantTypes = ( ( clientModel && clientModel . grantTypes ) || [ ] ) . reduce (
29- ( curr , prev ) => {
30- curr [ prev ] = true
31- return curr
32- } ,
33- { }
34- )
28+ const [ formErrors , setFormErrors ] = useState ( {
29+ clientId : false ,
30+ redirectUris : false ,
31+ } )
32+
33+ // Handle both string and array types for authorizationGrantTypes
34+ let authGrantTypesArray = [ ]
35+
36+ if ( clientModel && clientModel . authorizationGrantTypes ) {
37+ // If it's a string (from backend), split it
38+ if ( typeof clientModel . authorizationGrantTypes === 'string' ) {
39+ authGrantTypesArray = clientModel . authorizationGrantTypes
40+ . split ( ',' )
41+ . map ( ( type ) => type . trim ( ) )
42+ . filter ( Boolean )
43+ }
44+ // If it's already an array (from form update)
45+ else if ( Array . isArray ( clientModel . authorizationGrantTypes ) ) {
46+ authGrantTypesArray = clientModel . authorizationGrantTypes
47+ }
48+ }
49+
50+ const grantTypes = authGrantTypesArray . reduce ( ( curr , prev ) => {
51+ curr [ prev ] = true
52+ return curr
53+ } , { } )
54+
55+ // Format redirectUris for display in the form
56+ let formattedRedirectUris = ''
57+ if ( clientModel && clientModel . redirectUris ) {
58+ if ( Array . isArray ( clientModel . redirectUris ) ) {
59+ // If it's an array, join with newlines for display
60+ formattedRedirectUris = clientModel . redirectUris . join ( '\n' )
61+ } else if ( typeof clientModel . redirectUris === 'string' ) {
62+ // If it's a comma-separated string, replace commas with newlines for display
63+ formattedRedirectUris = clientModel . redirectUris
64+ . split ( ',' )
65+ . map ( ( uri ) => uri . trim ( ) )
66+ . filter ( Boolean )
67+ . join ( '\n' )
68+ }
69+ }
70+
71+ const handleSave = ( ) => {
72+ // Check fields and show errors if needed
73+ const clientIdEmpty =
74+ ! clientModel . clientId || clientModel . clientId . trim ( ) === ''
75+ const redirectUrisEmpty =
76+ ! formattedRedirectUris || formattedRedirectUris . trim ( ) === ''
77+
78+ setFormErrors ( {
79+ clientId : clientIdEmpty ,
80+ redirectUris : redirectUrisEmpty ,
81+ } )
82+
83+ // Only save if both fields are valid
84+ if ( ! clientIdEmpty && ! redirectUrisEmpty ) {
85+ onSave ( )
86+ }
87+ }
88+
89+ // Handle field updates and clear errors
90+ const handleFieldUpdate = ( fieldName , value ) => {
91+ // Clear the error for this field if it has a value
92+ if ( value ) {
93+ // Check if value is a string before using trim()
94+ const isValid =
95+ typeof value === 'string'
96+ ? value . trim ( ) . length > 0
97+ : Boolean ( value )
98+
99+ if ( isValid ) {
100+ setFormErrors ( ( prev ) => ( {
101+ ...prev ,
102+ [ fieldName ] : false ,
103+ } ) )
104+ }
105+ }
106+
107+ // Call the original onUpdate function
108+ onUpdate ( fieldName , value )
109+ }
35110
36111 const fields = [
37112 {
38- name : 'name' ,
39- value : clientModel . name ,
40- component : TextField ,
41- props : {
42- floatingLabelText : i18n . t ( 'Name' ) ,
43- style : formFieldStyle ,
44- changeEvent : 'onBlur' ,
45- } ,
46- validators : [
47- {
48- validator : isRequired ,
49- message : i18n . t ( 'Required' ) ,
50- } ,
51- ] ,
52- } ,
53- {
54- name : 'cid' ,
55- value : clientModel . cid ,
113+ name : 'clientId' ,
114+ value : clientModel . clientId ,
56115 component : TextField ,
57116 props : {
58117 floatingLabelText : i18n . t ( 'Client ID' ) ,
59118 style : formFieldStyle ,
60119 changeEvent : 'onBlur' ,
120+ errorText : formErrors . clientId ? i18n . t ( 'Required' ) : null ,
61121 } ,
62122 validators : [
63123 {
@@ -82,17 +142,12 @@ const ClientForm = ({ clientModel, onUpdate, onSave, onCancel }) => {
82142 } ,
83143 } ,
84144 {
85- name : 'grantTypes ' ,
145+ name : 'authorizationGrantTypes ' ,
86146 component : MultiToggle ,
87147 style : formFieldStyle ,
88148 props : {
89149 label : i18n . t ( 'Grant Types' ) ,
90150 items : [
91- {
92- name : 'password' ,
93- text : i18n . t ( 'Password' ) ,
94- value : grantTypes . password ,
95- } ,
96151 {
97152 name : 'refresh_token' ,
98153 text : i18n . t ( 'Refresh token' ) ,
@@ -108,18 +163,21 @@ const ClientForm = ({ clientModel, onUpdate, onSave, onCancel }) => {
108163 } ,
109164 {
110165 name : 'redirectUris' ,
111- value : ( clientModel . redirectUris || [ ] ) . join ( '\n' ) ,
166+ value : formattedRedirectUris ,
112167 component : TextField ,
113168 props : {
114169 hintText : i18n . t ( 'One URL per line' ) ,
115170 floatingLabelText : i18n . t ( 'Redirect URIs' ) ,
116171 multiLine : true ,
117172 style : formFieldStyle ,
118173 changeEvent : 'onBlur' ,
174+ errorText : formErrors . redirectUris
175+ ? i18n . t ( 'This field should contain a list of URLs' )
176+ : null ,
119177 } ,
120178 validators : [
121179 {
122- validator : isUrlArray ,
180+ validator : isRequired ,
123181 message : i18n . t ( 'This field should contain a list of URLs' ) ,
124182 } ,
125183 ] ,
@@ -130,13 +188,17 @@ const ClientForm = ({ clientModel, onUpdate, onSave, onCancel }) => {
130188 clientModel . id === undefined
131189 ? i18n . t ( 'Create new OAuth2 Client' )
132190 : i18n . t ( 'Edit OAuth2 Client' )
191+
133192 return (
134193 < Modal onClose = { onCancel } >
135194 < ModalTitle > { headerText } </ ModalTitle >
136195 < ModalContent >
137- < FormBuilder fields = { fields } onUpdateField = { onUpdate } />
196+ < FormBuilder
197+ fields = { fields }
198+ onUpdateField = { handleFieldUpdate }
199+ />
138200 < div style = { { marginTop : '1rem' } } >
139- < Button primary onClick = { onSave } >
201+ < Button primary onClick = { handleSave } >
140202 { i18n . t ( 'Save' ) }
141203 </ Button >
142204 < Button
0 commit comments