@ecomplus/widget-martan 1.1.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/README.md +11 -0
  3. package/cms.config.js +264 -0
  4. package/dist/public/widget-martan.1.min.js +2 -0
  5. package/dist/public/widget-martan.1.min.js.map +1 -0
  6. package/dist/public/widget-martan.2.min.js +2 -0
  7. package/dist/public/widget-martan.2.min.js.map +1 -0
  8. package/dist/public/widget-martan.3.min.js +2 -0
  9. package/dist/public/widget-martan.3.min.js.map +1 -0
  10. package/dist/public/widget-martan.4.min.js +2 -0
  11. package/dist/public/widget-martan.4.min.js.map +1 -0
  12. package/dist/public/widget-martan.5.min.js +2 -0
  13. package/dist/public/widget-martan.5.min.js.map +1 -0
  14. package/dist/public/widget-martan.var.min.js +71 -0
  15. package/dist/public/widget-martan.var.min.js.map +1 -0
  16. package/dist/widget-martan.1.min.js +2 -0
  17. package/dist/widget-martan.1.min.js.map +1 -0
  18. package/dist/widget-martan.2.min.js +2 -0
  19. package/dist/widget-martan.2.min.js.map +1 -0
  20. package/dist/widget-martan.3.min.js +2 -0
  21. package/dist/widget-martan.3.min.js.map +1 -0
  22. package/dist/widget-martan.4.min.js +2 -0
  23. package/dist/widget-martan.4.min.js.map +1 -0
  24. package/dist/widget-martan.5.min.js +2 -0
  25. package/dist/widget-martan.5.min.js.map +1 -0
  26. package/dist/widget-martan.min.js +71 -0
  27. package/dist/widget-martan.min.js.map +1 -0
  28. package/package.json +36 -0
  29. package/src/append/body.ejs +79 -0
  30. package/src/append/head.ejs +5 -0
  31. package/src/append/product-block.ejs +5 -0
  32. package/src/append/product-card-slots.ejs +3 -0
  33. package/src/append/product-slots.ejs +25 -0
  34. package/src/append/stamps.ejs +3 -0
  35. package/src/index.js +22 -0
  36. package/src/utils/get-width.js +11 -0
  37. package/src/utils/textToNumber.js +31 -0
  38. package/src/utils/time-ago.js +35 -0
  39. package/src/widgets/reviews/AuthorAndRating.vue +30 -0
  40. package/src/widgets/reviews/AverageScore.vue +160 -0
  41. package/src/widgets/reviews/AverageTotal.vue +35 -0
  42. package/src/widgets/reviews/CardReview.vue +87 -0
  43. package/src/widgets/reviews/GridView.vue +113 -0
  44. package/src/widgets/reviews/HeaderExpanded.vue +84 -0
  45. package/src/widgets/reviews/HeaderMinimal.vue +110 -0
  46. package/src/widgets/reviews/ListView.vue +47 -0
  47. package/src/widgets/reviews/Quickview.vue +398 -0
  48. package/src/widgets/reviews/ReviewBody.vue +39 -0
  49. package/src/widgets/reviews/ReviewReply.vue +57 -0
  50. package/src/widgets/reviews/Reviews.vue +496 -0
  51. package/src/widgets/reviews/Score.vue +186 -0
  52. package/src/widgets/reviews/Sort.vue +76 -0
  53. package/src/widgets/reviews/ThumbsPictures.vue +135 -0
  54. package/src/widgets/reviews/Total.vue +79 -0
  55. package/src/widgets/reviews/VerifiedPurchase.vue +111 -0
  56. package/src/widgets/reviews/VideoPlayer.vue +136 -0
  57. package/src/widgets/reviews/index.js +52 -0
  58. package/src/widgets/reviews/isRecommended.vue +44 -0
  59. package/src/widgets/snippets/Rating.vue +71 -0
  60. package/src/widgets/snippets/Snippets.vue +311 -0
  61. package/src/widgets/snippets/index.js +45 -0
  62. package/webpack.config.js +1 -0
