Skip to content

Commit c360a4e

Browse files
committed
fix(users): clear stale values when switching templates
Track the last applied user template so the form can treat initial apply and template switches differently. Initial apply now fills template-driven fields, while switching templates replaces values and clears fields not defined by the new template (including licenses, groups, phones, and custom attributes) to prevent stale data from lingering.
1 parent 612ef72 commit c360a4e

1 file changed

Lines changed: 112 additions & 85 deletions

File tree

src/components/CippFormPages/CippAddEditUser.jsx

Lines changed: 112 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ const CippAddEditUser = (props) => {
2020
const [selectedTemplate, setSelectedTemplate] = useState(null)
2121
const [displayNameManuallySet, setDisplayNameManuallySet] = useState(false)
2222
const [usernameManuallySet, setUsernameManuallySet] = useState(false)
23+
// Tracks the template already applied to the form so we can tell a first
24+
// apply (fill empty fields) apart from a switch (replace/clear stale values)
25+
const appliedTemplateKeyRef = useRef(null)
2326
const router = useRouter()
2427
const { userId } = router.query
2528

@@ -261,6 +264,7 @@ const CippAddEditUser = (props) => {
261264
// Only clear selected template if it's not the default template
262265
if (selectedTemplate && !selectedTemplate.defaultForTenant) {
263266
setSelectedTemplate(null)
267+
appliedTemplateKeyRef.current = null
264268
}
265269
}
266270
}, [
@@ -290,100 +294,123 @@ const CippAddEditUser = (props) => {
290294

291295
// Auto-populate fields when template selected
292296
useEffect(() => {
293-
if (formType === 'add' && watchedFields.userTemplate?.addedFields) {
294-
const template = watchedFields.userTemplate.addedFields
295-
setSelectedTemplate(template)
297+
if (formType !== 'add' || !watchedFields.userTemplate?.addedFields) return
298+
const template = watchedFields.userTemplate.addedFields
299+
const templateKey = watchedFields.userTemplate.value ?? template.GUID ?? template.templateName
296300

297-
// Reset manual edit flags when template changes
298-
setDisplayNameManuallySet(false)
299-
setUsernameManuallySet(false)
301+
// Distinguish the first apply from a switch. On a switch we replace
302+
// template-driven fields (and clear ones the new template doesn't define)
303+
// so stale values from the previous template don't linger. On the first
304+
// apply we only fill fields that have a template value, so we don't clobber
305+
// input the user already entered or copied from another user.
306+
const isSwitch =
307+
appliedTemplateKeyRef.current !== null && appliedTemplateKeyRef.current !== templateKey
308+
appliedTemplateKeyRef.current = templateKey
300309

301-
// Only set fields if they don't already have values (don't override user input)
302-
const setFieldIfEmpty = (fieldName, value) => {
303-
if (value) {
304-
formControl.setValue(fieldName, value)
305-
}
306-
}
310+
setSelectedTemplate(template)
307311

308-
// Populate form fields from template
309-
if (template.primDomain) {
310-
// If primDomain is an object, use it as-is; if it's a string, convert to object
311-
const primDomainValue =
312-
typeof template.primDomain === 'string'
313-
? { label: template.primDomain, value: template.primDomain }
314-
: template.primDomain
315-
formControl.setValue('primDomain', primDomainValue)
316-
}
317-
if (template.usageLocation) {
318-
// Handle both object and string formats
319-
const usageLocationCode =
320-
typeof template.usageLocation === 'string'
321-
? template.usageLocation
322-
: template.usageLocation?.value
323-
const country = countryList.find((c) => c.Code === usageLocationCode)
324-
if (country) {
325-
setFieldIfEmpty('usageLocation', {
326-
label: country.Name,
327-
value: country.Code,
328-
})
329-
}
330-
}
331-
setFieldIfEmpty('jobTitle', template.jobTitle)
332-
setFieldIfEmpty('streetAddress', template.streetAddress)
333-
setFieldIfEmpty('city', template.city)
334-
setFieldIfEmpty('state', template.state)
335-
setFieldIfEmpty('postalCode', template.postalCode)
336-
setFieldIfEmpty('country', template.country)
337-
setFieldIfEmpty('companyName', template.companyName)
338-
setFieldIfEmpty('department', template.department)
339-
setFieldIfEmpty('mobilePhone', template.mobilePhone)
340-
const templateBusinessPhone = Array.isArray(template.businessPhones)
341-
? template.businessPhones[0]
342-
: template.businessPhones
343-
if (templateBusinessPhone) {
344-
formControl.setValue('businessPhones', [templateBusinessPhone])
345-
}
312+
// Reset manual edit flags when template changes
313+
setDisplayNameManuallySet(false)
314+
setUsernameManuallySet(false)
346315

347-
// Handle licenses - need to match the format expected by CippFormLicenseSelector
348-
if (template.licenses && Array.isArray(template.licenses)) {
349-
setFieldIfEmpty('licenses', template.licenses)
316+
// Apply a template value to a field. When the template has a value we set
317+
// it; when it doesn't and this is a switch we clear the field (emptyValue)
318+
// so the previous template's value doesn't linger.
319+
const applyField = (fieldName, value, emptyValue = '') => {
320+
const hasValue = Array.isArray(value)
321+
? value.length > 0
322+
: value !== undefined && value !== null && value !== ''
323+
if (hasValue) {
324+
formControl.setValue(fieldName, value, { shouldDirty: true })
325+
} else if (isSwitch) {
326+
formControl.setValue(fieldName, emptyValue, { shouldDirty: true })
350327
}
328+
}
351329

352-
// Handle groups from template
353-
const templateGroups = template.addToGroups || template.groupMemberships
354-
if (templateGroups) {
355-
const rawGroups = Array.isArray(templateGroups) ? templateGroups : [templateGroups]
356-
const groups = rawGroups.map((g) => {
357-
if (g.label && g.value) return g
358-
const groupType = g.groupTypes?.includes('Unified')
359-
? 'Microsoft 365'
360-
: g.mailEnabled && !g.groupTypes?.includes('Unified')
361-
? g.securityEnabled
362-
? 'Mail-Enabled Security'
363-
: 'Distribution list'
364-
: 'Security'
365-
return {
366-
label: g.displayName,
367-
value: g.id,
368-
addedFields: { groupType },
369-
}
370-
})
371-
if (groups.length > 0) {
372-
const currentGroups = watchedFields.AddToGroups
373-
if (!currentGroups || (Array.isArray(currentGroups) && currentGroups.length === 0)) {
374-
formControl.setValue('AddToGroups', groups, { shouldDirty: true })
375-
}
376-
}
330+
// Primary domain - accept both object and string formats
331+
const primDomainValue = template.primDomain
332+
? typeof template.primDomain === 'string'
333+
? { label: template.primDomain, value: template.primDomain }
334+
: template.primDomain
335+
: null
336+
applyField('primDomain', primDomainValue, null)
337+
338+
// Usage location - accept both object and string formats
339+
const usageLocationCode =
340+
typeof template.usageLocation === 'string'
341+
? template.usageLocation
342+
: template.usageLocation?.value
343+
const country = usageLocationCode
344+
? countryList.find((c) => c.Code === usageLocationCode)
345+
: null
346+
applyField(
347+
'usageLocation',
348+
country ? { label: country.Name, value: country.Code } : null,
349+
null
350+
)
351+
352+
applyField('jobTitle', template.jobTitle)
353+
applyField('streetAddress', template.streetAddress)
354+
applyField('city', template.city)
355+
applyField('state', template.state)
356+
applyField('postalCode', template.postalCode)
357+
applyField('country', template.country)
358+
applyField('companyName', template.companyName)
359+
applyField('department', template.department)
360+
applyField('mobilePhone', template.mobilePhone)
361+
362+
const templateBusinessPhone = Array.isArray(template.businessPhones)
363+
? template.businessPhones[0]
364+
: template.businessPhones
365+
applyField('businessPhones', templateBusinessPhone ? [templateBusinessPhone] : [], [])
366+
367+
// Licenses - match the format expected by CippFormLicenseSelector
368+
applyField(
369+
'licenses',
370+
Array.isArray(template.licenses) ? template.licenses : [],
371+
[]
372+
)
373+
374+
// Groups from template
375+
const templateGroups = template.addToGroups || template.groupMemberships
376+
const rawGroups = templateGroups
377+
? Array.isArray(templateGroups)
378+
? templateGroups
379+
: [templateGroups]
380+
: []
381+
const groups = rawGroups.map((g) => {
382+
if (g.label && g.value) return g
383+
const groupType = g.groupTypes?.includes('Unified')
384+
? 'Microsoft 365'
385+
: g.mailEnabled && !g.groupTypes?.includes('Unified')
386+
? g.securityEnabled
387+
? 'Mail-Enabled Security'
388+
: 'Distribution list'
389+
: 'Security'
390+
return {
391+
label: g.displayName,
392+
value: g.id,
393+
addedFields: { groupType },
377394
}
395+
})
396+
applyField('AddToGroups', groups, [])
378397

379-
// Populate custom user attributes from template
380-
if (template.defaultAttributes) {
381-
Object.entries(template.defaultAttributes).forEach(([key, attr]) => {
382-
if (attr?.Value) {
383-
setFieldIfEmpty(`defaultAttributes.${key}.Value`, attr.Value)
384-
}
398+
// Custom user attributes. On a switch, clear every known attribute field
399+
// first so attributes the new template doesn't define don't linger, then
400+
// apply the template's values.
401+
if (isSwitch) {
402+
userSettingsDefaults?.userAttributes
403+
?.filter((attribute) => attribute.value !== 'sponsor')
404+
.forEach((attribute) => {
405+
formControl.setValue(`defaultAttributes.${attribute.label}.Value`, '', {
406+
shouldDirty: true,
407+
})
385408
})
386-
}
409+
}
410+
if (template.defaultAttributes) {
411+
Object.entries(template.defaultAttributes).forEach(([key, attr]) => {
412+
applyField(`defaultAttributes.${key}.Value`, attr?.Value)
413+
})
387414
}
388415
}, [watchedFields.userTemplate, formType])
389416

0 commit comments

Comments
 (0)