cozy-ui 128.2.0 → 128.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/package.json +6 -2
- package/react/Contacts/AddModal/ContactAddressDialog/helpers.js +22 -0
- package/react/Contacts/AddModal/ContactAddressDialog/helpers.spec.js +64 -0
- package/react/Contacts/AddModal/ContactAddressDialog/index.jsx +84 -0
- package/react/Contacts/AddModal/ContactAddressDialog/locales/en.json +25 -0
- package/react/Contacts/AddModal/ContactAddressDialog/locales/fr.json +25 -0
- package/react/Contacts/AddModal/ContactAddressDialog/locales/index.jsx +7 -0
- package/react/Contacts/AddModal/ContactForm/FieldInput.jsx +117 -0
- package/react/Contacts/AddModal/ContactForm/FieldInputArray.jsx +80 -0
- package/react/Contacts/AddModal/ContactForm/FieldInputLayout.jsx +65 -0
- package/react/Contacts/AddModal/ContactForm/FieldInputWrapper.jsx +41 -0
- package/react/Contacts/AddModal/ContactForm/HasValueCondition.jsx +31 -0
- package/react/Contacts/AddModal/ContactForm/HasValueCondition.spec.jsx +79 -0
- package/react/Contacts/AddModal/ContactForm/RelatedContactList.jsx +37 -0
- package/react/Contacts/AddModal/ContactForm/TextFieldCustomLabelSelect.jsx +78 -0
- package/react/Contacts/AddModal/ContactForm/TextFieldSelect.jsx +39 -0
- package/react/Contacts/AddModal/ContactForm/__snapshots__/HasValueCondition.spec.jsx.snap +33 -0
- package/react/Contacts/AddModal/ContactForm/contactToFormValues.js +99 -0
- package/react/Contacts/AddModal/ContactForm/contactToFormValues.spec.js +128 -0
- package/react/Contacts/AddModal/ContactForm/fieldsConfig.jsx +341 -0
- package/react/Contacts/AddModal/ContactForm/formValuesToContact.js +100 -0
- package/react/Contacts/AddModal/ContactForm/formValuesToContact.spec.js +494 -0
- package/react/Contacts/AddModal/ContactForm/helpers.js +324 -0
- package/react/Contacts/AddModal/ContactForm/helpers.spec.js +152 -0
- package/react/Contacts/AddModal/ContactForm/index.jsx +104 -0
- package/react/Contacts/AddModal/ContactForm/index.spec.jsx +289 -0
- package/react/Contacts/AddModal/ContactForm/locales/en.json +73 -0
- package/react/Contacts/AddModal/ContactForm/locales/fr.json +73 -0
- package/react/Contacts/AddModal/ContactForm/locales/index.jsx +7 -0
- package/react/Contacts/AddModal/ContactForm/styles.styl +2 -0
- package/react/Contacts/AddModal/CustomLabelDialog/index.jsx +108 -0
- package/react/Contacts/AddModal/CustomLabelDialog/locales/en.json +15 -0
- package/react/Contacts/AddModal/CustomLabelDialog/locales/fr.json +15 -0
- package/react/Contacts/AddModal/CustomLabelDialog/locales/index.jsx +7 -0
- package/react/Contacts/AddModal/Readme.md +46 -0
- package/react/Contacts/AddModal/index.jsx +78 -0
- package/react/Contacts/AddModal/locales/en.json +13 -0
- package/react/Contacts/AddModal/locales/fr.json +13 -0
- package/react/Contacts/AddModal/locales/index.jsx +7 -0
- package/react/Contacts/AddModal/mocks.js +249 -0
- package/react/Contacts/AddModal/types.js +57 -0
- package/react/Contacts/Header/Readme.md +0 -2
- package/react/providers/DemoProvider.jsx +3 -2
- package/transpiled/react/Contacts/AddModal/ContactAddressDialog/helpers.d.ts +4 -0
- package/transpiled/react/Contacts/AddModal/ContactAddressDialog/helpers.js +20 -0
- package/transpiled/react/Contacts/AddModal/ContactAddressDialog/helpers.spec.d.ts +1 -0
- package/transpiled/react/Contacts/AddModal/ContactAddressDialog/index.d.ts +39 -0
- package/transpiled/react/Contacts/AddModal/ContactAddressDialog/index.js +87 -0
- package/transpiled/react/Contacts/AddModal/ContactAddressDialog/locales/index.d.ts +6 -0
- package/transpiled/react/Contacts/AddModal/ContactAddressDialog/locales/index.js +54 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/FieldInput.d.ts +35 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/FieldInput.js +126 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputArray.d.ts +14 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputArray.js +82 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputLayout.d.ts +20 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputLayout.js +70 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputWrapper.d.ts +16 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputWrapper.js +31 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/HasValueCondition.d.ts +18 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/HasValueCondition.js +32 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/HasValueCondition.spec.d.ts +1 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/RelatedContactList.d.ts +15 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/RelatedContactList.js +39 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/TextFieldCustomLabelSelect.d.ts +9 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/TextFieldCustomLabelSelect.js +81 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/TextFieldSelect.d.ts +5 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/TextFieldSelect.js +42 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/contactToFormValues.d.ts +2 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/contactToFormValues.js +88 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/contactToFormValues.spec.d.ts +1 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/fieldsConfig.d.ts +4 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/fieldsConfig.js +278 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/formValuesToContact.d.ts +6 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/formValuesToContact.js +94 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/formValuesToContact.spec.d.ts +1 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/helpers.d.ts +28 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/helpers.js +335 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/helpers.spec.d.ts +1 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/index.d.ts +11 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/index.js +114 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/index.spec.d.ts +1 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/locales/index.d.ts +6 -0
- package/transpiled/react/Contacts/AddModal/ContactForm/locales/index.js +150 -0
- package/transpiled/react/Contacts/AddModal/CustomLabelDialog/index.d.ts +22 -0
- package/transpiled/react/Contacts/AddModal/CustomLabelDialog/index.js +113 -0
- package/transpiled/react/Contacts/AddModal/CustomLabelDialog/locales/index.d.ts +6 -0
- package/transpiled/react/Contacts/AddModal/CustomLabelDialog/locales/index.js +34 -0
- package/transpiled/react/Contacts/AddModal/index.d.ts +7 -0
- package/transpiled/react/Contacts/AddModal/index.js +109 -0
- package/transpiled/react/Contacts/AddModal/locales/index.d.ts +6 -0
- package/transpiled/react/Contacts/AddModal/locales/index.js +30 -0
- package/transpiled/react/Contacts/AddModal/mocks.d.ts +270 -0
- package/transpiled/react/Contacts/AddModal/mocks.js +214 -0
- package/transpiled/react/Contacts/AddModal/types.d.ts +54 -0
- package/transpiled/react/Contacts/AddModal/types.js +49 -0
- package/transpiled/react/providers/DemoProvider.d.ts +2 -1
- package/transpiled/react/providers/DemoProvider.js +7 -3
- package/transpiled/react/stylesheet.css +1 -1
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import get from 'lodash/get'
|
|
2
|
+
import isEqual from 'lodash/isEqual'
|
|
3
|
+
import merge from 'lodash/merge'
|
|
4
|
+
import uniqueId from 'lodash/uniqueId'
|
|
5
|
+
|
|
6
|
+
import { Association } from 'cozy-client'
|
|
7
|
+
import { makeDisplayName } from 'cozy-client/dist/models/contact'
|
|
8
|
+
import { CONTACTS_DOCTYPE } from 'cozy-client/dist/models/contact'
|
|
9
|
+
|
|
10
|
+
import contactToFormValues from './contactToFormValues'
|
|
11
|
+
|
|
12
|
+
export const fieldsRequired = [
|
|
13
|
+
'givenName',
|
|
14
|
+
'familyName',
|
|
15
|
+
'email[0].email',
|
|
16
|
+
'cozy'
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns errors if all required fields are empty
|
|
21
|
+
* @param {object} values - Fields values
|
|
22
|
+
* @param {func} t - Translation function
|
|
23
|
+
* @returns {object} Errors
|
|
24
|
+
*/
|
|
25
|
+
export const validateFields = (values, t) => {
|
|
26
|
+
const errors = {}
|
|
27
|
+
if (fieldsRequired.every(field => !get(values, field))) {
|
|
28
|
+
fieldsRequired.forEach(field => {
|
|
29
|
+
errors[field] = t('Contacts.AddModal.ContactForm.fields.required')
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
return errors
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {object} [item] - Contact attribute
|
|
37
|
+
* @returns {string} Stringified object
|
|
38
|
+
*/
|
|
39
|
+
export const makeItemLabel = item => {
|
|
40
|
+
if (!item) return undefined
|
|
41
|
+
|
|
42
|
+
const res =
|
|
43
|
+
item.label || item.type
|
|
44
|
+
? JSON.stringify({ type: item.type, label: item.label })
|
|
45
|
+
: undefined
|
|
46
|
+
|
|
47
|
+
return res
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @param {string} [itemLabel] - Value of the label for a contact attribute
|
|
53
|
+
* @returns {{ type?: string, label?: string }}
|
|
54
|
+
*/
|
|
55
|
+
export const makeTypeAndLabel = itemLabel => {
|
|
56
|
+
if (!itemLabel) {
|
|
57
|
+
return { type: undefined, label: undefined }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const itemLabelObj = JSON.parse(itemLabel)
|
|
61
|
+
|
|
62
|
+
const res = { type: itemLabelObj.type, label: itemLabelObj.label }
|
|
63
|
+
|
|
64
|
+
return res
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {object} addressField
|
|
69
|
+
* @returns {boolean} True if addressField has extended address
|
|
70
|
+
*/
|
|
71
|
+
export const hasExtendedAddress = addressField => {
|
|
72
|
+
if (!addressField) return false
|
|
73
|
+
const extendedAddressKeys = [
|
|
74
|
+
'addresslocality',
|
|
75
|
+
'addressbuilding',
|
|
76
|
+
'addressstairs',
|
|
77
|
+
'addressfloor',
|
|
78
|
+
'addressapartment',
|
|
79
|
+
'addressentrycode'
|
|
80
|
+
]
|
|
81
|
+
return Object.keys(addressField).some(ext =>
|
|
82
|
+
extendedAddressKeys.includes(ext)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const moveToHead = shouldBeHead => items =>
|
|
87
|
+
items.reduce((arr, v) => (shouldBeHead(v) ? [v, ...arr] : [...arr, v]), [])
|
|
88
|
+
|
|
89
|
+
export const movePrimaryToHead = moveToHead(v => v?.primary)
|
|
90
|
+
|
|
91
|
+
export const createAddress = ({ address, oldContact, t }) => {
|
|
92
|
+
return address
|
|
93
|
+
? address
|
|
94
|
+
.filter(val => val && val.address)
|
|
95
|
+
.map((addressField, index) => {
|
|
96
|
+
const oldContactAddress = oldContact?.address?.[index]
|
|
97
|
+
const oldContactFormValues = contactToFormValues(oldContact, t)
|
|
98
|
+
?.address?.[index]
|
|
99
|
+
|
|
100
|
+
const addressHasBeenModified = !isEqual(
|
|
101
|
+
addressField,
|
|
102
|
+
oldContactFormValues
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if (addressHasBeenModified) {
|
|
106
|
+
// Use "code" instead "postcode", to be vcard 4.0 rfc 6350 compliant
|
|
107
|
+
// eslint-disable-next-line no-unused-vars
|
|
108
|
+
const { postcode, ...oldContactAddressCleaned } =
|
|
109
|
+
oldContactAddress || {}
|
|
110
|
+
return {
|
|
111
|
+
// For keep other properties form connectors
|
|
112
|
+
...oldContactAddressCleaned,
|
|
113
|
+
formattedAddress: addressField.address,
|
|
114
|
+
number: addressField.addressnumber,
|
|
115
|
+
street: addressField.addressstreet,
|
|
116
|
+
code: addressField.addresscode,
|
|
117
|
+
city: addressField.addresscity,
|
|
118
|
+
region: addressField.addressregion,
|
|
119
|
+
country: addressField.addresscountry,
|
|
120
|
+
...(hasExtendedAddress(addressField) && {
|
|
121
|
+
extendedAddress: {
|
|
122
|
+
...oldContactAddressCleaned.extendedAddress,
|
|
123
|
+
locality: addressField.addresslocality,
|
|
124
|
+
building: addressField.addressbuilding,
|
|
125
|
+
stairs: addressField.addressstairs,
|
|
126
|
+
floor: addressField.addressfloor,
|
|
127
|
+
apartment: addressField.addressapartment,
|
|
128
|
+
entrycode: addressField.addressentrycode
|
|
129
|
+
}
|
|
130
|
+
}),
|
|
131
|
+
...makeTypeAndLabel(addressField.addressLabel),
|
|
132
|
+
primary: index === 0
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return oldContactAddress
|
|
136
|
+
})
|
|
137
|
+
: []
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {(import('../../../types').RelatedContact|undefined)[]} relatedContact - The related contacts array
|
|
142
|
+
* @returns {Record<string, { data: { _id: string, _type: string }[] }>} - The related contacts relationships
|
|
143
|
+
*/
|
|
144
|
+
export const getRelatedContactRelationships = relatedContact => {
|
|
145
|
+
// Tips filter Boolean to remove undefined value from array when relatedContact is empty (see contactToFormValues)
|
|
146
|
+
const data = relatedContact.filter(Boolean).reduce((acc, curr) => {
|
|
147
|
+
const relationType = curr.relatedContactLabel
|
|
148
|
+
? JSON.parse(curr.relatedContactLabel).type
|
|
149
|
+
: 'related'
|
|
150
|
+
|
|
151
|
+
const existingIndex = acc.findIndex(
|
|
152
|
+
item => item._id === curr.relatedContactId
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if (existingIndex !== -1) {
|
|
156
|
+
acc[existingIndex].metadata.relationTypes = Array.from(
|
|
157
|
+
new Set([...acc[existingIndex].metadata.relationTypes, relationType])
|
|
158
|
+
)
|
|
159
|
+
} else {
|
|
160
|
+
acc.push({
|
|
161
|
+
_id: curr.relatedContactId,
|
|
162
|
+
_type: CONTACTS_DOCTYPE,
|
|
163
|
+
metadata: {
|
|
164
|
+
relationTypes: [relationType]
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
return acc
|
|
169
|
+
}, [])
|
|
170
|
+
|
|
171
|
+
// `data` can be empty, you still have to return an object to override the behavior of the cozy-client store, otherwise it will keep the old value, and without refreshing the page, the data will not be up to date in the store and therefore on the interface
|
|
172
|
+
return { related: { data } }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* When changing the type of relationship, it must be ensured that no empty relationship remains.
|
|
177
|
+
* The old and new ones are merged into `formValuesToContact`.
|
|
178
|
+
*
|
|
179
|
+
* @param {import('cozy-client/types/types').IOCozyContact} contact - The contact object with all relationships
|
|
180
|
+
* @returns {import('cozy-client/types/types').IOCozyContact} - The contact object without the related contacts relationships
|
|
181
|
+
*/
|
|
182
|
+
export const removeRelatedContactRelationships = contact => {
|
|
183
|
+
if (!contact?.relationships) return contact
|
|
184
|
+
const updatedContact = merge({}, contact)
|
|
185
|
+
|
|
186
|
+
const relationshipsWithoutRelatedContact = Object.entries(
|
|
187
|
+
updatedContact.relationships
|
|
188
|
+
).reduce((acc, [relName, relValue]) => {
|
|
189
|
+
if ('related' === relName) {
|
|
190
|
+
acc[relName] = relValue
|
|
191
|
+
}
|
|
192
|
+
return acc
|
|
193
|
+
}, {})
|
|
194
|
+
|
|
195
|
+
updatedContact.relationships = relationshipsWithoutRelatedContact
|
|
196
|
+
|
|
197
|
+
return updatedContact
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// TODO : Update dehydrate function to HasMany class in cozy-client
|
|
201
|
+
/**
|
|
202
|
+
* This function is used to clean the contact object from the associated data
|
|
203
|
+
* cozy-client dehydrates the document before saving it (via the `HasMany` method), but by doing it manually, we ensure that all hydrated relationships in the document (and without data of course) are not saved in the `relationships` of the document, which adds unnecessary data.
|
|
204
|
+
*
|
|
205
|
+
* @param {import('cozy-client/types/types').IOCozyContact} contact - The contact object with associated data
|
|
206
|
+
* @returns {import('cozy-client/types/types').IOCozyContact} - The contact object without associated data
|
|
207
|
+
*/
|
|
208
|
+
export const removeAsscociatedData = contact => {
|
|
209
|
+
if (!contact) return {}
|
|
210
|
+
return Object.entries(contact).reduce((cleanedContact, [key, value]) => {
|
|
211
|
+
// Add `groups` condition to keep the old implementation functional, see below
|
|
212
|
+
if (!(value instanceof Association) || key === 'groups') {
|
|
213
|
+
cleanedContact[key] = value
|
|
214
|
+
}
|
|
215
|
+
return cleanedContact
|
|
216
|
+
}, {})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @param {import('cozy-client/types/types').IOCozyContact} contact
|
|
221
|
+
* @returns {import('../../../types').RelatedContact[]}
|
|
222
|
+
*/
|
|
223
|
+
export const makeRelatedContact = contact => {
|
|
224
|
+
if (
|
|
225
|
+
!(contact.related instanceof Association) ||
|
|
226
|
+
!contact.relationships?.related
|
|
227
|
+
) {
|
|
228
|
+
return [undefined]
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const relatedData = contact.related.data.reduce((acc, curr) => {
|
|
232
|
+
// Use `makeDisplayName` because if the contact is newly created, it has no `displayName` attribute. (Creation of a contact when selecting a linked contact)
|
|
233
|
+
acc[curr._id] = curr.displayName || makeDisplayName(curr)
|
|
234
|
+
return acc
|
|
235
|
+
}, {})
|
|
236
|
+
|
|
237
|
+
const res = contact.relationships.related.data.flatMap(item => {
|
|
238
|
+
return item.metadata.relationTypes.map(type => {
|
|
239
|
+
return {
|
|
240
|
+
relatedContactId: item._id,
|
|
241
|
+
relatedContact: relatedData[item._id],
|
|
242
|
+
relatedContactLabel: makeItemLabel({
|
|
243
|
+
type: type === 'related' ? '' : type
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
// Useful because a contact always has at least the `related` relationships (see `getRelatedContactRelationships`)
|
|
250
|
+
return res.length > 0 ? res : [undefined]
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export const addField = fields => fields.push({ fieldId: uniqueId('fieldId_') })
|
|
254
|
+
|
|
255
|
+
export const removeField = (fields, index) => {
|
|
256
|
+
const isLastRemainingField = fields.length === 1
|
|
257
|
+
|
|
258
|
+
if (isLastRemainingField) {
|
|
259
|
+
fields.update(index, undefined)
|
|
260
|
+
} else {
|
|
261
|
+
fields.remove(index)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
*
|
|
267
|
+
* @param {string} value
|
|
268
|
+
* @param {func} t
|
|
269
|
+
* @returns {string}
|
|
270
|
+
*/
|
|
271
|
+
export const makeCustomLabel = (value, t) => {
|
|
272
|
+
const { type, label } = JSON.parse(value)
|
|
273
|
+
|
|
274
|
+
const firstString = type || ''
|
|
275
|
+
const secondString = label
|
|
276
|
+
? type
|
|
277
|
+
? ` (${t(`Contacts.AddModal.ContactForm.label.${label}`)})`.toLowerCase()
|
|
278
|
+
: `label.${label}`
|
|
279
|
+
: ''
|
|
280
|
+
|
|
281
|
+
return firstString + secondString || null
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
*
|
|
286
|
+
* @param {string} name
|
|
287
|
+
* @param {string} value
|
|
288
|
+
* @returns {string}
|
|
289
|
+
*/
|
|
290
|
+
export const makeInitialCustomValue = (name, value) => {
|
|
291
|
+
// gender input doesn't support custom label
|
|
292
|
+
if (!name || !value || name === 'gender') return undefined
|
|
293
|
+
|
|
294
|
+
const valueObj = JSON.parse(value)
|
|
295
|
+
|
|
296
|
+
// Voluntarily before the "backwards compatibility" condition
|
|
297
|
+
if (name.includes('relatedContactLabel')) {
|
|
298
|
+
if (!'related' === valueObj.type) {
|
|
299
|
+
return JSON.stringify({ type: valueObj.type })
|
|
300
|
+
}
|
|
301
|
+
return undefined
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// for backwards compatiblity - historically there is only type and no label
|
|
305
|
+
if (valueObj.type && !valueObj.label) {
|
|
306
|
+
return JSON.stringify({ type: valueObj.type })
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// for phone label
|
|
310
|
+
if (name.includes('phoneLabel')) {
|
|
311
|
+
// but unsupported one
|
|
312
|
+
if (!['cell', 'voice', 'fax'].includes(valueObj.type)) {
|
|
313
|
+
return JSON.stringify({ type: valueObj.type, label: valueObj.label })
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// we don't want to create a custom label if supported
|
|
317
|
+
return undefined
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// at this point if label and type are both present, it's a custom label
|
|
321
|
+
if (valueObj.type && valueObj.label) {
|
|
322
|
+
return JSON.stringify({ type: valueObj.type, label: valueObj.label })
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
moveToHead,
|
|
3
|
+
makeItemLabel,
|
|
4
|
+
makeTypeAndLabel,
|
|
5
|
+
makeInitialCustomValue
|
|
6
|
+
} from './helpers'
|
|
7
|
+
|
|
8
|
+
describe('moveToHead function', () => {
|
|
9
|
+
it('should move an item to head of the array', () => {
|
|
10
|
+
const items = [1, 5, 657, 42, 3, 27, 88, 3, 4]
|
|
11
|
+
const shouldBeHead = v => v === 42
|
|
12
|
+
const expected = [42, 1, 5, 657, 3, 27, 88, 3, 4]
|
|
13
|
+
const actual = moveToHead(shouldBeHead)(items)
|
|
14
|
+
expect(actual).toEqual(expected)
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('makeItemLabel', () => {
|
|
19
|
+
it('should return undefined if no arg', () => {
|
|
20
|
+
const res = makeItemLabel()
|
|
21
|
+
|
|
22
|
+
expect(res).toBe(undefined)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should return undefined if nothing defined', () => {
|
|
26
|
+
const res = makeItemLabel({ type: undefined, label: undefined })
|
|
27
|
+
|
|
28
|
+
expect(res).toBe(undefined)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should return correct type and label', () => {
|
|
32
|
+
const res = makeItemLabel({ type: 'cell', label: 'work' })
|
|
33
|
+
|
|
34
|
+
expect(res).toBe('{"type":"cell","label":"work"}')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should return only label if no type', () => {
|
|
38
|
+
const res = makeItemLabel({ type: undefined, label: 'work' })
|
|
39
|
+
|
|
40
|
+
expect(res).toBe('{"label":"work"}')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should return only type if no label', () => {
|
|
44
|
+
const res = makeItemLabel({ type: 'cell', label: undefined })
|
|
45
|
+
|
|
46
|
+
expect(res).toBe('{"type":"cell"}')
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('makeTypeAndLabel', () => {
|
|
51
|
+
it('should return undefined if no arg', () => {
|
|
52
|
+
const res = makeTypeAndLabel()
|
|
53
|
+
|
|
54
|
+
expect(res).toStrictEqual({ type: undefined, label: undefined })
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should return correct type and label', () => {
|
|
58
|
+
const res = makeTypeAndLabel('{"type":"cell","label":"work"}')
|
|
59
|
+
|
|
60
|
+
expect(res).toStrictEqual({ type: 'cell', label: 'work' })
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should return only label', () => {
|
|
64
|
+
const res = makeTypeAndLabel('{"label":"work"}')
|
|
65
|
+
|
|
66
|
+
expect(res).toStrictEqual({ type: undefined, label: 'work' })
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should return only type', () => {
|
|
70
|
+
const res = makeTypeAndLabel('{"type":"cell"}')
|
|
71
|
+
|
|
72
|
+
expect(res).toStrictEqual({ type: 'cell', label: undefined })
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('makeInitialCustomValue', () => {
|
|
77
|
+
it('should return undefined if no name', () => {
|
|
78
|
+
const res = makeInitialCustomValue(
|
|
79
|
+
undefined,
|
|
80
|
+
'{"type":"fax","label":"work"}'
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
expect(res).toStrictEqual(undefined)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should return undefined if no value', () => {
|
|
87
|
+
const res = makeInitialCustomValue('phone[0].phoneLabel', undefined)
|
|
88
|
+
|
|
89
|
+
expect(res).toStrictEqual(undefined)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should return undefined if no name/value', () => {
|
|
93
|
+
const res = makeInitialCustomValue(undefined, undefined)
|
|
94
|
+
|
|
95
|
+
expect(res).toStrictEqual(undefined)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should return undefined for gender input', () => {
|
|
99
|
+
const res = makeInitialCustomValue(
|
|
100
|
+
'gender',
|
|
101
|
+
'{"type":"fax","label":"work"}'
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
expect(res).toStrictEqual(undefined)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should return the type if no label to ensure backwards compatibility', () => {
|
|
108
|
+
const res = makeInitialCustomValue('someInput', '{"type":"someType"}')
|
|
109
|
+
|
|
110
|
+
expect(res).toStrictEqual('{"type":"someType"}')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should return type and label if present', () => {
|
|
114
|
+
const res = makeInitialCustomValue(
|
|
115
|
+
'someInput',
|
|
116
|
+
'{"type":"someType","label":"work"}'
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
expect(res).toStrictEqual('{"type":"someType","label":"work"}')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('for phone input', () => {
|
|
123
|
+
const name = 'phone[0].phoneLabel'
|
|
124
|
+
|
|
125
|
+
it('should not return a custom label if the value is supported', () => {
|
|
126
|
+
const res = makeInitialCustomValue(name, '{"type":"fax","label":"work"}')
|
|
127
|
+
|
|
128
|
+
expect(res).toStrictEqual(undefined)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('should return the custom label', () => {
|
|
132
|
+
const res = makeInitialCustomValue(name, '{"type":"someType"}')
|
|
133
|
+
|
|
134
|
+
expect(res).toStrictEqual('{"type":"someType"}')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should return the custom label', () => {
|
|
138
|
+
const res = makeInitialCustomValue(name, '{"label":"work"}')
|
|
139
|
+
|
|
140
|
+
expect(res).toStrictEqual('{"label":"work"}')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('should return the custom value if the type is not supported even if there is a label', () => {
|
|
144
|
+
const res = makeInitialCustomValue(
|
|
145
|
+
name,
|
|
146
|
+
'{"type":"someType","label":"work"}'
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
expect(res).toStrictEqual('{"type":"someType","label":"work"}')
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import arrayMutators from 'final-form-arrays'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Form } from 'react-final-form'
|
|
4
|
+
|
|
5
|
+
import { getHasManyItems } from 'cozy-client/dist/associations/HasMany'
|
|
6
|
+
|
|
7
|
+
import FieldInputLayout from './FieldInputLayout'
|
|
8
|
+
import contactToFormValues from './contactToFormValues'
|
|
9
|
+
import { fields } from './fieldsConfig'
|
|
10
|
+
import formValuesToContact from './formValuesToContact'
|
|
11
|
+
import { validateFields } from './helpers'
|
|
12
|
+
import { locales } from './locales'
|
|
13
|
+
import { useI18n, useExtendI18n } from '../../../providers/I18n'
|
|
14
|
+
// import { fullContactPropTypes } from '../../ContactPropTypes' // !!
|
|
15
|
+
|
|
16
|
+
// this variable will be set in the form's render prop
|
|
17
|
+
// and used by the submit button in ContactFormModal
|
|
18
|
+
// to be able to trigger the submit from outside the form
|
|
19
|
+
// See react-final-form examples here: https://www.npmjs.com/package/react-final-form#external-submit
|
|
20
|
+
let _submitContactForm
|
|
21
|
+
|
|
22
|
+
function setSubmitContactForm(handleSubmit) {
|
|
23
|
+
_submitContactForm = handleSubmit
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getSubmitContactForm() {
|
|
27
|
+
return _submitContactForm
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param {object} params
|
|
33
|
+
* @param {import('cozy-client/types/types').IOCozyContact} params.contact
|
|
34
|
+
* @param {func} params.onSubmit
|
|
35
|
+
* @param {{ data: Array<object> }} params.contacts
|
|
36
|
+
* @returns
|
|
37
|
+
*/
|
|
38
|
+
const ContactForm = ({ contact, onSubmit, contacts }) => {
|
|
39
|
+
useExtendI18n(locales)
|
|
40
|
+
const { t } = useI18n()
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Form
|
|
44
|
+
mutators={{ ...arrayMutators }}
|
|
45
|
+
validate={values => validateFields(values, t)}
|
|
46
|
+
onSubmit={formValues =>
|
|
47
|
+
onSubmit(formValuesToContact({ formValues, oldContact: contact, t }))
|
|
48
|
+
}
|
|
49
|
+
initialValues={contactToFormValues(contact, t)}
|
|
50
|
+
render={({ handleSubmit, valid, submitFailed, errors }) => {
|
|
51
|
+
setSubmitContactForm(handleSubmit)
|
|
52
|
+
return (
|
|
53
|
+
<form
|
|
54
|
+
role="form"
|
|
55
|
+
onSubmit={handleSubmit}
|
|
56
|
+
className="u-flex u-flex-column"
|
|
57
|
+
>
|
|
58
|
+
{fields.map((attributes, index) => (
|
|
59
|
+
<FieldInputLayout
|
|
60
|
+
key={index}
|
|
61
|
+
attributes={attributes}
|
|
62
|
+
contacts={contacts}
|
|
63
|
+
formProps={{
|
|
64
|
+
valid,
|
|
65
|
+
submitFailed,
|
|
66
|
+
errors
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
))}
|
|
70
|
+
</form>
|
|
71
|
+
)
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Used to avoid unnecessary multiple rendering of ContactForm when creating a new contact in another way.
|
|
78
|
+
// These unnecessary renderings prevented the addition of a newly created linked contact. (Creation of a contact when selecting a linked contact)
|
|
79
|
+
export const isSameContactProp = (prevProps, nextProps) => {
|
|
80
|
+
if (!prevProps.contact?.relationships || !nextProps.contact?.relationships) {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const prevContactIdsRelated = getHasManyItems(
|
|
85
|
+
prevProps.contact,
|
|
86
|
+
'related'
|
|
87
|
+
).map(r => r._id)
|
|
88
|
+
const nextContactIdsRelated = getHasManyItems(
|
|
89
|
+
nextProps.contact,
|
|
90
|
+
'related'
|
|
91
|
+
).map(r => r._id)
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
prevContactIdsRelated.length !== nextContactIdsRelated.length ||
|
|
95
|
+
!prevContactIdsRelated.every(id => nextContactIdsRelated.includes(id))
|
|
96
|
+
) {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// export default ContactForm
|
|
104
|
+
export default React.memo(ContactForm, isSameContactProp)
|