@@ -0,0 +1,136 @@
1
+ <template>
2
+ <div class="mt-player">
3
+ <video preload="metadata" ref="videoPlayer">
4
+ <source :src="video" type="video/mp4" />
5
+ </video>
6
+
7
+ <div class="mt-player__controls">
8
+ <button ref="playButton" class="playPauseBtn" title="tocar vídeo">
9
+ <svg
10
+ v-if="this.isPlaying"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ class="icon icon-tabler icon-tabler-player-play"
13
+ width="24"
14
+ height="24"
15
+ viewBox="0 0 24 24"
16
+ stroke-width="2"
17
+ stroke="currentColor"
18
+ fill="none"
19
+ stroke-linecap="round"
20
+ stroke-linejoin="round"
21
+ >
22
+ <path stroke="none" d="M0 0h24v24H0z" fill="none" />
23
+ <path d="M7 4v16l13 -8z" />
24
+ </svg>
25
+ <svg
26
+ v-else
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ class="icon icon-tabler icon-tabler-player-pause"
29
+ width="24"
30
+ height="24"
31
+ viewBox="0 0 24 24"
32
+ stroke-width="2"
33
+ stroke="currentColor"
34
+ fill="none"
35
+ stroke-linecap="round"
36
+ stroke-linejoin="round"
37
+ >
38
+ <path stroke="none" d="M0 0h24v24H0z" fill="none" />
39
+ <path
40
+ d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"
41
+ />
42
+ <path
43
+ d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"
44
+ />
45
+ </svg>
46
+ </button>
47
+ </div>
48
+ </div>
49
+ </template>
50
+
51
+ <script>
52
+ export default {
53
+ name: "VideoPlayer",
54
+
55
+ props: {
56
+ video: {
57
+ type: String,
58
+ required: true
59
+ },
60
+ },
61
+
62
+ data() {
63
+ return {
64
+ isPlaying: true,
65
+ };
66
+ },
67
+
68
+ methods: {
69
+ play() {
70
+ if (this.$refs.videoPlayer.paused || this.$refs.videoPlayer.ended) {
71
+ this.$refs.videoPlayer.play();
72
+ } else {
73
+ this.$refs.videoPlayer.pause();
74
+ }
75
+ },
76
+ updateBtn() {
77
+ this.isPlaying = this.$refs.videoPlayer.paused;
78
+ },
79
+ },
80
+ mounted: function () {
81
+ this.$refs.playButton.addEventListener("click", this.play);
82
+ this.$refs.videoPlayer.addEventListener("click", this.play);
83
+ this.$refs.videoPlayer.addEventListener("play", this.updateBtn);
84
+ this.$refs.videoPlayer.addEventListener("pause", this.updateBtn);
85
+ },
86
+ };
87
+ </script>
88
+
89
+ <style lang="scss">
90
+ .mt-player {
91
+ video {
92
+ display: block;
93
+ width: 100%;
94
+ }
95
+
96
+ .playPauseBtn {
97
+ background: none;
98
+ border: 0;
99
+ line-height: 1;
100
+ color: #212529;
101
+ text-align: center;
102
+ outline: 0;
103
+ padding: 0;
104
+ cursor: pointer;
105
+ max-width: 50px;
106
+ }
107
+
108
+ &__controls {
109
+ width: 100%;
110
+ transform: translateY(0);
111
+ padding: 5px;
112
+ }
113
+
114
+ &__controls > * {
115
+ flex: 1;
116
+ }
117
+
118
+ &__progress {
119
+ flex: 10;
120
+ position: relative;
121
+ display: flex;
122
+ flex-basis: 100%;
123
+ height: 5px;
124
+ background: rgba(255, 255, 255, 0.5);
125
+ cursor: pointer;
126
+ max-width: 430px;
127
+ width: 100%;
128
+ &--filled {
129
+ width: 50%;
130
+ background: #aa5500;
131
+ flex: 0;
132
+ flex-basis: 0%;
133
+ }
134
+ }
135
+ }
136
+ </style>
@@ -0,0 +1,52 @@
1
+ import Vue from 'vue'
2
+ import Reviews from './Reviews.vue'
3
+
4
+ export default (options = {}, elId = 'reviews_widget') => {
5
+ const $el = document.getElementById(elId)
6
+ if (!options || !$el || !$el.dataset || !$el.dataset.product) {
7
+ return
8
+ }
9
+
10
+ const { product } = $el.dataset
11
+
12
+ const storeId = options.store_id
13
+ const webId = options.web_id
14
+ const widgetOptions = options.widget_review || {}
15
+
16
+ const starColor = widgetOptions.star_color || null
17
+ const title = widgetOptions.title || null
18
+ const headerLayout = widgetOptions.header_layout || null
19
+ const reviewsLayout = widgetOptions.reviews_layout || null
20
+
21
+ const obs = new IntersectionObserver(
22
+ (entries, observe) => {
23
+ entries.forEach(function (entry) {
24
+ const { isIntersecting, intersectionRatio } = entry
25
+ if (isIntersecting === true || intersectionRatio > 0) {
26
+ new Vue({
27
+ render(h) {
28
+ return h(Reviews, {
29
+ props: {
30
+ product,
31
+ storeId,
32
+ webId,
33
+ title,
34
+ starColor,
35
+ headerLayout,
36
+ reviewsLayout
37
+ }
38
+ })
39
+ }
40
+ }).$mount($el)
41
+
42
+ observe.disconnect()
43
+ }
44
+ })
45
+ },
46
+ {
47
+ rootMargin: '250px'
48
+ }
49
+ )
50
+
51
+ obs.observe($el)
52
+ }
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <div
3
+ class="mt-is__recomended"
4
+ :title="
5
+ recommended
6
+ ? i19customerRecommendProduct
7
+ : i19customerNoRecommendProduct
8
+ "
9
+ >
10
+ <span> {{ i19recommendProductQn }} </span>
11
+ <span>{{ recommended ? i19yes : i19no }}</span>
12
+ </div>
13
+ </template>
14
+
15
+ <script>
16
+
17
+ import {
18
+ i19yes,
19
+ i19no
20
+ } from '@ecomplus/i18n'
21
+
22
+ import { i18n } from '@ecomplus/utils'
23
+
24
+ export default {
25
+
26
+ name: "isRecommended",
27
+
28
+ props: {
29
+ recommended: {
30
+ type: Boolean,
31
+ default: true
32
+ }
33
+ },
34
+
35
+ computed: {
36
+ i19customerRecommendProduct: () => 'Cliente recomendaria o produto',
37
+ i19customerNoRecommendProduct: () => 'Cliente não recomendaria o produto',
38
+ i19no: () => i18n(i19no),
39
+ i19recommendProductQn: () => 'Recomendaria o produto ?',
40
+ i19yes: () => i18n(i19yes),
41
+ },
42
+
43
+ };
44
+ </script>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <span class="martan-rating">
3
+ <svg
4
+ v-for="(star, index) in stars"
5
+ :key="index"
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ class="icon icon-tabler"
8
+ :class="`${stars[index]} ? 'icon-tabler-star-filled' : icon-tabler-star`"
9
+ width="14"
10
+ height="14"
11
+ viewBox="0 0 24 24"
12
+ stroke-width="2"
13
+ stroke="currentColor"
14
+ fill="none"
15
+ stroke-linecap="round"
16
+ stroke-linejoin="round"
17
+ :style="{ color }"
18
+ >
19
+ <path stroke="none" d="M0 0h24v24H0z" fill="none" />
20
+ <path
21
+ :d="stars[index] ? 'M8.243 7.34l-6.38 .925l-.113 .023a1 1 0 0 0 -.44 1.684l4.622 4.499l-1.09 6.355l-.013 .11a1 1 0 0 0 1.464 .944l5.706 -3l5.693 3l.1 .046a1 1 0 0 0 1.352 -1.1l-1.091 -6.355l4.624 -4.5l.078 -.085a1 1 0 0 0 -.633 -1.62l-6.38 -.926l-2.852 -5.78a1 1 0 0 0 -1.794 0l-2.853 5.78z' : 'M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z'"
22
+ :fill="stars[index] ? 'currentColor' : 'none'" />
23
+ </svg>
24
+ </span>
25
+ </template>
26
+
27
+ <script>
28
+ export default {
29
+ name: 'Rating',
30
+
31
+ props: {
32
+ rating: {
33
+ type: Number,
34
+ default: 5
35
+ },
36
+
37
+ color: {
38
+ type: String,
39
+ default: "#000000"
40
+ },
41
+ },
42
+
43
+ data() {
44
+ return {
45
+ stars: Array(5).fill(false)
46
+ };
47
+ },
48
+
49
+ mounted() {
50
+ this.updateStars();
51
+ },
52
+
53
+ methods: {
54
+ updateStars() {
55
+ const filledStars = Math.floor(this.rating);
56
+ this.stars = this.stars.map((star, index) => index < filledStars);
57
+ }
58
+ },
59
+
60
+ watch: {
61
+ rating: 'updateStars'
62
+ }
63
+ };
64
+ </script>
65
+
66
+ <style lang="css">
67
+ .martan-rating {
68
+ display: flex;
69
+ gap: 4px;
70
+ }
71
+ </style>
@@ -0,0 +1,311 @@
1
+ <template>
2
+ <div
3
+ id="mt-snippet"
4
+ ref="wrapper"
5
+ class="glide martan-snippets d-none"
6
+ style="margin-bottom: 1rem; height: 85px"
7
+ >
8
+ <div class="glide__track" data-glide-el="track">
9
+ <ul class="glide__slides">
10
+ <li class="glide__slide" v-for="review in list" :key="review.id">
11
+ <div class="martan-snippets__galery" v-if="getImageUrl(review)">
12
+ <img
13
+ :alt="`Foto da avaliação do produto feita por ${review.display_name}`"
14
+ :src="getImageUrl(review)"
15
+ />
16
+ </div>
17
+
18
+ <div class="martan-snippets__reviews snippet-review">
19
+ <div class="martan-snippets__reviews-info snippet-review__info">
20
+ <span>{{ review.display_name }}</span>
21
+ <rating :rating="review.rating" :color="starColor" />
22
+ </div>
23
+
24
+ <div
25
+ class="martan-snippets__reviews-body snippet-review__body"
26
+ v-if="review.body"
27
+ >
28
+ <span>{{ review.body.substring(0, 115) }}...</span>
29
+ </div>
30
+ </div>
31
+ </li>
32
+ </ul>
33
+ </div>
34
+
35
+ <div class="martan-snippets__controls" data-glide-el="controls">
36
+ <button
37
+ v-if="glide && glide.index > 0"
38
+ :title="i19previous"
39
+ :aria-label="i19previous"
40
+ class="left"
41
+ data-glide-dir="<"
42
+ v-on:click="glide.go('<')"
43
+ >
44
+ <i class="i-chevron-left"></i>
45
+ </button>
46
+
47
+ <button
48
+ v-if="glide && glide.index !== list.length - 1"
49
+ :title="i19next"
50
+ :aria-label="i19next"
51
+ class="right"
52
+ data-glide-dir=">"
53
+ v-on:click="glide.go('>')"
54
+ :data-glide="glide.index"
55
+ >
56
+ <i class="i-chevron-right"></i>
57
+ </button>
58
+ </div>
59
+ </div>
60
+ </template>
61
+
62
+ <script>
63
+ import axios from "axios";
64
+ import Glide, { Breakpoints } from "@glidejs/glide";
65
+ import { i19next, i19previous } from "@ecomplus/i18n";
66
+ import { i18n } from "@ecomplus/utils";
67
+
68
+ import Rating from "./Rating.vue";
69
+ import Quickview from "../reviews/Quickview.vue";
70
+ import { MARTAN_API } from "../..";
71
+
72
+ export default {
73
+ name: "SnippetWidget",
74
+
75
+ components: {
76
+ Rating,
77
+ Quickview,
78
+ },
79
+
80
+ data: function () {
81
+ return {
82
+ list: [],
83
+ glide: null,
84
+ isVisible: false,
85
+ isOpenQuickView: false,
86
+ selectedReview: null,
87
+ };
88
+ },
89
+
90
+ computed: {
91
+ i19next: () => i18n(i19next),
92
+ i19previous: () => i18n(i19previous),
93
+ },
94
+
95
+ props: {
96
+ storeId: {
97
+ type: Number,
98
+ required: true,
99
+ },
100
+ webId: {
101
+ type: String,
102
+ required: true,
103
+ },
104
+ product: {
105
+ type: String,
106
+ required: true,
107
+ },
108
+ starColor: {
109
+ type: String,
110
+ required: true,
111
+ },
112
+ backgroundColor: {
113
+ type: String,
114
+ default: "#f8f9fa",
115
+ },
116
+ textColor: {
117
+ type: String,
118
+ default: "#212529",
119
+ },
120
+ border: {
121
+ type: Boolean,
122
+ default: false,
123
+ },
124
+ borderColor: {
125
+ type: String,
126
+ default: "#212529",
127
+ },
128
+ },
129
+
130
+ methods: {
131
+ openQuickview: function ({ review, slide }) {
132
+ this.selectedReview = review;
133
+ this.isOpenQuickView = true;
134
+ },
135
+
136
+ onCloseQuickview: function () {
137
+ this.isOpenQuickView = false;
138
+ this.selectedReview = null;
139
+ },
140
+
141
+ fetch() {
142
+ axios({
143
+ url: MARTAN_API + "/reviews.json",
144
+ headers: {
145
+ "X-Store-Id": this.storeId,
146
+ "X-Web-Id": this.webId,
147
+ },
148
+ params: {
149
+ sku: this.product,
150
+ limit: 10,
151
+ offset: 0,
152
+ },
153
+ })
154
+ .then(({ data }) => {
155
+ if (data && data.result) {
156
+ const filted = data.result.filter((r) => r.body);
157
+ this.list = filted || [];
158
+
159
+ this.glide = new Glide("#mt-snippet", {
160
+ keyboard: false,
161
+ rewind: false,
162
+ type: "slider",
163
+ startAt: 0,
164
+ perView: 1,
165
+ touchAngle: 0,
166
+ breakpoints: {
167
+ 1900: {
168
+ perView: 1,
169
+ },
170
+ 1024: {
171
+ perView: 1,
172
+ },
173
+ 600: {
174
+ perView: 1,
175
+ },
176
+ },
177
+ });
178
+
179
+ return filted;
180
+ }
181
+
182
+ return []
183
+ })
184
+ .then((data) => {
185
+ if (data.length >= 2) {
186
+ this.$refs.wrapper.classList.remove("d-none");
187
+ this.glide.mount();
188
+ this.isVisible = true;
189
+ }
190
+
191
+ return this;
192
+ })
193
+ .then((t) => {
194
+ if (t.isVisible) {
195
+ this.glide.update({ perView: 1 });
196
+ }
197
+ });
198
+ },
199
+
200
+ getImageUrl(review) {
201
+ if (
202
+ review &&
203
+ review.pictures &&
204
+ review.pictures.length &&
205
+ review.pictures[0].thumb
206
+ ) {
207
+ return review.pictures[0].thumb;
208
+ }
209
+ return null;
210
+ },
211
+
212
+ loadWidgetOptions() {
213
+ this.$refs.wrapper.style.color = this.textColor;
214
+ this.$refs.wrapper.style.backgroundColor = this.backgroundColor;
215
+ if (this.border) {
216
+ this.$refs.wrapper.style.border = `1px solid ${this.borderColor}`;
217
+ }
218
+ },
219
+ },
220
+
221
+ mounted: function () {
222
+ if (this.storeId && this.webId && this.product) {
223
+ this.fetch();
224
+ this.loadWidgetOptions();
225
+ }
226
+ },
227
+ };
228
+ </script>
229
+
230
+ <style scoped lang="scss">
231
+ .martan-snippets {
232
+ max-width: 525px;
233
+ border-radius: 8px;
234
+ display: flex;
235
+ overflow: hidden;
236
+ background-color: #e3e3e3;
237
+ box-shadow: none;
238
+
239
+ &__galery {
240
+ img {
241
+ border-radius: 6px;
242
+ aspect-ratio: 1 / 1;
243
+ width: 60px;
244
+ object-fit: cover;
245
+ }
246
+ }
247
+
248
+ &__reviews {
249
+ height: 60px;
250
+ max-width: fit-content;
251
+ flex: 1 1 0%;
252
+ display: flex;
253
+ flex-direction: column;
254
+ // overflow: hidden;
255
+
256
+ &-info {
257
+ font-weight: 500;
258
+ display: flex;
259
+ align-items: center;
260
+ gap: 10px;
261
+ }
262
+ }
263
+
264
+ &__controls {
265
+ button {
266
+ background: #fff;
267
+ border-radius: 50%;
268
+ border: none;
269
+ color: var(--secondary);
270
+ cursor: pointer;
271
+ display: block;
272
+ font-size: 10px;
273
+ line-height: 1;
274
+ opacity: 0.9;
275
+ position: absolute;
276
+ top: 50%;
277
+ transform: translateY(-50%);
278
+ transition: opacity 0.2s;
279
+ z-index: 2;
280
+ height: 20px;
281
+ width: 20px;
282
+ box-shadow: 0px -2px 12px rgba(0, 0, 0, 0.08);
283
+ transition: all ease-in-out 0.3s;
284
+ &:hover {
285
+ opacity: 0.7;
286
+ }
287
+ &.left {
288
+ left: -5px;
289
+ }
290
+
291
+ &.right {
292
+ right: -5px;
293
+ }
294
+ }
295
+ }
296
+
297
+ .glide__slide {
298
+ margin-left: 5px;
299
+ margin-right: 5px;
300
+ font-size: 14px;
301
+ line-height: 1.5;
302
+ font-weight: normal;
303
+ scroll-snap-align: start;
304
+ display: flex;
305
+ align-items: center;
306
+ justify-content: stretch;
307
+ padding: 12px;
308
+ gap: 12px;
309
+ }
310
+ }
311
+ </style>
@@ -0,0 +1,45 @@
1
+ import Vue from 'vue'
2
+ import Snippets from './Snippets.vue'
3
+
4
+ export default (options = {}, elId = 'snippet_widget') => {
5
+ const $el = document.getElementById(elId)
6
+
7
+ if (!options || !$el || !$el.dataset || !$el.dataset.sku) {
8
+ return
9
+ }
10
+
11
+ const { sku } = $el.dataset
12
+
13
+ const storeId = options.store_id
14
+ const webId = options.web_id
15
+ const widgetOptions = options.widget_snippet || {}
16
+
17
+ const starColor = widgetOptions.star_color || null
18
+ const border = widgetOptions.border || false
19
+ const backgroundColor = widgetOptions.background_color || null
20
+ const textColor = widgetOptions.text_color || null
21
+ const borderColor = widgetOptions.border_color || null
22
+
23
+ const obs = new IntersectionObserver((_, observe) => {
24
+ new Vue({
25
+ render(h) {
26
+ return h(Snippets, {
27
+ props: {
28
+ product: sku,
29
+ storeId,
30
+ webId,
31
+ starColor,
32
+ border,
33
+ borderColor,
34
+ backgroundColor,
35
+ textColor
36
+ }
37
+ })
38
+ }
39
+ }).$mount($el)
40
+
41
+ observe.disconnect()
42
+ })
43
+
44
+ obs.observe($el)
45
+ }
@@ -0,0 +1 @@
1
+ module.exports = require('../../webpack.config')