project-booster-vue 9.41.0 → 9.42.0
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/package.json +1 -1
- package/src/components/products/PbProducts.vue +1 -2
- package/src/components/question/PbQuestion.vue +1 -49
- package/src/components/question/amount-input/PbAmountInput.vue +1 -1
- package/src/components/question/city-search/PbCitySearch.vue +1 -2
- package/src/components/question/configurations-import/PbConfigurationsImport.vue +1 -2
- package/src/components/question/dimensions-input/PbDimensionsInput.vue +1 -1
- package/src/components/question/incremental-amount-input/PbIncrementalAmountInput.vue +1 -4
- package/src/components/question/list-select/PbListSelect.vue +1 -3
- package/src/components/question/name-input/PbNameInput.vue +1 -1
- package/src/components/question/space-input/PbSpaceInput.vue +1 -1
- package/src/components/question/upload-document/PbUploadDocument.vue +1 -2
- package/src/components/restitution/PbRestitutionList.stories.mdx +46 -39
- package/src/components/restitution/PbRestitutionList.vue +23 -40
- package/src/components/restitution/PbRestitutionListBlock.vue +13 -2
- package/src/components/restitution/PbRestitutionListLine.vue +12 -5
- package/src/components/trezor/PbTrezor.stories.mdx +47 -0
- package/src/components/trezor/PbTrezor.vue +356 -0
- package/src/components/trezor/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-components-trezor-pb-trezor-/360/237/246/240-101-sandbox-1-snap.png +0 -0
- package/src/components/trezor/default-payload.json +17 -0
- package/src/services/api/trezorApi.ts +30 -0
- package/src/services/decorate.ts +65 -0
- package/src/stores/modules/trezorStore.ts +39 -0
- package/src/stores/store.ts +2 -0
package/package.json
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
:left-icon="BACK_ICON"
|
|
7
7
|
:class="{
|
|
8
8
|
'pb-products__back-button': true,
|
|
9
|
-
'pb-products__back-button--hidden':
|
|
10
|
-
!showBackButton && !decorate(answers, runtimeOptions, payload.viewModel.forceBackButton),
|
|
9
|
+
'pb-products__back-button--hidden': true,
|
|
11
10
|
}"
|
|
12
11
|
@click.once="$emit('go-back')"
|
|
13
12
|
/>
|
|
@@ -352,7 +352,6 @@
|
|
|
352
352
|
|
|
353
353
|
<script lang="ts">
|
|
354
354
|
import { defineComponent, PropType } from 'vue';
|
|
355
|
-
import objectPath from 'object-path';
|
|
356
355
|
import MButton from '../mozaic/buttons/MButton.vue';
|
|
357
356
|
import MContainer from '../mozaic/grid/MContainer.vue';
|
|
358
357
|
import MDialog from '../mozaic/dialog/MDialog.vue';
|
|
@@ -366,6 +365,7 @@ import PbCard from '../cards/PbCard.vue';
|
|
|
366
365
|
import PbStickyFooter from '../sticky-footer/PbStickyFooter.vue';
|
|
367
366
|
import { sortAnswers } from './sortAnswers';
|
|
368
367
|
import { areConditionsValid } from '../../services/scenarioConditionals';
|
|
368
|
+
import { decorate } from '@/services/decorate';
|
|
369
369
|
import {
|
|
370
370
|
ScenarioStepAnswer,
|
|
371
371
|
ScenarioStepAnswerDialog,
|
|
@@ -383,54 +383,6 @@ const BACK_ICON =
|
|
|
383
383
|
const INFO_ICON =
|
|
384
384
|
'https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Notification_Information_24px.svg';
|
|
385
385
|
|
|
386
|
-
// Function used by the scenario conditions
|
|
387
|
-
const getAnswerValue = (answers: Map<string, ScenarioStepAnswer[]>) => {
|
|
388
|
-
return (answerCode: string, path: string) => {
|
|
389
|
-
let answerValue: ScenarioStepAnswer | undefined = undefined;
|
|
390
|
-
if (answers?.get(answerCode)) {
|
|
391
|
-
const answerValues = answers.get(answerCode) ?? [];
|
|
392
|
-
answerValue = objectPath.get(answerValues[0], path);
|
|
393
|
-
}
|
|
394
|
-
return answerValue;
|
|
395
|
-
};
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
export const doEval = (
|
|
399
|
-
answers: Map<string, ScenarioStepAnswer[]>,
|
|
400
|
-
valueToEval: string,
|
|
401
|
-
defaultValue: string,
|
|
402
|
-
runtimeOptions: any,
|
|
403
|
-
) => {
|
|
404
|
-
return new Function('getAnswerValue', 'answers', 'defaultValue', 'runtimeOptions', `return ${valueToEval}`).call(
|
|
405
|
-
this,
|
|
406
|
-
getAnswerValue(answers),
|
|
407
|
-
Object.fromEntries(answers),
|
|
408
|
-
defaultValue,
|
|
409
|
-
runtimeOptions,
|
|
410
|
-
);
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
//TODO Think to move this kind of function in something like "helpers" or "tools"
|
|
414
|
-
// because this is use in multiple components (as PbIncrementalAmountInput.vue),
|
|
415
|
-
// and it shouldn't be in a component.
|
|
416
|
-
export const decorate = (
|
|
417
|
-
answers: Map<string, ScenarioStepAnswer[]>,
|
|
418
|
-
runtimeOptions = {},
|
|
419
|
-
valueToDecorate: string,
|
|
420
|
-
defaultValue = '',
|
|
421
|
-
): string => {
|
|
422
|
-
let decoratedValue = valueToDecorate;
|
|
423
|
-
if (valueToDecorate) {
|
|
424
|
-
const stringToEval = `\`${valueToDecorate}\``;
|
|
425
|
-
try {
|
|
426
|
-
decoratedValue = doEval(answers, stringToEval, defaultValue, runtimeOptions);
|
|
427
|
-
} catch (error) {
|
|
428
|
-
decoratedValue = valueToDecorate || defaultValue;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
return decoratedValue;
|
|
432
|
-
};
|
|
433
|
-
|
|
434
386
|
export default defineComponent({
|
|
435
387
|
name: 'PbQuestion',
|
|
436
388
|
|
|
@@ -63,7 +63,7 @@ import MTextInput from './../../mozaic/text-input/MTextInput.vue';
|
|
|
63
63
|
import DEFAULT_PAYLOAD from './default-payload.json';
|
|
64
64
|
import { AmountInputPayload } from '@/components/question/amount-input/AmountInput';
|
|
65
65
|
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
66
|
-
import { decorate } from '@/
|
|
66
|
+
import { decorate } from '@/services/decorate';
|
|
67
67
|
|
|
68
68
|
const BACK_ICON =
|
|
69
69
|
'https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Arrow_Arrow--Left_16px.svg';
|
|
@@ -106,14 +106,13 @@ import debounce from 'lodash.debounce';
|
|
|
106
106
|
import merge from 'lodash.merge';
|
|
107
107
|
import initGoogleMapsApi from './google-maps-api';
|
|
108
108
|
import axios from 'axios';
|
|
109
|
-
import objectPath from 'object-path';
|
|
110
109
|
import MFlex from '../../mozaic/flex/MFlex.vue';
|
|
111
110
|
import MButton from '../../mozaic/buttons/MButton.vue';
|
|
112
111
|
import MLink from '../../mozaic/link/MLink.vue';
|
|
113
112
|
import MTextInput from '../../mozaic/text-input/MTextInput.vue';
|
|
114
113
|
import DEFAULT_PAYLOAD from './default-payload.json';
|
|
115
114
|
import { DebouncedFunc } from 'lodash-es';
|
|
116
|
-
import { decorate } from '@/
|
|
115
|
+
import { decorate } from '@/services/decorate';
|
|
117
116
|
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
118
117
|
|
|
119
118
|
const BACK_ICON =
|
|
@@ -165,7 +165,6 @@ import MLink from '../../../components/mozaic/link/MLink.vue';
|
|
|
165
165
|
import MNotification from '../../../components/mozaic/notifications/MNotification.vue';
|
|
166
166
|
import PbCard from '../../../components/cards/PbCard.vue';
|
|
167
167
|
import { format } from 'date-fns';
|
|
168
|
-
import objectPath from 'object-path';
|
|
169
168
|
import {
|
|
170
169
|
ConfigurationsImportMultiSelectAction,
|
|
171
170
|
ConfigurationsImportPayload,
|
|
@@ -174,7 +173,7 @@ import {
|
|
|
174
173
|
} from '@/components/question/configurations-import/ConfigurationsImport';
|
|
175
174
|
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
176
175
|
import { Configuration } from '@/types/pb/Configuration';
|
|
177
|
-
import { decorate } from '@/
|
|
176
|
+
import { decorate } from '@/services/decorate';
|
|
178
177
|
|
|
179
178
|
const BACK_ICON =
|
|
180
179
|
'https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Arrow_Arrow--Left_16px.svg';
|
|
@@ -101,7 +101,7 @@ import MIcon from '../../mozaic/icon/MIcon.vue';
|
|
|
101
101
|
import { ref } from 'vue';
|
|
102
102
|
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
103
103
|
import { DimensionsInputPayload } from '@/components/question/dimensions-input/DimensionsInput';
|
|
104
|
-
import { decorate } from '@/
|
|
104
|
+
import { decorate } from '@/services/decorate';
|
|
105
105
|
|
|
106
106
|
const BACK_ICON =
|
|
107
107
|
'https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Arrow_Arrow--Left_16px.svg';
|
|
@@ -72,7 +72,7 @@ import MLink from '../../mozaic/link/MLink.vue';
|
|
|
72
72
|
import MButton from '../../mozaic/buttons/MButton.vue';
|
|
73
73
|
import { ref } from 'vue';
|
|
74
74
|
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
75
|
-
import { decorate } from '@/
|
|
75
|
+
import { decorate } from '@/services/decorate';
|
|
76
76
|
import MQuantitySelector from '../../mozaic/quantityselector/MQuantitySelector.vue';
|
|
77
77
|
import { IncrementalAmountPayload } from '@/components/question/incremental-amount-input/IncrementalAmount';
|
|
78
78
|
|
|
@@ -169,9 +169,6 @@ export default defineComponent({
|
|
|
169
169
|
let tempPayload = cloneDeep(DEFAULT_PAYLOAD);
|
|
170
170
|
tempPayload = merge(tempPayload, props.payload);
|
|
171
171
|
|
|
172
|
-
// This will doEval and decorate dynamically variables from JSON templates
|
|
173
|
-
// For instance:
|
|
174
|
-
// "${getAnswerValue('DEVELOPER', 'code') == 'YES' ? 'I'm developer' : 'I'm not developer'}"
|
|
175
172
|
tempPayload.viewModel.label = decorate(props.answers!, props.runtimeOptions, tempPayload.viewModel.label);
|
|
176
173
|
tempPayload.viewModel.subtitle = decorate(props.answers!, props.runtimeOptions, tempPayload.viewModel.subtitle);
|
|
177
174
|
|
|
@@ -75,15 +75,13 @@
|
|
|
75
75
|
<script lang="ts">
|
|
76
76
|
import { defineComponent, PropType } from 'vue';
|
|
77
77
|
import { ref, computed, onMounted, Ref } from 'vue';
|
|
78
|
-
import objectPath from 'object-path';
|
|
79
78
|
import MFlex from '../../mozaic/flex/MFlex.vue';
|
|
80
79
|
import MButton from '../../mozaic/buttons/MButton.vue';
|
|
81
80
|
import MLink from '../../mozaic/link/MLink.vue';
|
|
82
81
|
import PbItemsList from '../../items/PbItemsList.vue';
|
|
83
82
|
import { sortAnswers } from '../sortAnswers';
|
|
84
83
|
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
85
|
-
import {
|
|
86
|
-
import { decorate } from '@/components/question/PbQuestion.vue';
|
|
84
|
+
import { decorate } from '@/services/decorate';
|
|
87
85
|
|
|
88
86
|
const BACK_ICON =
|
|
89
87
|
'https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Arrow_Arrow--Left_16px.svg';
|
|
@@ -60,7 +60,7 @@ import MToggle from '../../mozaic/toggle/MToggle.vue';
|
|
|
60
60
|
import DEFAULT_PAYLOAD from './default-payload.json';
|
|
61
61
|
import { NameInputPayload } from '@/components/question/name-input/NameInput';
|
|
62
62
|
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
63
|
-
import { decorate } from '@/
|
|
63
|
+
import { decorate } from '@/services/decorate';
|
|
64
64
|
|
|
65
65
|
const BACK_ICON =
|
|
66
66
|
'https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Arrow_Arrow--Left_16px.svg';
|
|
@@ -146,7 +146,7 @@ import MDialog from '../../mozaic/dialog/MDialog.vue';
|
|
|
146
146
|
import DEFAULT_PAYLOAD from './default-payload.json';
|
|
147
147
|
import { SpaceInputPayload } from '@/components/question/space-input/SpaceInput';
|
|
148
148
|
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
149
|
-
import { decorate } from '@/
|
|
149
|
+
import { decorate } from '@/services/decorate';
|
|
150
150
|
|
|
151
151
|
const BACK_ICON =
|
|
152
152
|
'https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Arrow_Arrow--Left_16px.svg';
|
|
@@ -139,7 +139,6 @@
|
|
|
139
139
|
|
|
140
140
|
<script lang="ts">
|
|
141
141
|
import { defineComponent, computed, onMounted, ref, PropType, Ref, ComponentCustomProperties } from 'vue';
|
|
142
|
-
import objectPath from 'object-path';
|
|
143
142
|
import MButton from '../../mozaic/buttons/MButton.vue';
|
|
144
143
|
import MContainer from '../../mozaic/grid/MContainer.vue';
|
|
145
144
|
import MFlex from '../../mozaic/flex/MFlex.vue';
|
|
@@ -156,7 +155,7 @@ import {
|
|
|
156
155
|
UploadDocumentViewModel,
|
|
157
156
|
} from '@/components/question/upload-document/UploadDocument';
|
|
158
157
|
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
159
|
-
import { decorate, doEval } from '@/
|
|
158
|
+
import { decorate, doEval } from '@/services/decorate';
|
|
160
159
|
|
|
161
160
|
const BACK_ICON =
|
|
162
161
|
'https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Arrow_Arrow--Left_16px.svg';
|
|
@@ -23,49 +23,67 @@ import CALL_TO_ACTION_WITH_MODAL from './restitution-call-to-actions-with-modal.
|
|
|
23
23
|
/>
|
|
24
24
|
|
|
25
25
|
export const summary = {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
subprojectTemplateId: '44cddf9d-f0e8-4d3d-82dc-d717dc70342a',
|
|
27
|
+
subprojectTemplateLabel: 'Pompe à chaleur air-eau',
|
|
28
28
|
businessUnit: 'LMFR',
|
|
29
|
-
summaryDate: '
|
|
30
|
-
subprojectTemplateLabel: '<strong>Pompe à chaleur air/air</strong>',
|
|
29
|
+
summaryDate: '2022-10-13T12:24:39.316059261Z',
|
|
31
30
|
cost: {
|
|
32
|
-
min:
|
|
33
|
-
max:
|
|
31
|
+
min: 12103.78,
|
|
32
|
+
max: 19103.78,
|
|
34
33
|
currency: 'EURO',
|
|
35
34
|
},
|
|
36
35
|
components: [
|
|
37
36
|
{
|
|
38
|
-
componentId: '
|
|
39
|
-
title: '
|
|
37
|
+
componentId: 'FURNISHING',
|
|
38
|
+
title: 'Pompe à chaleur installée (hors aides)',
|
|
39
|
+
details: 'Travaux réalisés par nos artisans partenaires Leroy Merlin',
|
|
40
40
|
cost: {
|
|
41
|
-
min:
|
|
42
|
-
max:
|
|
41
|
+
min: 13000,
|
|
42
|
+
max: 20000,
|
|
43
43
|
currency: 'EURO',
|
|
44
44
|
},
|
|
45
|
-
details: 'Test du message vert',
|
|
46
45
|
lines: [
|
|
47
46
|
{
|
|
48
|
-
text: '
|
|
49
|
-
details: 'Terrain constructible, délimité et viabilisé. La nature du sous-sol est garantie.',
|
|
47
|
+
text: 'Pompe à chaleur',
|
|
50
48
|
cost: {
|
|
51
|
-
min:
|
|
52
|
-
max:
|
|
49
|
+
min: 10000,
|
|
50
|
+
max: 15000,
|
|
53
51
|
currency: 'EURO',
|
|
54
52
|
},
|
|
55
53
|
},
|
|
56
54
|
{
|
|
57
|
-
text: '
|
|
55
|
+
text: 'Installation',
|
|
58
56
|
cost: {
|
|
59
|
-
min:
|
|
60
|
-
max:
|
|
57
|
+
min: 3000,
|
|
58
|
+
max: 5000,
|
|
59
|
+
currency: 'EURO',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
componentId: 'AID',
|
|
66
|
+
title: 'Aides estimées (MaPrimeRénov, Prime Energie)',
|
|
67
|
+
details: 'Les aides varient selon certains critères.',
|
|
68
|
+
cost: {
|
|
69
|
+
min: -896.22,
|
|
70
|
+
max: -950.22,
|
|
71
|
+
currency: 'EURO',
|
|
72
|
+
},
|
|
73
|
+
lines: [
|
|
74
|
+
{
|
|
75
|
+
text: 'Estimation Prime énergie Leroy Merlin',
|
|
76
|
+
cost: {
|
|
77
|
+
min: -96.22,
|
|
78
|
+
max: -650.22,
|
|
61
79
|
currency: 'EURO',
|
|
62
80
|
},
|
|
63
81
|
},
|
|
64
82
|
{
|
|
65
|
-
text: '
|
|
66
|
-
type: 'REDUCTION',
|
|
83
|
+
text: 'Estimation MaPrimeRénov',
|
|
67
84
|
cost: {
|
|
68
|
-
|
|
85
|
+
min: -800,
|
|
86
|
+
max: -900,
|
|
69
87
|
currency: 'EURO',
|
|
70
88
|
},
|
|
71
89
|
},
|
|
@@ -73,21 +91,15 @@ export const summary = {
|
|
|
73
91
|
},
|
|
74
92
|
],
|
|
75
93
|
subprojectAttributes: {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
age: 'NEW',
|
|
95
|
+
housingType: 'HOUSE',
|
|
96
|
+
income: 'MIDDLE',
|
|
97
|
+
inhabitantsCount: 1,
|
|
98
|
+
location: '20137',
|
|
99
|
+
situation: 'OWNER',
|
|
100
|
+
surfaceAreaInSquareMeters: 40,
|
|
83
101
|
},
|
|
84
102
|
suggestedTypologies: [
|
|
85
|
-
{
|
|
86
|
-
domesticSpaceLabel: 'Cuisine',
|
|
87
|
-
domesticSpaceHref: '/domestic-spaces/59405',
|
|
88
|
-
projectKindHref: '/project-kinds/34048',
|
|
89
|
-
projectTypeHref: '/project-types/29239',
|
|
90
|
-
},
|
|
91
103
|
{
|
|
92
104
|
domesticSpaceLabel: 'Tout le logement',
|
|
93
105
|
domesticSpaceHref: '/domestic-spaces/59461',
|
|
@@ -95,11 +107,6 @@ export const summary = {
|
|
|
95
107
|
projectTypeHref: '/project-types/61855',
|
|
96
108
|
},
|
|
97
109
|
],
|
|
98
|
-
legalMentions: [
|
|
99
|
-
"Les prix ci-dessus ne sont proposés qu'à titre indicatif et sont basés sur les prix du marché.",
|
|
100
|
-
"L'outil d'estimation ne vaut pas offre de vente.",
|
|
101
|
-
"La disponibilité des produits au moment de l'achat n'est pas garantie.",
|
|
102
|
-
],
|
|
103
110
|
};
|
|
104
111
|
|
|
105
112
|
# 🦠 `PbRestitutionList` - Component
|
|
@@ -74,53 +74,36 @@
|
|
|
74
74
|
<div class="pb-restitution-list__modal__details__title" v-html="summary.subprojectTemplateLabel"></div>
|
|
75
75
|
|
|
76
76
|
<div class="pb-restitution-list__modal__details__row">
|
|
77
|
-
<div
|
|
78
|
-
<div
|
|
79
|
-
|
|
80
|
-
<
|
|
77
|
+
<div v-for="component in summary.components" :key="component.details">
|
|
78
|
+
<div class="pb-restitution-list__modal__details__row__title" justify-content="space-between">
|
|
79
|
+
<div v-html="component.title"></div>
|
|
80
|
+
<div v-if="component.cost.min != component.cost.max">
|
|
81
|
+
<strong>{{ component.cost.min }}€ et {{ component.cost.max }}€</strong>
|
|
82
|
+
</div>
|
|
83
|
+
<div v-else>
|
|
84
|
+
<strong>{{ component.cost.min }}€</strong>
|
|
85
|
+
</div>
|
|
81
86
|
</div>
|
|
82
|
-
</div>
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{{ line.details }}
|
|
88
|
+
<div class="pb-restitution-list__modal__details__row__label" v-if="component.details">
|
|
89
|
+
{{ component.details }}
|
|
87
90
|
</div>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
<div
|
|
91
|
-
v-for="line in summary.components[0].lines"
|
|
92
|
-
:key="line.text"
|
|
93
|
-
class="pb-restitution-list__modal__details__row__list"
|
|
94
|
-
:class="{ ' pb-restitution-list__modal__details__row': line.cost.reduce }"
|
|
95
|
-
>
|
|
96
|
-
<ul>
|
|
97
|
-
<div>
|
|
98
|
-
<li v-if="!line.cost.reduce">
|
|
99
|
-
<div v-html="line.text" v-if="!line.cost.reduce"></div>
|
|
100
|
-
<div v-if="!line.cost.reduce">{{ line.cost.min }}€ - {{ line.cost.max }}€</div>
|
|
101
|
-
</li>
|
|
102
|
-
</div>
|
|
103
|
-
</ul>
|
|
104
|
-
</div>
|
|
105
91
|
|
|
106
|
-
<div v-for="line in summary.components[0].lines" :key="line.text">
|
|
107
92
|
<div
|
|
108
|
-
v-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
93
|
+
v-for="line in component.lines"
|
|
94
|
+
:key="line.text"
|
|
95
|
+
class="pb-restitution-list__modal__details__row__list"
|
|
96
|
+
:class="{ ' pb-restitution-list__modal__details__row': line.cost.reduce }"
|
|
112
97
|
>
|
|
113
|
-
<
|
|
114
|
-
<div
|
|
115
|
-
<
|
|
98
|
+
<ul>
|
|
99
|
+
<div>
|
|
100
|
+
<li>
|
|
101
|
+
<div v-html="line.text"></div>
|
|
102
|
+
<div v-if="line.cost.min != line.cost.max">{{ line.cost.min }}€ et {{ line.cost.max }}€</div>
|
|
103
|
+
<div v-else>{{ line.cost.min }}€</div>
|
|
104
|
+
</li>
|
|
116
105
|
</div>
|
|
117
|
-
</
|
|
118
|
-
|
|
119
|
-
<div v-for="line in summary.components[0].lines" :key="line">
|
|
120
|
-
<div v-if="line.cost.reduce" class="green">
|
|
121
|
-
<strong>{{ line.cost.reduce }}€</strong>
|
|
122
|
-
</div>
|
|
123
|
-
</div>
|
|
106
|
+
</ul>
|
|
124
107
|
</div>
|
|
125
108
|
</div>
|
|
126
109
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<m-flex align-items="center" direction="column">
|
|
2
|
+
<m-flex align-items="center" direction="column" v-if="payload.componentId != 'AID'">
|
|
3
3
|
<m-flex :class="`pb-restitution-list-block`" direction="column" ref="pbRestitutionListBlock">
|
|
4
4
|
<div class="pb-restitution-list-block__title" v-html="summary.subprojectTemplateLabel"></div>
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<div class="pb-restitution-list-line">
|
|
8
8
|
<div class="pb-restitution-list-line__title" v-html="summary.components[0].title"></div>
|
|
9
9
|
<div class="pb-restitution-list-line__price">
|
|
10
|
-
<div class="pb-restitution-list-line__price__min">{{ summary.components[0].cost.min }}€ 
|
|
10
|
+
<div class="pb-restitution-list-line__price__min">{{ summary.components[0].cost.min }}€ et </div>
|
|
11
11
|
<div class="pb-restitution-list-line__price__max">{{ summary.components[0].cost.max }}€</div>
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
@@ -19,6 +19,17 @@
|
|
|
19
19
|
:line="line"
|
|
20
20
|
></pb-restitution-list-line>
|
|
21
21
|
|
|
22
|
+
<div v-for="aidComponent in summary.components" :key="aidComponent.componentId">
|
|
23
|
+
<div v-if="aidComponent.componentId === 'AID'">
|
|
24
|
+
<pb-restitution-list-line
|
|
25
|
+
:key="aidComponent.title"
|
|
26
|
+
:component="aidComponent"
|
|
27
|
+
:line="aidComponent"
|
|
28
|
+
:renovAid="true"
|
|
29
|
+
></pb-restitution-list-line>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
22
33
|
<div class="pb-restitution-list-block__body__row unbordered">
|
|
23
34
|
<div class="pb-restitution-list-block__body__row__title full">
|
|
24
35
|
<div><strong>Montant total</strong> (aides déduites)</div>
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="pb-restitution-list-line" :class="{ green:
|
|
3
|
-
<div class="pb-restitution-list-line__title" v-html="line.text"></div>
|
|
4
|
-
<div class="pb-restitution-list-line__price" v-if="
|
|
2
|
+
<div class="pb-restitution-list-line" :class="{ green: renovAid }" v-if="renovAid">
|
|
3
|
+
<div class="pb-restitution-list-line__title" v-html="line.text || line.title"></div>
|
|
4
|
+
<div class="pb-restitution-list-line__price" v-if="!renovAid">
|
|
5
5
|
<div class="pb-restitution-list-line__price__min">
|
|
6
|
-
{{ formatPriceRange(line.cost.min, line.cost.max).min }}€ 
|
|
6
|
+
{{ formatPriceRange(line.cost.min, line.cost.max).min }}€ et
|
|
7
7
|
</div>
|
|
8
8
|
<div class="pb-restitution-list-line__price__max">{{ formatPriceRange(line.cost.min, line.cost.max).max }}€</div>
|
|
9
9
|
</div>
|
|
10
|
-
<div class="pb-restitution-list-line__price">
|
|
10
|
+
<div class="pb-restitution-list-line__price">
|
|
11
|
+
<span v-if="line.cost.min === line.cost.max">{{ line.cost.min }}€</span>
|
|
12
|
+
<span v-else>{{ line.cost.min }} et {{ line.cost.max }}€</span>
|
|
13
|
+
</div>
|
|
11
14
|
</div>
|
|
12
15
|
</template>
|
|
13
16
|
|
|
@@ -24,6 +27,10 @@ export default defineComponent({
|
|
|
24
27
|
type: Object,
|
|
25
28
|
default: () => ({}),
|
|
26
29
|
},
|
|
30
|
+
renovAid: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: () => false,
|
|
33
|
+
},
|
|
27
34
|
},
|
|
28
35
|
methods: {
|
|
29
36
|
handleLinkClick() {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { nestedAppDecorator } from '../../../.storybook/nested-app-decorator';
|
|
2
|
+
import { ArgsTable, Canvas, Meta, Source, Story } from '@storybook/addon-docs';
|
|
3
|
+
import cloneDeep from 'lodash.clonedeep';
|
|
4
|
+
import PbTrezor from './PbTrezor';
|
|
5
|
+
import DEFAULT_PAYLOAD from './default-payload.json';
|
|
6
|
+
|
|
7
|
+
<Meta
|
|
8
|
+
title="Project Booster/Components/Trezor/PbTrezor 🦠"
|
|
9
|
+
component={PbTrezor}
|
|
10
|
+
decorators={[nestedAppDecorator({}, [])]}
|
|
11
|
+
parameters={{ layout: 'fullscreen' }}
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
# 🦠 `PbTrezor` - Components
|
|
15
|
+
|
|
16
|
+
[](https://github.com/adeo/project-booster-vue/tree/master/src/components/question)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
The `PbTrezor` Vue component allows select family amount persons. It
|
|
21
|
+
is customizable through the payload property.
|
|
22
|
+
|
|
23
|
+
It has a default provided payload, used in the Sandbox below.
|
|
24
|
+
|
|
25
|
+
The input field is automatically focused.
|
|
26
|
+
|
|
27
|
+
# `PbTrezor`
|
|
28
|
+
|
|
29
|
+
export const TemplateSandbox = (args, { argTypes }) => ({
|
|
30
|
+
props: Object.keys(argTypes),
|
|
31
|
+
components: { PbTrezor },
|
|
32
|
+
setup() {
|
|
33
|
+
return { args };
|
|
34
|
+
},
|
|
35
|
+
template: `<pb-trezor
|
|
36
|
+
:payload="args.payload"
|
|
37
|
+
:show-back-button="args.showBackButton"
|
|
38
|
+
:completed-event-name="args.completedEventName"
|
|
39
|
+
@step-completed="args.onStepCompleted"
|
|
40
|
+
/>`,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
<Canvas>
|
|
44
|
+
<Story name="101 Sandbox" args={{ payload: DEFAULT_PAYLOAD }}>
|
|
45
|
+
{TemplateSandbox.bind({})}
|
|
46
|
+
</Story>
|
|
47
|
+
</Canvas>
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<m-flex class="pb-trezor" direction="column">
|
|
3
|
+
<div class="pb-trezor__title" v-if="payload?.viewModel?.label">
|
|
4
|
+
{{ payload.viewModel.label }}
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<form action="" class="pb-trezor__form">
|
|
8
|
+
<m-flex class="pb-trezor__form__row">
|
|
9
|
+
<m-text-input
|
|
10
|
+
v-model="formData.values.lastname"
|
|
11
|
+
:error="formData.errors.lastname"
|
|
12
|
+
@focus="validate('lastname')"
|
|
13
|
+
@keypress="validate('lastname')"
|
|
14
|
+
type="text"
|
|
15
|
+
label="Nom"
|
|
16
|
+
:required="true"
|
|
17
|
+
class="pb-trezor__input"
|
|
18
|
+
></m-text-input>
|
|
19
|
+
</m-flex>
|
|
20
|
+
|
|
21
|
+
<m-flex class="pb-trezor__form__row">
|
|
22
|
+
<m-text-input
|
|
23
|
+
v-model="formData.values.firstname"
|
|
24
|
+
:error="formData.errors.firstname"
|
|
25
|
+
@focus="validate('firstname')"
|
|
26
|
+
@keypress="validate('firstname')"
|
|
27
|
+
type="text"
|
|
28
|
+
label="Prénom"
|
|
29
|
+
:required="true"
|
|
30
|
+
class="pb-trezor__input"
|
|
31
|
+
></m-text-input>
|
|
32
|
+
</m-flex>
|
|
33
|
+
|
|
34
|
+
<m-flex class="pb-trezor__form__row">
|
|
35
|
+
<m-text-input
|
|
36
|
+
v-model="formData.values.zipcode"
|
|
37
|
+
:error="formData.errors.zipcode"
|
|
38
|
+
@focus="validate('zipcode')"
|
|
39
|
+
@keypress="validate('zipcode')"
|
|
40
|
+
type="text"
|
|
41
|
+
label="Code postal"
|
|
42
|
+
:required="true"
|
|
43
|
+
class="pb-trezor__input"
|
|
44
|
+
></m-text-input>
|
|
45
|
+
</m-flex>
|
|
46
|
+
|
|
47
|
+
<m-flex class="pb-trezor__form__row">
|
|
48
|
+
<m-text-input
|
|
49
|
+
v-model="formData.values.email"
|
|
50
|
+
:error="formData.errors.email"
|
|
51
|
+
@focus="validate('email')"
|
|
52
|
+
@keypress="validate('email')"
|
|
53
|
+
type="email"
|
|
54
|
+
label="E-mail"
|
|
55
|
+
:required="true"
|
|
56
|
+
class="pb-trezor__input"
|
|
57
|
+
></m-text-input>
|
|
58
|
+
</m-flex>
|
|
59
|
+
|
|
60
|
+
<m-flex class="pb-trezor__form__row">
|
|
61
|
+
<m-text-input
|
|
62
|
+
v-model="formData.values.phone"
|
|
63
|
+
:error="formData.errors.phone"
|
|
64
|
+
@focus="validate('phone')"
|
|
65
|
+
@keypress="validate('phone')"
|
|
66
|
+
type="phone"
|
|
67
|
+
label="Numéro de téléphone"
|
|
68
|
+
:required="true"
|
|
69
|
+
class="pb-trezor__input"
|
|
70
|
+
></m-text-input>
|
|
71
|
+
</m-flex>
|
|
72
|
+
|
|
73
|
+
<m-flex class="pb-trezor__form__row space" direction="column">
|
|
74
|
+
<label for="" class="pb-trezor__form__label"><span>- obligatoire</span></label>
|
|
75
|
+
<m-checkbox
|
|
76
|
+
class="pb-trezor__form__checkbox"
|
|
77
|
+
:required="true"
|
|
78
|
+
label="J'ai lu est accepte sans réserve les conditions générales d'utilisation"
|
|
79
|
+
v-model="formData.values.optin"
|
|
80
|
+
></m-checkbox>
|
|
81
|
+
</m-flex>
|
|
82
|
+
|
|
83
|
+
<m-flex class="pb-trezor__form__row space" direction="column">
|
|
84
|
+
<m-checkbox
|
|
85
|
+
class="pb-trezor__form__checkbox"
|
|
86
|
+
:required="false"
|
|
87
|
+
v-model="formData.values.optinPartners"
|
|
88
|
+
label="J'autorise Leroy Merlin à transmettre les données collectées ici à l'un de nos partenaires susceptibles de réaliser le chantier (en fonction des critères géographiques)"
|
|
89
|
+
></m-checkbox>
|
|
90
|
+
</m-flex>
|
|
91
|
+
|
|
92
|
+
<div class="pb-trezor__container">
|
|
93
|
+
<m-flex class="pb-trezor__container--button" direction="row">
|
|
94
|
+
<m-button
|
|
95
|
+
:theme="action.bordered ? 'bordered' : 'solid'"
|
|
96
|
+
class="pb-trezor__container--button__ok mc-button--full"
|
|
97
|
+
:type="action.type"
|
|
98
|
+
:label="action.label"
|
|
99
|
+
icon-position="right"
|
|
100
|
+
:href="action.href"
|
|
101
|
+
v-for="action in payload.callToActions"
|
|
102
|
+
:key="action.label"
|
|
103
|
+
@click.prevent="callAction(action)"
|
|
104
|
+
/>
|
|
105
|
+
</m-flex>
|
|
106
|
+
</div>
|
|
107
|
+
</form>
|
|
108
|
+
</m-flex>
|
|
109
|
+
</template>
|
|
110
|
+
|
|
111
|
+
<script lang="ts">
|
|
112
|
+
import { defineComponent, PropType, ref } from 'vue';
|
|
113
|
+
import { useStore } from 'vuex';
|
|
114
|
+
import MFlex from './../mozaic/flex/MFlex.vue';
|
|
115
|
+
import MButton from '../mozaic/buttons/MButton.vue';
|
|
116
|
+
import MCheckbox from '../mozaic/checkbox/MCheckbox.vue';
|
|
117
|
+
import MTextInput from '../mozaic/text-input/MTextInput.vue';
|
|
118
|
+
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
119
|
+
import { object, string, boolean } from 'yup';
|
|
120
|
+
|
|
121
|
+
export default defineComponent({
|
|
122
|
+
name: 'PbTrezor',
|
|
123
|
+
components: {
|
|
124
|
+
MFlex,
|
|
125
|
+
MButton,
|
|
126
|
+
MTextInput,
|
|
127
|
+
MCheckbox,
|
|
128
|
+
},
|
|
129
|
+
props: {
|
|
130
|
+
/**
|
|
131
|
+
* The component view model and business data as an object. The provided prop
|
|
132
|
+
* is merged with the default payload value so only overriden values will change
|
|
133
|
+
* from the default ones.
|
|
134
|
+
*/
|
|
135
|
+
payload: {
|
|
136
|
+
type: Object,
|
|
137
|
+
default: () => ({}),
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* The options provided at runtime to customize component behaviour
|
|
141
|
+
*/
|
|
142
|
+
runtimeOptions: {
|
|
143
|
+
type: Object,
|
|
144
|
+
default: () => ({}),
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* Indicates whether the back button should be displayed
|
|
148
|
+
*/
|
|
149
|
+
showBackButton: {
|
|
150
|
+
type: Boolean,
|
|
151
|
+
default: true,
|
|
152
|
+
},
|
|
153
|
+
/**
|
|
154
|
+
* Name for the event to send when the step is questio is answered
|
|
155
|
+
*/
|
|
156
|
+
completedEventName: {
|
|
157
|
+
type: String,
|
|
158
|
+
default: 'step-completed',
|
|
159
|
+
},
|
|
160
|
+
/**
|
|
161
|
+
* The previous answers to inject
|
|
162
|
+
*/
|
|
163
|
+
answers: {
|
|
164
|
+
type: Object as PropType<Map<string, ScenarioStepAnswer[]>>,
|
|
165
|
+
default: () => new Map<string, ScenarioStepAnswer[]>(),
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
setup(props, { emit }) {
|
|
169
|
+
const store = useStore();
|
|
170
|
+
let formData = ref({
|
|
171
|
+
values: {
|
|
172
|
+
lastname: '',
|
|
173
|
+
firstname: '',
|
|
174
|
+
zipcode: '',
|
|
175
|
+
email: '',
|
|
176
|
+
phone: '',
|
|
177
|
+
optin: false,
|
|
178
|
+
optinPartners: false,
|
|
179
|
+
},
|
|
180
|
+
errors: {
|
|
181
|
+
lastname: '',
|
|
182
|
+
firstname: '',
|
|
183
|
+
zipcode: '',
|
|
184
|
+
email: '',
|
|
185
|
+
phone: '',
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const validationSchema = object({
|
|
190
|
+
lastname: string().required(),
|
|
191
|
+
firstname: string().required(),
|
|
192
|
+
zipcode: string().required(),
|
|
193
|
+
email: string().email().required(),
|
|
194
|
+
phone: string().required(),
|
|
195
|
+
optin: boolean(),
|
|
196
|
+
optinPartners: boolean(),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const validate = async (field: string) => {
|
|
200
|
+
await validationSchema
|
|
201
|
+
.validateAt(field, formData.value.values)
|
|
202
|
+
.then(() => {
|
|
203
|
+
formData.value.errors[field] = '';
|
|
204
|
+
})
|
|
205
|
+
.catch((err) => {
|
|
206
|
+
formData.value.errors[err.path] = err.message;
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const validateAllFields = async (action: any) => {
|
|
211
|
+
await validationSchema.validate(formData.value.values).then(async () => {
|
|
212
|
+
const sending = await store.dispatch(
|
|
213
|
+
'trezor/sendFormData',
|
|
214
|
+
{
|
|
215
|
+
customer: {
|
|
216
|
+
lastname: formData.value.values.lastname,
|
|
217
|
+
firstname: formData.value.values.firstname,
|
|
218
|
+
zipcode: formData.value.values.zipcode,
|
|
219
|
+
email: formData.value.values.email,
|
|
220
|
+
phone: formData.value.values.phone,
|
|
221
|
+
},
|
|
222
|
+
optin: formData.value.values.optin,
|
|
223
|
+
optinPartners: formData.value.values.optinPartners,
|
|
224
|
+
},
|
|
225
|
+
props.payload.viewModel.typeLead,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (sending) {
|
|
229
|
+
emit('step-completed', {
|
|
230
|
+
answers: [],
|
|
231
|
+
nextStep: action,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const callAction = async (item: any) => {
|
|
238
|
+
if (item.action.type === 'submit') {
|
|
239
|
+
await validateAllFields(item);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
formData,
|
|
245
|
+
callAction,
|
|
246
|
+
validate,
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
</script>
|
|
251
|
+
|
|
252
|
+
<style lang="scss" scoped>
|
|
253
|
+
@import 'pb-variables';
|
|
254
|
+
@import 'settings-tools/all-settings';
|
|
255
|
+
@import 'typography/_t.bodys';
|
|
256
|
+
|
|
257
|
+
$small-responsive-breakpoint: 's-large';
|
|
258
|
+
$responsive-breakpoint: 'm';
|
|
259
|
+
|
|
260
|
+
.pb-trezor {
|
|
261
|
+
margin: 0 auto;
|
|
262
|
+
max-width: 1024px;
|
|
263
|
+
&__title {
|
|
264
|
+
@include set-font-face('semi-bold');
|
|
265
|
+
@include set-font-scale('08', 'm');
|
|
266
|
+
max-width: calc(100% - 60px);
|
|
267
|
+
padding: 30px;
|
|
268
|
+
position: relative;
|
|
269
|
+
|
|
270
|
+
@include set-from-screen($responsive-breakpoint) {
|
|
271
|
+
margin: auto;
|
|
272
|
+
padding: $mu250 0 $mu250 0;
|
|
273
|
+
text-align: center;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
&__input {
|
|
278
|
+
width: 100%;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
&__form {
|
|
282
|
+
padding: 30px;
|
|
283
|
+
width: calc(100% - 60px);
|
|
284
|
+
|
|
285
|
+
@include set-from-screen($responsive-breakpoint) {
|
|
286
|
+
margin: auto;
|
|
287
|
+
padding: 0;
|
|
288
|
+
width: 400px;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
&__checkbox {
|
|
292
|
+
align-items: center;
|
|
293
|
+
display: flex;
|
|
294
|
+
justify-content: center;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
&__radio {
|
|
298
|
+
margin-right: 15px;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
&__label {
|
|
302
|
+
@include set-font-face('regular');
|
|
303
|
+
@include set-font-scale('04', 's');
|
|
304
|
+
margin-bottom: $mu050;
|
|
305
|
+
|
|
306
|
+
span {
|
|
307
|
+
@include set-font-face('regular');
|
|
308
|
+
@include set-font-scale('03', 's');
|
|
309
|
+
color: $color-grey-500;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
&__row {
|
|
314
|
+
margin-bottom: $mu050;
|
|
315
|
+
|
|
316
|
+
&.space {
|
|
317
|
+
margin-bottom: $mu150;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
&__col {
|
|
322
|
+
width: 100%;
|
|
323
|
+
|
|
324
|
+
@include set-from-screen($responsive-breakpoint) {
|
|
325
|
+
width: 50%;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
&:first-child {
|
|
329
|
+
padding-right: 15px;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
&:last-child {
|
|
333
|
+
padding-left: 15px;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
&__container--button {
|
|
339
|
+
margin: $mu250 0;
|
|
340
|
+
|
|
341
|
+
button {
|
|
342
|
+
&:first-child {
|
|
343
|
+
margin-right: 15px;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
&:last-child {
|
|
347
|
+
margin-left: 15px;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
&__ok {
|
|
352
|
+
margin-bottom: $mu100;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"viewModel": {
|
|
3
|
+
"backLabel": "Question précédente",
|
|
4
|
+
"label": "Renseignez notre formulaire pour être recontacté",
|
|
5
|
+
"typeLead": "HEAT_PUMP"
|
|
6
|
+
},
|
|
7
|
+
"callToActions": [
|
|
8
|
+
{
|
|
9
|
+
"type": "button",
|
|
10
|
+
"label": "Envoyer",
|
|
11
|
+
"action": {
|
|
12
|
+
"type": "submit"
|
|
13
|
+
},
|
|
14
|
+
"bordered": false
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { escape } from '../../services/htmlEscape';
|
|
3
|
+
|
|
4
|
+
export const clientApi = axios.create({
|
|
5
|
+
baseURL: '/project-booster/api',
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
if ((<any>window).config) {
|
|
9
|
+
clientApi.defaults.baseURL = (<any>window).config.VUE_APP_DEFAULT_BASE_URL;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const updateTrezorApiClient = (config: { apiKey: string; baseUrl: string; apiHeader: string }) => {
|
|
13
|
+
if (config.apiKey) {
|
|
14
|
+
const header = config.apiHeader ?? 'X-Gateway-ApiKey';
|
|
15
|
+
clientApi.defaults.headers.common[header] = config.apiKey;
|
|
16
|
+
}
|
|
17
|
+
if (config.baseUrl) {
|
|
18
|
+
clientApi.defaults.baseURL = config.baseUrl;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const sendTrezorForm = async (typeLead: string, payload: object) => {
|
|
23
|
+
const response = await clientApi.post(`leads/${typeLead}`, payload);
|
|
24
|
+
|
|
25
|
+
return JSON.parse(
|
|
26
|
+
JSON.stringify(response.data).replace(/:"([^"]+)"/g, (match, $1) => {
|
|
27
|
+
return `: "${escape($1)}"`;
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import objectPath from 'object-path';
|
|
2
|
+
|
|
3
|
+
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This function will doEval and decorate dynamically variables from JSON templates.
|
|
7
|
+
*
|
|
8
|
+
* For instance:
|
|
9
|
+
* "${getAnswerValue('DEVELOPER', 'code') == 'YES' ? 'I'm developer' : 'I'm not developer'}".
|
|
10
|
+
*
|
|
11
|
+
* @param answers A map of string and an array of ScenarioStepAnswer.
|
|
12
|
+
* @param valueToDecorate Targeted value to decorate.
|
|
13
|
+
* @param defaultValue Empty string by default.
|
|
14
|
+
* @returns Decorated value.
|
|
15
|
+
*/
|
|
16
|
+
export function decorate(
|
|
17
|
+
answers: Map<string, ScenarioStepAnswer[]>,
|
|
18
|
+
runtimeOptions = {},
|
|
19
|
+
valueToDecorate: string,
|
|
20
|
+
defaultValue = '',
|
|
21
|
+
): string {
|
|
22
|
+
let decoratedValue = valueToDecorate;
|
|
23
|
+
if (valueToDecorate) {
|
|
24
|
+
const stringToEval = `\`${valueToDecorate}\``;
|
|
25
|
+
try {
|
|
26
|
+
decoratedValue = doEval(answers, stringToEval, defaultValue, runtimeOptions);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
decoratedValue = valueToDecorate || defaultValue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return decoratedValue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function doEval(
|
|
35
|
+
this: any,
|
|
36
|
+
answers: Map<string, ScenarioStepAnswer[]>,
|
|
37
|
+
valueToEval: string,
|
|
38
|
+
defaultValue: string,
|
|
39
|
+
runtimeOptions: unknown,
|
|
40
|
+
): any {
|
|
41
|
+
return new Function('getAnswerValue', 'answers', 'defaultValue', 'runtimeOptions', `return ${valueToEval}`).call(
|
|
42
|
+
this,
|
|
43
|
+
getAnswerValue(answers),
|
|
44
|
+
Object.fromEntries(answers),
|
|
45
|
+
defaultValue,
|
|
46
|
+
runtimeOptions,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Function used by the scenario conditions.
|
|
52
|
+
*
|
|
53
|
+
* @param answers A map of string and an array of ScenarioStepAnswer.
|
|
54
|
+
* @returns Answers values.
|
|
55
|
+
*/
|
|
56
|
+
function getAnswerValue(answers: Map<string, ScenarioStepAnswer[]>) {
|
|
57
|
+
return (answerCode: string, path: string) => {
|
|
58
|
+
let answerValue: ScenarioStepAnswer | undefined = undefined;
|
|
59
|
+
if (answers?.get(answerCode)) {
|
|
60
|
+
const answerValues = answers.get(answerCode) ?? [];
|
|
61
|
+
answerValue = objectPath.get(answerValues[0], path);
|
|
62
|
+
}
|
|
63
|
+
return answerValue;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ActionContext } from 'vuex';
|
|
2
|
+
import { State } from '@/stores/state';
|
|
3
|
+
import { sendTrezorForm } from '@/services/api/trezorApi';
|
|
4
|
+
|
|
5
|
+
export interface TrezorState {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
7
|
+
typeLead: string;
|
|
8
|
+
formData: object;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type TrezorContext = ActionContext<TrezorState, State>;
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
namespaced: true,
|
|
15
|
+
state: {
|
|
16
|
+
typeLead: '',
|
|
17
|
+
formData: '',
|
|
18
|
+
},
|
|
19
|
+
getters: {},
|
|
20
|
+
mutations: {
|
|
21
|
+
setTypeLead: (state: TrezorState, typeLead: string) => {
|
|
22
|
+
state.typeLead = typeLead;
|
|
23
|
+
},
|
|
24
|
+
setFormData: (state: TrezorState, formData: object) => {
|
|
25
|
+
state.formData = formData;
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
actions: {
|
|
29
|
+
sendFormData: async (
|
|
30
|
+
{ commit, state }: TrezorContext,
|
|
31
|
+
{ payload, typeLead }: { payload: object; typeLead: string },
|
|
32
|
+
) => {
|
|
33
|
+
if (!typeLead) {
|
|
34
|
+
const sendData = await sendTrezorForm(typeLead, payload);
|
|
35
|
+
commit('setFormData', sendData);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
package/src/stores/store.ts
CHANGED
|
@@ -9,6 +9,7 @@ import toolsStore from './modules/toolsStore';
|
|
|
9
9
|
import documentsStore from './modules/documentsStore';
|
|
10
10
|
import consentStore from './modules/consentStore';
|
|
11
11
|
import productsStore from './modules/productsStore';
|
|
12
|
+
import trezorStore from './modules/trezorStore';
|
|
12
13
|
import cloneDeep from 'lodash.clonedeep';
|
|
13
14
|
import { State } from '@/stores/state';
|
|
14
15
|
import { ActionContext } from 'vuex';
|
|
@@ -28,6 +29,7 @@ export default {
|
|
|
28
29
|
documentsPictures: cloneDeep(documentsStore),
|
|
29
30
|
consent: consentStore,
|
|
30
31
|
products: productsStore,
|
|
32
|
+
trezor: trezorStore,
|
|
31
33
|
},
|
|
32
34
|
mutations: {
|
|
33
35
|
eventBusSendEvent(state: State, { code, payload }: { code: string; payload: any }) {
|