project-booster-vue 8.128.10 → 8.131.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-booster-vue",
3
- "version": "8.128.10",
3
+ "version": "8.131.0",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "test:unit": "vue-cli-service test:unit --forceExit --detectOpenHandles",
@@ -0,0 +1,135 @@
1
+ <template>
2
+ <m-flex direction="column" full-width class="pb-consent">
3
+ <span class="pb-consent__label">
4
+ Faites le plein d’inspirations et de promotions pour votre projet chaque semaine en vous abonnant à notre
5
+ newsletter
6
+ </span>
7
+ <m-flex
8
+ v-if="isMailConsentUpdating"
9
+ class="pb-consent__loading"
10
+ direction="column"
11
+ align-items="center"
12
+ justify-content="center"
13
+ align-content="center"
14
+ >
15
+ <pb-animable-loader class="pb-consent__loading-loader" />
16
+ <div class="pb-consent__loading-label">Chargement...</div>
17
+ </m-flex>
18
+ <m-flex v-else class="pb-consent__actions">
19
+ <m-button
20
+ class="pb-consent__actions--button"
21
+ label="S'abonner à la newsletter"
22
+ theme="bordered"
23
+ @click="handleUpdateConsent"
24
+ v-if="!errorUpdateMailConsent && !isMailConsentUpdated"
25
+ />
26
+ <m-notification
27
+ v-if="isMailConsentUpdated"
28
+ class="pb-consent__actions--notification"
29
+ type="success"
30
+ text="Merci, votre inscription à la newsletter est confirmée"
31
+ />
32
+ <m-notification
33
+ v-if="errorUpdateMailConsent"
34
+ class="pb-consent__actions--notification"
35
+ type="warning"
36
+ text="Une erreur est survenue"
37
+ link-label="Merci de réessayer"
38
+ @link-click="handleRetry"
39
+ />
40
+ </m-flex>
41
+ </m-flex>
42
+ </template>
43
+
44
+ <script>
45
+ import MFlex from '../mozaic/flex/MFlex';
46
+ import MButton from '../mozaic/buttons/MButton';
47
+ import MNotification from '../mozaic/notifications/MNotification';
48
+ import PbAnimableLoader from '../loader/PbAnimableLoader';
49
+ import { useStore } from 'vuex';
50
+ import { computed, onMounted } from 'vue';
51
+
52
+ export default {
53
+ name: 'PbConsent',
54
+
55
+ components: {
56
+ MFlex,
57
+ MButton,
58
+ MNotification,
59
+ PbAnimableLoader,
60
+ },
61
+
62
+ setup() {
63
+ const store = useStore();
64
+ const isMailConsentUpdating = computed(() => store.getters['consent/isMailConsentUpdating']);
65
+ const isMailConsentUpdated = computed(() => store.getters['consent/isMailConsentUpdated']);
66
+ const errorUpdateMailConsent = computed(() => store.getters['consent/getErrorUpdateMailConsent']);
67
+
68
+ const handleUpdateConsent = () => {
69
+ store.dispatch('sendEventToBus', {
70
+ code: 'UPDATE-MAIL-CONSENT',
71
+ payload: {},
72
+ });
73
+ store.dispatch('consent/updateMailConsent');
74
+ };
75
+
76
+ const handleRetry = () => {
77
+ store.dispatch('sendEventToBus', {
78
+ code: 'RETRY-UPDATE-MAIL-CONSENT',
79
+ payload: {},
80
+ });
81
+ store.dispatch('consent/updateMailConsent');
82
+ };
83
+
84
+ onMounted(() => {
85
+ store.dispatch('sendEventToBus', {
86
+ code: 'DISPLAY-MAIL-CONSENT',
87
+ payload: {},
88
+ });
89
+ });
90
+
91
+ return {
92
+ isMailConsentUpdating,
93
+ isMailConsentUpdated,
94
+ errorUpdateMailConsent,
95
+ handleUpdateConsent,
96
+ handleRetry,
97
+ };
98
+ },
99
+ };
100
+ </script>
101
+
102
+ <style lang="scss" scoped>
103
+ @import 'pb-variables';
104
+
105
+ .pb-consent {
106
+ &__label {
107
+ @include set-font-face('regular');
108
+ @include set-font-scale('06', 'm');
109
+
110
+ color: $color-grey-900;
111
+ }
112
+
113
+ &__actions {
114
+ width: 100%;
115
+
116
+ &--notification {
117
+ width: 100%;
118
+ }
119
+
120
+ &--button {
121
+ margin-top: $mu150;
122
+ }
123
+ }
124
+
125
+ &__loading {
126
+ margin: auto;
127
+ padding-top: $mu100;
128
+
129
+ &-loader {
130
+ height: $mu300;
131
+ width: $mu300;
132
+ }
133
+ }
134
+ }
135
+ </style>
@@ -352,38 +352,38 @@ export default {
352
352
  {
353
353
  title: 'Cuisine',
354
354
  image: 'https://storage.googleapis.com/project-booster-media/landing-projet/projet-cuisine.png',
355
- href: '/cuisine-lp.html',
355
+ href: '/produits/cuisine/',
356
356
  },
357
357
  {
358
358
  title: 'Salle de bains',
359
359
  image: 'https://storage.googleapis.com/project-booster-media/landing-projet/projet-salle_de_bain.png',
360
- href: '/salle-de-bain-lp.html',
360
+ href: '/produits/salle-de-bains/',
361
361
  },
362
362
  {
363
363
  title: 'Rangement',
364
364
  image: 'https://storage.googleapis.com/project-booster-media/landing-projet/projet-rangement.png',
365
- href: '/comment-choisir/mon-projet-rangement-en-5-etapes.html',
365
+ href: '/produits/rangement-dressing/',
366
366
  },
367
367
  {
368
368
  title: 'Aménagement extérieur',
369
369
  image: 'https://storage.googleapis.com/project-booster-media/landing-projet/projet-amenagement_exterieur.png',
370
- href: '/comment-choisir/mon-projet-terrasse-et-sol-exterieur-en-5-etapes.html',
370
+ href: '/produits/terrasse-jardin/',
371
371
  },
372
372
  {
373
373
  title: 'Menuiserie',
374
374
  image: 'https://storage.googleapis.com/project-booster-media/landing-projet/projet-menuiserie.png',
375
- href: '/comment-choisir/mon-projet-fenetres-en-6-etapes.html',
375
+ href: '/produits/menuiserie/',
376
376
  },
377
377
  {
378
378
  title: 'Combles',
379
379
  image: 'https://storage.googleapis.com/project-booster-media/landing-projet/projet-materiaux.png',
380
- href: '/comment-choisir/tout-savoir-sur-amenagement-des-combles.html',
380
+ href: '/produits/materiaux/',
381
381
  },
382
382
  ],
