project-booster-vue 9.54.0 → 9.55.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.
Files changed (28) hide show
  1. package/package.json +1 -1
  2. package/src/components/rework/question/city-search/MPbCitySearch.vue +33 -52
  3. package/src/components/rework/question/name-input/MPbNameInput.stories.mdx +308 -0
  4. package/src/components/rework/question/name-input/MPbNameInput.vue +349 -0
  5. package/src/components/rework/question/name-input/NameInput.ts +22 -0
  6. package/src/components/rework/question/name-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-name-input-/360/237/246/240-101-sandbox-1-snap.png +0 -0
  7. package/src/components/rework/question/name-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-name-input-/360/237/246/240-showcase-feature-customize-1-snap.png +0 -0
  8. package/src/components/rework/question/name-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-name-input-/360/237/246/240-showcase-feature-optin-1-snap.png +0 -0
  9. package/src/components/rework/question/name-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-name-input-/360/237/246/240-showcase-feature-previous-value-from-answer-1-snap.png +0 -0
  10. package/src/components/rework/question/name-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-name-input-/360/237/246/240-showcase-feature-previous-value-from-answer-with-optin-1-snap.png +0 -0
  11. package/src/components/rework/question/name-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-name-input-/360/237/246/240-showcase-feature-previous-value-from-default-1-snap.png +0 -0
  12. package/src/components/rework/question/name-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-name-input-/360/237/246/240-showcase-feature-previous-value-from-default-with-optin-1-snap.png +0 -0
  13. package/src/components/rework/question/name-input/default-payload.json +13 -0
  14. package/src/components/rework/question/space-input/MPbSpaceInput.stories.mdx +297 -0
  15. package/src/components/rework/question/space-input/MPbSpaceInput.vue +511 -0
  16. package/src/components/rework/question/space-input/SpaceInput.ts +29 -0
  17. package/src/components/rework/question/space-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-space-input-/360/237/246/240-101-sandbox-1-snap.png +0 -0
  18. package/src/components/rework/question/space-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-space-input-/360/237/246/240-showcase-feature-custom-footer-1-snap.png +0 -0
  19. package/src/components/rework/question/space-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-space-input-/360/237/246/240-showcase-feature-customize-1-snap.png +0 -0
  20. package/src/components/rework/question/space-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-space-input-/360/237/246/240-showcase-feature-previous-value-from-answer-1-snap.png +0 -0
  21. package/src/components/rework/question/space-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-space-input-/360/237/246/240-showcase-feature-previous-value-from-default-1-snap.png +0 -0
  22. package/src/components/rework/question/space-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-space-input-/360/237/246/240-showcase-feature-with-modal-1-snap.png +0 -0
  23. package/src/components/rework/question/space-input/default-payload.json +15 -0
  24. package/src/components/rework/ui/progress/MPbProgress.vue +4 -1
  25. package/src/components/scenario/PbScenario.vue +5 -0
  26. package/src/components/scenario/scenarii/floor-insulation.json +31 -13
  27. package/src/components/trezor/PbTrezor.vue +12 -2
  28. package/src/components/trezor/default-payload.json +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-booster-vue",
3
- "version": "9.54.0",
3
+ "version": "9.55.1",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -115,7 +115,7 @@ const BACK_ICON =
115
115
  'https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Arrow_Arrow--Left_16px.svg';
116
116
 
117
117
  export default defineComponent({
118
- name: 'MPbCitySearch',
118
+ name: 'PbCitySearch',
119
119
 
120
120
  components: {
121
121
  MFlex,
@@ -257,16 +257,12 @@ export default defineComponent({
257
257
  updateHeaderHeight() {
258
258
  this.headerHeight = this.$refs?.pbCitySearchHeader?.$el?.offsetHeight;
259
259
  },
260
- formatSuggestion(suggestion: any) {
261
- if (suggestion.name) {
262
- return suggestion.name;
263
- } else {
264
- let text = '';
265
- for (let i = 0; i < suggestion.terms.length - 1; i++) {
266
- text = `${text} ${suggestion.terms[i].value}`;
267
- }
268
- return text;
260
+ formatSuggestion(suggestion: google.maps.places.AutocompletePrediction) {
261
+ let text = '';
262
+ for (let i = 0; i < suggestion.terms.length - 1; i++) {
263
+ text = `${text} ${suggestion.terms[i].value}`;
269
264
  }
265
+ return text;
270
266
  },
271
267
  handleSearchKeywordChange() {
272
268
  this.updateHeaderHeight();
@@ -293,39 +289,24 @@ export default defineComponent({
293
289
  return;
294
290
  }
295
291
 
296
- try {
297
- this.loadingSuggestions = false;
298
- this.displaySuggestions = true;
299
-
300
- if (Number.isNaN(parseInt(this.searchKeyword))) {
301
- const request = {
302
- input: this.searchKeyword,
303
- types: ['(regions)'],
304
- componentRestrictions: {
305
- country: 'fr',
306
- },
307
- };
308
-
309
- this.googlePlacesAutocompleteService?.getPlacePredictions(request, (results, status) => {
310
- this.loadingSuggestions = false;
311
- this.displaySuggestions = true;
312
- if (status === 'OK') {
313
- this.suggestions = results;
314
- } else if (status === 'ZERO_RESULTS') {
315
- this.suggestions = null;
316
- }
317
- });
318
- } else {
319
- const results = await this.getPlaceFromGeoGouvAPI(this.searchKeyword);
292
+ const request = {
293
+ input: this.searchKeyword,
294
+ types: ['(regions)'],
295
+ componentRestrictions: {
296
+ country: 'fr',
297
+ },
298
+ };
320
299
 
300
+ try {
301
+ this.googlePlacesAutocompleteService?.getPlacePredictions(request, (results, status) => {
321
302
  this.loadingSuggestions = false;
322
303
  this.displaySuggestions = true;
323
- if (results) {
304
+ if (status === 'OK') {
324
305
  this.suggestions = results;
325
- } else {
306
+ } else if (status === 'ZERO_RESULTS') {
326
307
  this.suggestions = null;
327
308
  }
328
- }
309
+ });
329
310
  } catch (error) {
330
311
  console.error(`Could not get city search suggestions for ${this.searchKeyword}`, error);
331
312
  }
@@ -352,10 +333,14 @@ export default defineComponent({
352
333
  const selectedCity = results[0];
353
334
  this.suggestions = null;
354
335
 
355
- const postalCode = selectedCity.address_components.filter((field) => field.types.includes('postal_code'))[0]
356
- ?.short_name;
336
+ const placeData = await this.getPlaceFromGeoGouvAPI(
337
+ selectedCity.geometry.location.lat(),
338
+ selectedCity.geometry.location.lng(),
339
+ );
357
340
 
358
- const placeData: any = await this.getPlaceFromGeoGouvAPI(postalCode);
341
+ const postalCode =
342
+ selectedCity.address_components.filter((field) => field.types.includes('postal_code'))[0]?.short_name ||
343
+ placeData.postalCode;
359
344
 
360
345
  const region =
361
346
  selectedCity.address_components.filter((field) => field.types.includes('administrative_area_level_1'))[0]
@@ -389,26 +374,22 @@ export default defineComponent({
389
374
  ],
390
375
  });
391
376
  },
392
- async getPlaceFromGeoGouvAPI(codePostal: string) {
377
+ async getPlaceFromGeoGouvAPI(lat: number, lng: number) {
393
378
  const placeData = await axios.get('https://geo.api.gouv.fr/communes', {
394
379
  params: {
395
- codePostal: codePostal,
380
+ lat: lat,
381
+ lon: lng,
396
382
  fields: 'nom,code,codesPostaux',
397
383
  format: 'json',
398
384
  geometry: 'centre',
399
385
  },
400
386
  });
401
- let placeDataFormatted: any = [];
402
387
 
403
- placeData.data.forEach((element: any) => {
404
- placeDataFormatted.push({
405
- inseeCode: element.code,
406
- postalCode: element.codesPostaux[0],
407
- name: element.nom,
408
- });
409
- });
410
-
411
- return placeDataFormatted;
388
+ return {
389
+ inseeCode: placeData.data[0].code,
390
+ postalCode: placeData.data[0].codesPostaux[0],
391
+ name: placeData.data[0].nom,
392
+ };
412
393
  },
413
394
  selectedCityToKeyword(): string {
414
395
  return this.selectedCity?.postalCode + ' ' + this.selectedCity?.name + ', ' + this.selectedCity?.region;
@@ -0,0 +1,308 @@
1
+ import { nestedAppDecorator } from '../../../../../.storybook/nested-app-decorator';
2
+ import { Story, Preview, Meta, Props, ArgsTable, Source, Canvas } from '@storybook/addon-docs';
3
+ import MPbNameInput from './MPbNameInput';
4
+ import DEFAULT_PAYLOAD from './default-payload.json';
5
+ import dedent from 'ts-dedent';
6
+
7
+ <Meta
8
+ title="Project Booster/Rework/Questions/MPbNameInput 🦠"
9
+ component={MPbNameInput}
10
+ argTypes={{
11
+ 'payload': {
12
+ table: {
13
+ defaultValue: {
14
+ summary: 'object',
15
+ detail: JSON.stringify(DEFAULT_PAYLOAD, null, ' '),
16
+ },
17
+ },
18
+ control: {
19
+ type: 'object',
20
+ },
21
+ },
22
+ 'dynamic event name according to completedEventName prop': {
23
+ table: {
24
+ defaultValue: {
25
+ summary: 'Event payload',
26
+ detail: JSON.stringify({ answers: [{ projectName: 'My project name', optin: false }] }, null, ' '),
27
+ },
28
+ },
29
+ },
30
+ 'onStepCompleted': {
31
+ action: 'Step completed',
32
+ table: { disable: true },
33
+ },
34
+ }}
35
+ decorators={[
36
+ nestedAppDecorator(
37
+ {
38
+ actions: {
39
+ sendEventToBus({}, payload) {
40
+ console.log('Event sent to bus', payload);
41
+ },
42
+ },
43
+ },
44
+ [],
45
+ ),
46
+ ]}
47
+ parameters={{ layout: 'fullscreen' }}
48
+ />
49
+
50
+ # 🦠 `MPbNameInput` - Components
51
+
52
+ [![alt text](https://storage.googleapis.com/project-booster-media/project-booster-vue/project-booster-sources.svg 'Project booster component source code')](https://github.com/adeo/project-booster-vue/tree/master/src/components/question)
53
+
54
+ ---
55
+
56
+ The `MPbNameInput` Vue component allows to ask for a name. It
57
+ is customizable through the payload property. It has the same
58
+ behaviour as the `PbQuestion` component and is designed to work in scenarii.
59
+
60
+ It also provides the debrayable optin collect feature.
61
+
62
+ It has a default provided payload, used in the Sandbox below.
63
+
64
+ The input field is automatically focused.
65
+
66
+ The back button will make `vue-router` trigger the history back feature from the browser. It can be hidden with the
67
+ `showBackButton` flag.
68
+
69
+ # `MPbNameInput` - Component props
70
+
71
+ export const TemplateSandbox = (args, { argTypes }) => ({
72
+ props: Object.keys(argTypes),
73
+ components: { MPbNameInput },
74
+ setup() {
75
+ return { args };
76
+ },
77
+ template: `<m-pb-name-input :payload="args.payload" :show-back-button="args.showBackButton" :answers="args.answers" :runtime-options="args.runtimeOptions" :completed-event-name="args.completedEventName" @step-completed="args.onStepCompleted" />`,
78
+ });
79
+
80
+ <Canvas>
81
+ <Story name="101 Sandbox" args={{ payload: DEFAULT_PAYLOAD }}>
82
+ {TemplateSandbox.bind({})}
83
+ </Story>
84
+ </Canvas>
85
+
86
+ <ArgsTable story="101 Sandbox" />
87
+
88
+ # `MPbNameInput` - Features
89
+
90
+ ## Customization
91
+
92
+ It is possible to customize the component default behaviour by **overriding** a property in the provided `payload` property.
93
+
94
+ Here is the default payload:
95
+
96
+ <Source language="json" code={JSON.stringify(DEFAULT_PAYLOAD, null, ' ')} />
97
+
98
+ Here is the payload with overriden values:
99
+
100
+ export const overridenLabelsPayload = {
101
+ viewModel: {
102
+ backLabel: 'Retour',
103
+ label: 'Quelle est le montant de votre bien ?',
104
+ placeholder: 'Montant de votre bien',
105
+ actionLabel: 'Enregistrer',
106
+ },
107
+ };
108
+
109
+ <Source language="json" code={JSON.stringify(overridenLabelsPayload, null, ' ')} />
110
+
111
+ <Canvas>
112
+ <Story
113
+ name="Showcase - Feature customize"
114
+ parameters={{ controls: { disable: true } }}
115
+ args={{ payload: overridenLabelsPayload }}
116
+ >
117
+ {TemplateSandbox.bind({})}
118
+ </Story>
119
+ </Canvas>
120
+
121
+ ## Optin management with runtime options
122
+
123
+ The optin toggle display is managed with the `runtimeOptions` property :
124
+
125
+ export const runtimeOptions = {
126
+ displayOptin: true,
127
+ };
128
+
129
+ <Source language="json" code={JSON.stringify(runtimeOptions, null, ' ')} />
130
+
131
+ <Canvas>
132
+ <Story
133
+ name="Showcase - Feature Optin"
134
+ parameters={{ controls: { disable: true } }}
135
+ args={{ runtimeOptions: runtimeOptions }}
136
+ >
137
+ {TemplateSandbox.bind({})}
138
+ </Story>
139
+ </Canvas>
140
+
141
+ ## Value injection from a previous answer with default value fallback
142
+
143
+ It is possible to inject a value for the component from a previous answer. To do so, add a `value` to the component
144
+ `payload`. The value should have the same structure as the component anwser when completed (See event payload in the
145
+ [Sandbox story](#pbnameinput---component-props)) :
146
+
147
+ export const valuePayload = {
148
+ value: {
149
+ projectName: "${getAnswerValue('LMFR_PREVIOUS_QUESTION', 'pathForProjectName')}",
150
+ optin: "${getAnswerValue('LMFR_PREVIOUS_QUESTION', 'pathForOptin')}",
151
+ },
152
+ };
153
+
154
+ <Source language="json" code={JSON.stringify(valuePayload, null, ' ')} />
155
+
156
+ The `getAnswerValue` function allows to retrieve a value from one provided answer. To retrieve a value, the first
157
+ parameter is the answer Id, the second is an optionnal path to extract a spcific value.
158
+
159
+ A `defaultDecoratorValue` value can be added as a fallback when no previous answer is found. When no default value is provided and no answer
160
+ is found, the component value will be empty :
161
+
162
+ export const valueWithFallbackPayload = {
163
+ defaultDecoratorValue: {
164
+ projectName: 'Rénover la salle de bain',
165
+ optin: true,
166
+ },
167
+ value: {
168
+ projectName: "${getAnswerValue('LMFR_PREVIOUS_QUESTION', 'pathForProjectName') || defaultValue.projectName}",
169
+ optin: "${getAnswerValue('LMFR_PREVIOUS_QUESTION', 'pathForOptin') || defaultValue.optin}",
170
+ },
171
+ };
172
+
173
+ <Source language="json" code={JSON.stringify(valueWithFallbackPayload, null, ' ')} />
174
+
175
+ It will try to find a value in a previous answer for a question with id `LMFR_PREVIOUS_QUESTION`,
176
+ provided thanks to the `answers` prop. If no value is found, it will use the `defaultDecoratorValue` as a fallback.
177
+
178
+ export const previousAnswers = new Map(
179
+ Object.entries({
180
+ LMFR_PREVIOUS_QUESTION: [
181
+ {
182
+ pathForProjectName: 'Rénover la cuisine',
183
+ },
184
+ ],
185
+ }),
186
+ );
187
+
188
+ <Source language="json" code={JSON.stringify(previousAnswers, null, ' ')} />
189
+
190
+ <Canvas>
191
+ <Story
192
+ name="Showcase - Feature previous value from answer with optin"
193
+ parameters={{ controls: { disable: true } }}
194
+ args={{
195
+ payload: valuePayload,
196
+ answers: previousAnswers,
197
+ }}
198
+ >
199
+ {TemplateSandbox.bind({})}
200
+ </Story>
201
+ </Canvas>
202
+
203
+ When no answer is found, the default value fallback is used:
204
+
205
+ <Canvas>
206
+ <Story
207
+ name="Showcase - Feature previous value from default with optin"
208
+ parameters={{ controls: { disable: true } }}
209
+ args={{ payload: valueWithFallbackPayload }}
210
+ >
211
+ {TemplateSandbox.bind({})}
212
+ </Story>
213
+ </Canvas>
214
+
215
+ With optin value :
216
+
217
+ `runtimeOptions` prop to add option :
218
+
219
+ <Source language="json" code={JSON.stringify(runtimeOptions, null, ' ')} />
220
+
221
+ `answers` prop to add a previous answer :
222
+
223
+ export const previousAnswersWithOptin = {
224
+ LMFR_PREVIOUS_QUESTION: [
225
+ {
226
+ pathForProjectName: 'Rénover la cuisine',
227
+ pathForOptin: true,
228
+ },
229
+ ],
230
+ };
231
+
232
+ <Source language="json" code={JSON.stringify(previousAnswersWithOptin, null, ' ')} />
233
+
234
+ <Canvas>
235
+ <Story
236
+ name="Showcase - Feature previous value from answer"
237
+ parameters={{ controls: { disable: true } }}
238
+ args={{
239
+ payload: valuePayload,
240
+ answers: new Map(Object.entries(previousAnswersWithOptin)),
241
+ runtimeOptions: runtimeOptions,
242
+ }}
243
+ >
244
+ {TemplateSandbox.bind({})}
245
+ </Story>
246
+ </Canvas>
247
+
248
+ When no answer is found, the default value fallback is used:
249
+
250
+ export const valueWithFallbackPayloadAndOptin = {
251
+ defaultDecoratorValue: {
252
+ projectName: 'Rénover la salle de bain',
253
+ optin: true,
254
+ },
255
+ value: {
256
+ projectName: "${getAnswerValue('LMFR_PREVIOUS_QUESTION', 'pathForProjectName') || defaultValue.projectName}",
257
+ optin: "${getAnswerValue('LMFR_PREVIOUS_QUESTION', 'pathForOptin') || defaultValue.optin}",
258
+ },
259
+ };
260
+
261
+ <Source language="json" code={JSON.stringify(valueWithFallbackPayloadAndOptin, null, ' ')} />
262
+
263
+ <Canvas>
264
+ <Story
265
+ name="Showcase - Feature previous value from default"
266
+ parameters={{ controls: { disable: true } }}
267
+ args={{
268
+ payload: valueWithFallbackPayloadAndOptin,
269
+ runtimeOptions: runtimeOptions,
270
+ }}
271
+ >
272
+ {TemplateSandbox.bind({})}
273
+ </Story>
274
+ </Canvas>
275
+
276
+ ## Custom validation
277
+
278
+ It is possible to customize the input field validation. To do so, provide a `validation` property to the `viewModel`.
279
+
280
+ The default `validation` object is:
281
+
282
+ <Source language="json" code={JSON.stringify(DEFAULT_PAYLOAD.viewModel.validation, null, ' ')} />
283
+
284
+ It is possible to customize those values:
285
+
286
+ export const customValidation = {
287
+ viewModel: {
288
+ validation: {
289
+ minLength: 5,
290
+ minErrorMessage: 'Plus de 5 caractères',
291
+ maxLength: 10,
292
+ maxErrorMessage: 'Moins de 10 caractères',
293
+ requiredErrorMessage: 'Champ obligatoire',
294
+ },
295
+ },
296
+ };
297
+
298
+ <Source language="json" code={JSON.stringify(customValidation, null, ' ')} />
299
+
300
+ <Canvas>
301
+ <Story
302
+ name="Showcase - Feature custom validation"
303
+ parameters={{ controls: { disable: true }, storyshots: { disable: true } }}
304
+ args={{ payload: customValidation }}
305
+ >
306
+ {TemplateSandbox.bind({})}
307
+ </Story>
308
+ </Canvas>