383
383
  toolsCards3: [
384
384
  {
385
385
  image: 'https://storage.googleapis.com/project-booster-media/landing-projet/artisans-partenaires.png',
386
- href: '/cuisine-lp.html',
386
+ href: '/produits/cuisine/',
387
387
  },
388
388
  ],
389
389
  toolsCards4: [
@@ -0,0 +1,123 @@
1
+ import { Story, Meta, Canvas } from '@storybook/addon-docs';
2
+ import { nestedAppDecorator } from '../../../../.storybook/nested-app-decorator';
3
+ import PbProjectHub from './PbProjectHub';
4
+ import store from '../../../stores/store';
5
+ import { rest } from 'msw';
6
+ import { internalServerErrorResolver } from '../../../services/api/mocks/commonMock';
7
+ import { getProjectByIdResolver } from '../../../services/api/mocks/projectsMock';
8
+ import {
9
+ getMailConsentToUpdateResolver,
10
+ getMailConsentUpdatedResolver,
11
+ putConsentsUpdateResolver,
12
+ putConsentsUpdateWithDelayResolver,
13
+ } from '../../../services/api/mocks/consentMock';
14
+
15
+ <Meta
16
+ title="Project Booster/Components/Projects/PbProjectHub 🦠/Features/Consent (Mail)"
17
+ component={PbProjectHub}
18
+ parameters={{
19
+ layout: 'fullscreen',
20
+ }}
21
+ decorators={[
22
+ nestedAppDecorator(store, [
23
+ {
24
+ name: 'ProjectHub',
25
+ path: '/',
26
+ component: PbProjectHub,
27
+ beforeEnter: async (to, from, next, store) => {
28
+ store.dispatch('projects/loadProject', '001');
29
+ store.dispatch('consent/checkMailConsent');
30
+ next();
31
+ },
32
+ },
33
+ ]),
34
+ ]}
35
+ />
36
+
37
+ export const TemplateSandbox = (args, { argTypes }) => ({
38
+ props: Object.keys(argTypes),
39
+ template: `<div style="margin: 0 auto;min-height: 100vh;width: 100%;">
40
+ <router-view v-slot="{ Component }">
41
+ <component :is="Component" :key="$route.fullPath" />
42
+ </router-view>
43
+ </div>`,
44
+ });
45
+
46
+ # 🦠 `PbProjectHub` - Component
47
+
48
+ The `PbProjectHub` component to access your project.
49
+
50
+ ## Showcase with consent section to display
51
+
52
+ <Canvas>
53
+ <Story
54
+ name="Showcase with consent section"
55
+ parameters={{
56
+ controls: { disable: true },
57
+ msw: [
58
+ rest.get('/api/inhabitant-projects/:projectId', getProjectByIdResolver),
59
+ rest.get('/api/users/current/consents', getMailConsentToUpdateResolver),
60
+ rest.put('/api/users/current/consents', putConsentsUpdateResolver),
61
+ ],
62
+ }}
63
+ height="100vh"
64
+ >
65
+ {TemplateSandbox.bind({})}
66
+ </Story>
67
+ </Canvas>
68
+
69
+ ## Showcase with consent section to hide
70
+
71
+ <Canvas>
72
+ <Story
73
+ name="Showcase with consent section hidden"
74
+ parameters={{
75
+ controls: { disable: true },
76
+ msw: [
77
+ rest.get('/api/inhabitant-projects/:projectId', getProjectByIdResolver),
78
+ rest.get('/api/users/current/consents', getMailConsentUpdatedResolver),
79
+ ],
80
+ }}
81
+ height="100vh"
82
+ >
83
+ {TemplateSandbox.bind({})}
84
+ </Story>
85
+ </Canvas>
86
+
87
+ ## Showcase with failure on update consent
88
+
89
+ <Canvas>
90
+ <Story
91
+ name="Showcase with update failure"
92
+ parameters={{
93
+ controls: { disable: true },
94
+ msw: [
95
+ rest.get('/api/inhabitant-projects/:projectId', getProjectByIdResolver),
96
+ rest.get('/api/users/current/consents', getMailConsentToUpdateResolver),
97
+ rest.put('/api/users/current/consents', internalServerErrorResolver),
98
+ ],
99
+ }}
100
+ height="100vh"
101
+ >
102
+ {TemplateSandbox.bind({})}
103
+ </Story>
104
+ </Canvas>
105
+
106
+ ## Showcase with delay on update
107
+
108
+ <Canvas>
109
+ <Story
110
+ name="Showcase with delay on update"
111
+ parameters={{
112
+ controls: { disable: true },
113
+ msw: [
114
+ rest.get('/api/inhabitant-projects/:projectId', getProjectByIdResolver),
115
+ rest.get('/api/users/current/consents', getMailConsentToUpdateResolver),
116
+ rest.put('/api/users/current/consents', putConsentsUpdateWithDelayResolver),
117
+ ],
118
+ }}
119
+ height="100vh"
120
+ >
121
+ {TemplateSandbox.bind({})}
122
+ </Story>
123
+ </Canvas>
@@ -492,6 +492,14 @@
492
492
  </m-flex>
493
493
  </div>
494
494
  </m-flex>
495
+ <m-flex v-if="isMailConsentDisplayed" class="pb-project-hub__section" direction="column" full-width>
496
+ <div class="pb-project-hub__section-header">
497
+ <div class="pb-project-hub__section-title" ref="pbConsent">Souscription newsletter</div>
498
+ </div>
499
+ <div class="pb-project-hub__section-content">
500
+ <pb-consent />
501
+ </div>
502
+ </m-flex>
495
503
  <m-flex
496
504
  v-if="!readOnly"
497
505
  class="pb-project-hub__section pb-project-hub__section-separator"
@@ -794,6 +802,7 @@ import PbMOpinion from '../../mopinion/PbMOpinion';
794
802
  import PbProjectAttributes from '../project-attributes/PbProjectAttributes';
795
803
  import PbRestitution from '../../restitution/PbRestitution';
796
804
  import PbTasksPreview from '../../tasks/preview/PbTasksPreview';
805
+ import PbConsent from '../../consent/PbConsent';
797
806
  import TASK_MODEL from './plannerTask.json';
798
807
  import ESTIMATES_PAYLOAD from './../../estimates/estimates-payload.json';
799
808
  import cloneDeep from 'lodash.clonedeep';
@@ -852,6 +861,7 @@ export default {
852
861
  PbProjectAttributes,
853
862
  PbRestitution,
854
863
  PbTasksPreview,
864
+ PbConsent,
855
865
  },
856
866
 
857
867
  props: {
@@ -1001,6 +1011,10 @@ export default {
1001
1011
  configurationsLoadError: 'getConfigurationsLoadError',
1002
1012
  }),
1003
1013
 
1014
+ ...mapGetters('consent', {
1015
+ isMailConsentDisplayed: 'isMailConsentDisplayed',
1016
+ }),
1017
+
1004
1018
  ...mapGetters('inhabitants', {
1005
1019
  isInhabitantDisplayed: 'isInhabitantDisplayed',
1006
1020
  currentInhabitantId: 'getCurrentInhabitantId',
@@ -0,0 +1,33 @@
1
+ import axios from 'axios';
2
+ import he from 'he';
3
+
4
+ export const clientApi = axios.create({
5
+ baseURL: '/project-booster/api',
6
+ });
7
+
8
+ if (global.window.config) {
9
+ clientApi.defaults.baseURL = global.window.config.VUE_APP_DEFAULT_BASE_URL;
10
+ }
11
+
12
+ export const updateConsentApiClient = (config) => {
13
+ if (config.apiKey) {
14
+ clientApi.defaults.headers.common['X-ClientApiKey'] = config.apiKey;
15
+ }
16
+ if (config.baseUrl) {
17
+ clientApi.defaults.baseURL = config.baseUrl;
18
+ }
19
+ };
20
+
21
+ export const getConsents = async () => {
22
+ const response = await clientApi.get('/users/current/consents');
23
+
24
+ return JSON.parse(
25
+ JSON.stringify(response.data).replace(/:"([^"]+)"/g, (match, $1) => {
26
+ return `: "${he.escape($1)}"`;
27
+ }),
28
+ );
29
+ };
30
+
31
+ export const updateMailConsent = async (mailConsent) => {
32
+ return await clientApi.put('/users/current/consents', { mail: mailConsent });
33
+ };
@@ -0,0 +1,25 @@
1
+ export const getMailConsentToUpdateResolver = (req, res, ctx) => {
2
+ return res.once(
3
+ ctx.status(200),
4
+ ctx.json({
5
+ mail: false,
6
+ }),
7
+ );
8
+ };
9
+
10
+ export const getMailConsentUpdatedResolver = (req, res, ctx) => {
11
+ return res.once(
12
+ ctx.status(200),
13
+ ctx.json({
14
+ mail: true,
15
+ }),
16
+ );
17
+ };
18
+
19
+ export const putConsentsUpdateResolver = (req, res, ctx) => {
20
+ return res.once(ctx.status(204));
21
+ };
22
+
23
+ export const putConsentsUpdateWithDelayResolver = (req, res, ctx) => {
24
+ return res.once(ctx.delay(1000), ctx.status(204));
25
+ };
@@ -0,0 +1,68 @@
1
+ import { updateMailConsent, getConsents } from '../../services/api/consentApi';
2
+
3
+ export default {
4
+ namespaced: true,
5
+
6
+ state: {
7
+ isMailConsentDisplayed: false,
8
+ isMailConsentUpdated: false,
9
+ isMailConsentUpdating: false,
10
+ errorUpdateMailConsent: null,
11
+ },
12
+
13
+ getters: {
14
+ isMailConsentDisplayed(state) {
15
+ return state.isMailConsentDisplayed;
16
+ },
17
+ isMailConsentUpdated(state) {
18
+ return state.isMailConsentUpdated;
19
+ },
20
+ getErrorUpdateMailConsent(state) {
21
+ return state.errorUpdateMailConsent;
22
+ },
23
+ isMailConsentUpdating(state) {
24
+ return state.isMailConsentUpdating;
25
+ },
26
+ },
27
+
28
+ mutations: {
29
+ setIsMailConsentDisplayed(state, isDisplayed) {
30
+ state.isMailConsentDisplayed = isDisplayed;
31
+ },
32
+ setIsMailConsentUpdated(state, isUpdated) {
33
+ state.isMailConsentUpdated = isUpdated;
34
+ },
35
+ setErrorUpdateMailConsent(state, error) {
36
+ state.errorUpdateMailConsent = error;
37
+ },
38
+ setIsMailConsentUpdating(state, isUpdating) {
39
+ state.isMailConsentUpdating = isUpdating;
40
+ },
41
+ },
42
+
43
+ actions: {
44
+ async checkMailConsent({ commit }) {
45
+ try {
46
+ const consents = await getConsents();
47
+ if (!consents.mail) {
48
+ commit('setIsMailConsentDisplayed', true);
49
+ }
50
+ } catch (error) {
51
+ console.error(error);
52
+ }
53
+ },
54
+ setMailConsentDisplayed({ commit }, isDisplayed) {
55
+ commit('setIsMailConsentDisplayed', isDisplayed);
56
+ },
57
+ async updateMailConsent({ commit }) {
58
+ commit('setIsMailConsentUpdating', true);
59
+ try {
60
+ await updateMailConsent(true);
61
+ commit('setIsMailConsentUpdated', true);
62
+ } catch (error) {
63
+ commit('setErrorUpdateMailConsent', error);
64
+ }
65
+ commit('setIsMailConsentUpdating', false);
66
+ },
67
+ },
68
+ };
@@ -8,6 +8,7 @@ import plannerStore from './modules/plannerStore';
8
8
  import projectsStore from './modules/projectsStore';
9
9
  import toolsStore from './modules/toolsStore';
10
10
  import mediaDocumentsStore from './modules/mediaDocumentsStore';
11
+ import consentStore from './modules/consentStore';
11
12
  import cloneDeep from 'lodash.clonedeep';
12
13
 
13
14
  export default {
@@ -25,6 +26,7 @@ export default {
25
26
  media: cloneDeep(mediaDocumentsStore),
26
27
  documentsPlans: cloneDeep(mediaDocumentsStore),
27
28
  documentsPictures: cloneDeep(mediaDocumentsStore),
29
+ consent: consentStore,
28
30
  },
29
31
  mutations: {
30
32
  eventBusSendEvent(state, { code, payload }) {