@hulkapps/app-manager-vue 2.5.12 → 3.0.3
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/dist/ArrowLeft.svg +14 -0
- package/dist/CheckFalse.svg +11 -0
- package/dist/CheckTrue.svg +11 -0
- package/dist/NavigationLeft.svg +12 -0
- package/dist/NavigationRight.svg +12 -0
- package/dist/app-manager-vue.esm.css +1 -0
- package/dist/app-manager-vue.esm.js +8655 -1442
- package/dist/app-manager-vue.min.css +1 -0
- package/dist/app-manager-vue.min.js +4 -4
- package/dist/app-manager-vue.ssr.css +1 -0
- package/dist/app-manager-vue.ssr.js +8282 -1577
- package/dist/cogsettings.svg +15 -0
- package/dist/hulkapps-app-manager.css +1 -1
- package/dist/hulkapps-app-manager.min.css +1 -1
- package/package.json +5 -1
- package/src/components/Plans/AppManagerGroupPlan.vue +12 -3
- package/src/components/Plans/AppManagerPlan.vue +16 -3
- package/src/components/Plans/AppManagerSliderPlan.vue +940 -953
- package/src/components/PolarisNew/BundlePlanCard.vue +153 -0
- package/src/components/PolarisNew/CustomizationModal.vue +297 -0
- package/src/components/PolarisNew/GetCustomBlock.vue +93 -0
- package/src/components/PolarisNew/PlanCardsHighlights.vue +746 -0
- package/src/components/PolarisNew/PlanShowcaseBanner.vue +51 -20
- package/src/components/PolarisNew/PlanTable.vue +900 -0
- package/src/components/PolarisNew/SelectButton.vue +95 -0
- package/src/components/PolarisNew/ToggleButton.vue +92 -0
- package/src/components/PolarisNew/VariantButton.vue +134 -12
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Swiper, { Navigation, Pagination } from "swiper";
|
|
3
|
+
import "swiper/swiper-bundle.css";
|
|
4
|
+
import VariantButton from "./VariantButton";
|
|
5
|
+
import {calculatePlanPriceWithDiscounts, formatFeature} from "@/helpers";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
data() {
|
|
9
|
+
return {
|
|
10
|
+
isSyncing: false,
|
|
11
|
+
remainingPlansMonthly: {
|
|
12
|
+
before: 0,
|
|
13
|
+
after: 0
|
|
14
|
+
},
|
|
15
|
+
remainingPlansAnnually: {
|
|
16
|
+
before: 0,
|
|
17
|
+
after: 0
|
|
18
|
+
},
|
|
19
|
+
anyMonthlyPlanHasDiscount: false,
|
|
20
|
+
anyAnnuallyPlanHasDiscount: false,
|
|
21
|
+
loadingPlanId: null,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
name: "PlanTable",
|
|
25
|
+
components: {
|
|
26
|
+
VariantButton,
|
|
27
|
+
},
|
|
28
|
+
props: {
|
|
29
|
+
plans: {
|
|
30
|
+
type: Array,
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
33
|
+
currentPlan: {
|
|
34
|
+
type: Object,
|
|
35
|
+
required: false,
|
|
36
|
+
},
|
|
37
|
+
selectedInterval: {
|
|
38
|
+
type: String,
|
|
39
|
+
required: false,
|
|
40
|
+
},
|
|
41
|
+
promotionalDiscount: {
|
|
42
|
+
type: Object,
|
|
43
|
+
required: false,
|
|
44
|
+
},
|
|
45
|
+
features: {
|
|
46
|
+
type: Array,
|
|
47
|
+
required: false,
|
|
48
|
+
default: () => []
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
methods: {
|
|
52
|
+
formatFeature,
|
|
53
|
+
async handlePlanClick(plan) {
|
|
54
|
+
this.loadingPlanId = plan.id;
|
|
55
|
+
try {
|
|
56
|
+
await this.$emit("plan-clicked", plan);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Error handling plan click:', error);
|
|
59
|
+
this.loadingPlanId = null;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
translateMe(message) {
|
|
63
|
+
return this.$translations.hasOwnProperty(message)
|
|
64
|
+
? this.$translations[message]
|
|
65
|
+
: message;
|
|
66
|
+
},
|
|
67
|
+
hasFeature(plan, feature) {
|
|
68
|
+
return !!plan.features[feature.uuid];
|
|
69
|
+
},
|
|
70
|
+
syncHeights(which = "features") {
|
|
71
|
+
this.$nextTick(() => {
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
const slides = document.querySelectorAll(".swiper-slide");
|
|
74
|
+
if (which === "features") {
|
|
75
|
+
const featureType = `-${this.selectedInterval}`; // Append '-monthly' or '-annually' based on selectedInterval
|
|
76
|
+
const featureNames = document.querySelectorAll(
|
|
77
|
+
`.plan-feature-name${featureType}`
|
|
78
|
+
);
|
|
79
|
+
slides.forEach((slide) => {
|
|
80
|
+
const features = slide.querySelectorAll(
|
|
81
|
+
`.plan-feature${featureType}`
|
|
82
|
+
);
|
|
83
|
+
featureNames.forEach((featureName, index) => {
|
|
84
|
+
const feature = features[index];
|
|
85
|
+
if (featureName && feature) {
|
|
86
|
+
feature.style.height = `${featureName.offsetHeight}px`;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
} else if (which === "plans") {
|
|
91
|
+
|
|
92
|
+
const planType = `-${this.selectedInterval}`;
|
|
93
|
+
const planNames = document.querySelectorAll(
|
|
94
|
+
`.plan-header-wrapper${planType}`
|
|
95
|
+
);
|
|
96
|
+
const plansAvailableName = document.querySelector(
|
|
97
|
+
`.plans-available${planType}`
|
|
98
|
+
);
|
|
99
|
+
if (!plansAvailableName) return;
|
|
100
|
+
let planNameHeight = 0;
|
|
101
|
+
slides.forEach((slide, index) => {
|
|
102
|
+
const planName = planNames[index];
|
|
103
|
+
if (planName) {
|
|
104
|
+
planNameHeight = Math.max(
|
|
105
|
+
planName.offsetHeight,
|
|
106
|
+
planNameHeight
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// Set the minHeight for the plans available name
|
|
111
|
+
plansAvailableName.style.minHeight = `${planNameHeight}px`;
|
|
112
|
+
|
|
113
|
+
// Set the minHeight for all elements
|
|
114
|
+
planNames.forEach((el) => {
|
|
115
|
+
el.style.minHeight = `${planNameHeight}px`;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}, 2); // delay 0ms
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
syncAllHeights() {
|
|
122
|
+
this.syncHeights("features");
|
|
123
|
+
this.syncHeights("plans");
|
|
124
|
+
},
|
|
125
|
+
syncNavigationWidth() {
|
|
126
|
+
const swiperPlanNavigations = document.querySelectorAll(
|
|
127
|
+
".swiper-plan-navigation"
|
|
128
|
+
);
|
|
129
|
+
const pricingTable = document.querySelector(".pricing-table");
|
|
130
|
+
if (!pricingTable) return;
|
|
131
|
+
swiperPlanNavigations.forEach((swiperPlanNavigation) => {
|
|
132
|
+
swiperPlanNavigation.style.width = `${
|
|
133
|
+
pricingTable.offsetWidth + 130
|
|
134
|
+
}px`;
|
|
135
|
+
swiperPlanNavigation.style.left = `${pricingTable.offsetLeft - 65}px`;
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
syncScroll(source, targets) {
|
|
139
|
+
if (this.isSyncing) return;
|
|
140
|
+
this.isSyncing = true;
|
|
141
|
+
const scrollTop = source.scrollTop;
|
|
142
|
+
targets.forEach((target) => {
|
|
143
|
+
if (target !== source) {
|
|
144
|
+
target.scrollTop = scrollTop;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
this.isSyncing = false;
|
|
148
|
+
},
|
|
149
|
+
setupScrollListeners() {
|
|
150
|
+
const tableLeftElements = document.querySelectorAll("#table-left");
|
|
151
|
+
const plansTableElements = document.querySelectorAll("#plans-table");
|
|
152
|
+
const allElements = [...tableLeftElements, ...plansTableElements];
|
|
153
|
+
allElements.forEach((element) => {
|
|
154
|
+
element.addEventListener("scroll", () => {
|
|
155
|
+
this.syncScroll(element, allElements);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
updateRemainingPlans(swiper, type = 'monthly') {
|
|
161
|
+
if (!swiper) return;
|
|
162
|
+
|
|
163
|
+
const totalSlides = swiper.slides.length;
|
|
164
|
+
const currentIndex = swiper.activeIndex;
|
|
165
|
+
|
|
166
|
+
let visibleSlides = 1;
|
|
167
|
+
if (typeof swiper.params.slidesPerView === 'number') {
|
|
168
|
+
visibleSlides = swiper.params.slidesPerView;
|
|
169
|
+
} else if (swiper.params.breakpoints) {
|
|
170
|
+
const viewport = window.innerWidth;
|
|
171
|
+
const breakpoints = swiper.params.breakpoints;
|
|
172
|
+
const sorted = Object.keys(breakpoints)
|
|
173
|
+
.map(Number)
|
|
174
|
+
.sort((a, b) => a - b);
|
|
175
|
+
|
|
176
|
+
for (const bp of sorted) {
|
|
177
|
+
if (viewport >= bp && typeof breakpoints[bp].slidesPerView === 'number') {
|
|
178
|
+
visibleSlides = breakpoints[bp].slidesPerView;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const after = Math.max(totalSlides - (currentIndex + visibleSlides), 0);
|
|
184
|
+
const before = currentIndex;
|
|
185
|
+
|
|
186
|
+
if (type === 'annually') {
|
|
187
|
+
this.remainingPlansAnnually = { after, before };
|
|
188
|
+
} else {
|
|
189
|
+
this.remainingPlansMonthly = { after, before };
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
computed: {
|
|
195
|
+
monthlyPlans() {
|
|
196
|
+
return this.plans
|
|
197
|
+
.filter(plan => plan.interval === "EVERY_30_DAYS")
|
|
198
|
+
.map(plan => {
|
|
199
|
+
const planDetails = calculatePlanPriceWithDiscounts(plan, this.promotionalDiscount);
|
|
200
|
+
if (planDetails.has_discount && !this.anyMonthlyPlanHasDiscount) {
|
|
201
|
+
this.anyMonthlyPlanHasDiscount = true;
|
|
202
|
+
}
|
|
203
|
+
return planDetails;
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
annualPlans() {
|
|
207
|
+
return this.plans
|
|
208
|
+
.filter(plan => plan.interval === "ANNUAL")
|
|
209
|
+
.map(plan => {
|
|
210
|
+
const planDetails = calculatePlanPriceWithDiscounts(plan, this.promotionalDiscount);
|
|
211
|
+
if (planDetails.has_discount && !this.anyAnnuallyPlanHasDiscount) {
|
|
212
|
+
this.anyAnnuallyPlanHasDiscount = true;
|
|
213
|
+
}
|
|
214
|
+
return planDetails;
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
monthlyPlansFeatures() {
|
|
218
|
+
if (!this.features.length) return [];
|
|
219
|
+
|
|
220
|
+
// First, find which features are actually used in any monthly plan
|
|
221
|
+
const usedFeatures = new Set();
|
|
222
|
+
this.monthlyPlans.forEach(plan => {
|
|
223
|
+
Object.keys(plan.features).forEach(featureUuid => {
|
|
224
|
+
usedFeatures.add(featureUuid);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Filter features and group them
|
|
229
|
+
let groupedFeatures = {};
|
|
230
|
+
let hasGroupFields = false;
|
|
231
|
+
|
|
232
|
+
this.features
|
|
233
|
+
.filter(feature => usedFeatures.has(feature.uuid) && !feature.hidden_feature) // Filter out hidden features
|
|
234
|
+
.forEach((feature) => {
|
|
235
|
+
if (feature.group && feature.group_order) {
|
|
236
|
+
hasGroupFields = true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const group = hasGroupFields ? (feature.group || 'Default') : 'Default';
|
|
240
|
+
if (!groupedFeatures[group]) {
|
|
241
|
+
groupedFeatures[group] = {
|
|
242
|
+
name: group,
|
|
243
|
+
order: hasGroupFields ? (feature.group_order || 999) : 999,
|
|
244
|
+
features: []
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
groupedFeatures[group].features.push(feature);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const groups = Object.values(groupedFeatures).sort((a, b) => a.order - b.order);
|
|
251
|
+
if (groups.length === 1 || !hasGroupFields) {
|
|
252
|
+
groups.forEach(group => group.name = '');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return groups;
|
|
256
|
+
},
|
|
257
|
+
annualPlansFeatures() {
|
|
258
|
+
if (!this.features.length) return [];
|
|
259
|
+
|
|
260
|
+
// First, find which features are actually used in any annual plan
|
|
261
|
+
const usedFeatures = new Set();
|
|
262
|
+
this.annualPlans.forEach(plan => {
|
|
263
|
+
// Use feature.uuid instead of feature_id to match with plan.features
|
|
264
|
+
Object.keys(plan.features).forEach(featureUuid => {
|
|
265
|
+
usedFeatures.add(featureUuid);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Filter features and group them
|
|
270
|
+
let groupedFeatures = {};
|
|
271
|
+
let hasGroupFields = false;
|
|
272
|
+
|
|
273
|
+
this.features
|
|
274
|
+
.filter(feature => usedFeatures.has(feature.uuid) && !feature.hidden_feature) // Filter out hidden features
|
|
275
|
+
.forEach((feature) => {
|
|
276
|
+
if (feature.group && feature.group_order) {
|
|
277
|
+
hasGroupFields = true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const group = hasGroupFields ? (feature.group || 'Default') : 'Default';
|
|
281
|
+
if (!groupedFeatures[group]) {
|
|
282
|
+
groupedFeatures[group] = {
|
|
283
|
+
name: group,
|
|
284
|
+
order: hasGroupFields ? (feature.group_order || 999) : 999,
|
|
285
|
+
features: []
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
groupedFeatures[group].features.push(feature);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const groups = Object.values(groupedFeatures).sort((a, b) => a.order - b.order);
|
|
292
|
+
if (groups.length === 1 || !hasGroupFields) {
|
|
293
|
+
groups.forEach(group => group.name = '');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return groups;
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
watch: {
|
|
301
|
+
selectedInterval() {
|
|
302
|
+
let monthlyPlanTable = document.querySelector(".monthly-table");
|
|
303
|
+
let annuallyPlanTable = document.querySelector(".annually-table");
|
|
304
|
+
let monthlyPlanTableNavigation =
|
|
305
|
+
document.querySelector(".nav-monthly-table");
|
|
306
|
+
let annuallyPlanTableNavigation = document.querySelector(
|
|
307
|
+
".nav-annually-table"
|
|
308
|
+
);
|
|
309
|
+
if (this.selectedInterval === "monthly") {
|
|
310
|
+
monthlyPlanTable.style.visibility = "visible";
|
|
311
|
+
monthlyPlanTable.style.height = "auto";
|
|
312
|
+
monthlyPlanTable.style.padding = "16px";
|
|
313
|
+
monthlyPlanTable.style.padding = "16px";
|
|
314
|
+
annuallyPlanTable.style.visibility = "hidden";
|
|
315
|
+
annuallyPlanTable.style.height = "0px";
|
|
316
|
+
annuallyPlanTable.style.padding = "0px";
|
|
317
|
+
monthlyPlanTableNavigation.style.display = "flex";
|
|
318
|
+
annuallyPlanTableNavigation.style.display = "none";
|
|
319
|
+
this.interval = "EVERY_30_DAYS";
|
|
320
|
+
this.syncAllHeights();
|
|
321
|
+
} else if (this.selectedInterval === "annually") {
|
|
322
|
+
monthlyPlanTable.style.visibility = "hidden";
|
|
323
|
+
monthlyPlanTable.style.height = "0px";
|
|
324
|
+
monthlyPlanTable.style.padding = "0px";
|
|
325
|
+
monthlyPlanTable.style.padding = "0px";
|
|
326
|
+
annuallyPlanTable.style.visibility = "visible";
|
|
327
|
+
annuallyPlanTable.style.height = "auto";
|
|
328
|
+
annuallyPlanTable.style.padding = "16px";
|
|
329
|
+
monthlyPlanTableNavigation.style.display = "none";
|
|
330
|
+
annuallyPlanTableNavigation.style.display = "flex";
|
|
331
|
+
this.interval = "ANNUAL";
|
|
332
|
+
this.syncAllHeights();
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
mounted() {
|
|
338
|
+
new Swiper(this.$refs.swiperMonthlyTable, {
|
|
339
|
+
modules: [Navigation, Pagination],
|
|
340
|
+
loop: false,
|
|
341
|
+
slidesPerView: 2,
|
|
342
|
+
speed: 500,
|
|
343
|
+
navigation: {
|
|
344
|
+
nextEl: ".swiper-plan-monthly-next",
|
|
345
|
+
prevEl: ".swiper-plan-monthly-prev",
|
|
346
|
+
},
|
|
347
|
+
breakpoints: {
|
|
348
|
+
0: {
|
|
349
|
+
slidesPerView: 1,
|
|
350
|
+
},
|
|
351
|
+
768: {
|
|
352
|
+
slidesPerView: 2,
|
|
353
|
+
},
|
|
354
|
+
1024: {
|
|
355
|
+
slidesPerView: Math.min(this.monthlyPlans.length, 3),
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
on: {
|
|
359
|
+
slideChange: (swiper) => {
|
|
360
|
+
this.syncAllHeights()
|
|
361
|
+
this.updateRemainingPlans(swiper)
|
|
362
|
+
}, // Run syncHeights on each slide change
|
|
363
|
+
afterInit: (swiper) => {
|
|
364
|
+
this.syncAllHeights()
|
|
365
|
+
this.updateRemainingPlans(swiper)
|
|
366
|
+
}, // Sync heights after initial Swiper setup
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
new Swiper(this.$refs.swiperAnnuallyTable, {
|
|
370
|
+
modules: [Navigation, Pagination],
|
|
371
|
+
loop: false,
|
|
372
|
+
slidesPerView: 2,
|
|
373
|
+
speed: 500,
|
|
374
|
+
navigation: {
|
|
375
|
+
nextEl: ".swiper-plan-annually-next",
|
|
376
|
+
prevEl: ".swiper-plan-annually-prev",
|
|
377
|
+
},
|
|
378
|
+
breakpoints: {
|
|
379
|
+
0: {
|
|
380
|
+
slidesPerView: 1,
|
|
381
|
+
},
|
|
382
|
+
768: {
|
|
383
|
+
slidesPerView: 2,
|
|
384
|
+
},
|
|
385
|
+
1024: {
|
|
386
|
+
slidesPerView: Math.min(this.annualPlans.length, 3),
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
on: {
|
|
390
|
+
slideChange: (swiper) => {
|
|
391
|
+
this.syncAllHeights()
|
|
392
|
+
this.updateRemainingPlans(swiper, 'annually')
|
|
393
|
+
}, // Run syncHeights on each slide change
|
|
394
|
+
afterInit: (swiper) => {
|
|
395
|
+
this.syncAllHeights()
|
|
396
|
+
this.updateRemainingPlans(swiper, 'annually')
|
|
397
|
+
}, // Sync heights after initial Swiper setup
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
this.syncAllHeights(); // Run syncHeights once after mount
|
|
402
|
+
this.syncNavigationWidth(); // Sync navigation width after mount
|
|
403
|
+
this.setupScrollListeners();
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
</script>
|
|
407
|
+
|
|
408
|
+
<template>
|
|
409
|
+
<div class="container">
|
|
410
|
+
<div class="swiper-plan-navigation nav-monthly-table">
|
|
411
|
+
<button class="swiper-plan-monthly-prev">
|
|
412
|
+
<span class="plans-remaining" v-if="this.remainingPlansMonthly.before > 0">+{{ this.remainingPlansMonthly.before }} Plans</span>
|
|
413
|
+
<img src="../../assets/NavigationLeft.svg" alt="Nav Left" />
|
|
414
|
+
</button>
|
|
415
|
+
<button class="swiper-plan-monthly-next">
|
|
416
|
+
<span class="plans-remaining" v-if="this.remainingPlansMonthly.after > 0">+{{ this.remainingPlansMonthly.after }} Plans</span>
|
|
417
|
+
<img src="../../assets/NavigationRight.svg" alt="Nav Right" />
|
|
418
|
+
</button>
|
|
419
|
+
</div>
|
|
420
|
+
<div class="swiper-plan-navigation nav-annually-table">
|
|
421
|
+
<button class="swiper-plan-annually-prev">
|
|
422
|
+
<span class="plans-remaining" v-if="this.remainingPlansAnnually.before > 0">+{{ this.remainingPlansAnnually.before }} Plans</span>
|
|
423
|
+
<img src="../../assets/NavigationLeft.svg" alt="Nav Left" />
|
|
424
|
+
</button>
|
|
425
|
+
<button class="swiper-plan-annually-next">
|
|
426
|
+
<span class="plans-remaining" v-if="this.remainingPlansAnnually.after > 0">+{{ this.remainingPlansAnnually.after }} Plans</span>
|
|
427
|
+
<img src="../../assets/NavigationRight.svg" alt="Nav Right" />
|
|
428
|
+
</button>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<div class="pricing-table monthly-table">
|
|
432
|
+
<div class="pricing-table-inner__left" id="table-left">
|
|
433
|
+
<div class="table-header plans-available plans-available-monthly">
|
|
434
|
+
<h3>
|
|
435
|
+
{{ monthlyPlans.length }} {{ translateMe("Plans available") }}
|
|
436
|
+
</h3>
|
|
437
|
+
</div>
|
|
438
|
+
<template v-for="group in monthlyPlansFeatures">
|
|
439
|
+
<div v-if="group.name" class="feature-group-header plan-feature-name plan-feature-name-monthly">
|
|
440
|
+
{{ group.name }}
|
|
441
|
+
</div>
|
|
442
|
+
<div
|
|
443
|
+
class="plan-feature-name plan-feature-name-monthly"
|
|
444
|
+
v-for="feature in group.features"
|
|
445
|
+
:key="feature.uuid"
|
|
446
|
+
>
|
|
447
|
+
{{ feature.name }}
|
|
448
|
+
</div>
|
|
449
|
+
</template>
|
|
450
|
+
</div>
|
|
451
|
+
<div class="swiper plans monthly" ref="swiperMonthlyTable" id="plans-table">
|
|
452
|
+
<div class="swiper-wrapper">
|
|
453
|
+
<div
|
|
454
|
+
v-for="(plan, index) in monthlyPlans"
|
|
455
|
+
:key="plan.id"
|
|
456
|
+
class="swiper-slide"
|
|
457
|
+
:class="{ 'last-slide': index === monthlyPlans.length - 1 }"
|
|
458
|
+
>
|
|
459
|
+
<div class="plan-header-wrapper plan-header-wrapper-monthly">
|
|
460
|
+
<div
|
|
461
|
+
:class="[
|
|
462
|
+
'price-wrapper',
|
|
463
|
+
anyMonthlyPlanHasDiscount ? 'has-discount' : ''
|
|
464
|
+
]"
|
|
465
|
+
>
|
|
466
|
+
<template v-if="plan.strike_price">
|
|
467
|
+
<h5>
|
|
468
|
+
<span class="strike-price">${{ plan.strike_price }}</span>
|
|
469
|
+
<span class="plan-interval" v-if="plan.strike_price !== 0">{{ translateMe("/mo") }}</span>
|
|
470
|
+
</h5>
|
|
471
|
+
</template>
|
|
472
|
+
<div class="main-price">
|
|
473
|
+
<h4 class="plan-name">{{ plan.name }}</h4>
|
|
474
|
+
<h4 v-if="plan.name !== 'free' && plan.name !== 'FREE'">
|
|
475
|
+
<span class="plan-price">${{ plan.price }}</span>
|
|
476
|
+
<span class="plan-interval">{{ translateMe("/mo") }}</span>
|
|
477
|
+
</h4>
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
<VariantButton
|
|
481
|
+
:variant="'secondary'"
|
|
482
|
+
:disabled="currentPlan && currentPlan.id === plan.id"
|
|
483
|
+
:loading="loadingPlanId === plan.id"
|
|
484
|
+
@click="handlePlanClick(plan)"
|
|
485
|
+
class="button"
|
|
486
|
+
>{{
|
|
487
|
+
currentPlan && currentPlan.id === plan.id
|
|
488
|
+
? translateMe("Selected Plan")
|
|
489
|
+
: (
|
|
490
|
+
!currentPlan
|
|
491
|
+
? translateMe("Choose Plan")
|
|
492
|
+
: (
|
|
493
|
+
plan.price > currentPlan.price
|
|
494
|
+
? translateMe("Upgrade")
|
|
495
|
+
: translateMe("Switch to this plan")
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
}}</VariantButton>
|
|
499
|
+
</div>
|
|
500
|
+
<template v-for="group in monthlyPlansFeatures">
|
|
501
|
+
<div v-if="group.name" class="feature-group-header plan-feature plan-feature-monthly">
|
|
502
|
+
{{ group.name }}
|
|
503
|
+
</div>
|
|
504
|
+
<div
|
|
505
|
+
class="plan-feature plan-feature-monthly"
|
|
506
|
+
v-for="feature in group.features"
|
|
507
|
+
:key="feature.uuid"
|
|
508
|
+
>
|
|
509
|
+
<div v-if="hasFeature(plan, feature)">
|
|
510
|
+
<div v-if="feature.value_type === 'boolean'">
|
|
511
|
+
<img
|
|
512
|
+
src="../../assets/CheckTrue.svg"
|
|
513
|
+
alt="Feature is included"
|
|
514
|
+
class="plan-table-checkmark"
|
|
515
|
+
v-if="hasFeature(plan, feature)"
|
|
516
|
+
/>
|
|
517
|
+
</div>
|
|
518
|
+
<div v-else>
|
|
519
|
+
<span>{{ translateMe(formatFeature(plan.features[feature.uuid])) }}</span>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
<div v-else>
|
|
523
|
+
<img
|
|
524
|
+
src="../../assets/CheckFalse.svg"
|
|
525
|
+
alt="Feature is not included"
|
|
526
|
+
class="plan-table-checkmark"
|
|
527
|
+
/>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
</template>
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
<div class="pricing-table annually-table">
|
|
536
|
+
<div class="pricing-table-inner__left" id="table-left">
|
|
537
|
+
<div class="table-header plans-available plans-available-annually">
|
|
538
|
+
<h3>{{ annualPlans.length }} {{ translateMe("Plans available") }}</h3>
|
|
539
|
+
</div>
|
|
540
|
+
<template v-for="group in annualPlansFeatures">
|
|
541
|
+
<div v-if="group.name" class="feature-group-header plan-feature-name plan-feature-name-annually">
|
|
542
|
+
{{ group.name }}
|
|
543
|
+
</div>
|
|
544
|
+
<div
|
|
545
|
+
class="plan-feature-name plan-feature-name-annually"
|
|
546
|
+
v-for="feature in group.features"
|
|
547
|
+
:key="feature.uuid"
|
|
548
|
+
>
|
|
549
|
+
{{ feature.name }}
|
|
550
|
+
</div>
|
|
551
|
+
</template>
|
|
552
|
+
</div>
|
|
553
|
+
<div class="swiper plans annually" ref="swiperAnnuallyTable" id="plans-table">
|
|
554
|
+
<div class="swiper-wrapper">
|
|
555
|
+
<div
|
|
556
|
+
v-for="(plan, index) in annualPlans"
|
|
557
|
+
:key="plan.id"
|
|
558
|
+
class="swiper-slide"
|
|
559
|
+
:class="{ 'last-slide': index === annualPlans.length - 1 }"
|
|
560
|
+
>
|
|
561
|
+
<div class="plan-header-wrapper plan-header-wrapper-annually">
|
|
562
|
+
<div
|
|
563
|
+
:class="[
|
|
564
|
+
'price-wrapper',
|
|
565
|
+
anyAnnuallyPlanHasDiscount ? 'has-discount' : ''
|
|
566
|
+
]"
|
|
567
|
+
>
|
|
568
|
+
<template v-if="plan.strike_price">
|
|
569
|
+
<h5>
|
|
570
|
+
<span class="strike-price">${{ plan.strike_price }}</span>
|
|
571
|
+
<span class="plan-interval" v-if="plan.strike_price !== 0">{{ translateMe("/yr") }}</span>
|
|
572
|
+
</h5>
|
|
573
|
+
</template>
|
|
574
|
+
<div class="main-price">
|
|
575
|
+
<h4 class="plan-name">{{ plan.name }}</h4>
|
|
576
|
+
<h4 v-if="plan.name !== 'free' && plan.name !== 'FREE'">
|
|
577
|
+
<span class="plan-price">${{ plan.price }}</span>
|
|
578
|
+
<span class="plan-interval">{{ translateMe("/yr") }}</span>
|
|
579
|
+
</h4>
|
|
580
|
+
</div>
|
|
581
|
+
</div>
|
|
582
|
+
<VariantButton
|
|
583
|
+
:variant="'secondary'"
|
|
584
|
+
:disabled="currentPlan && currentPlan.id === plan.id"
|
|
585
|
+
:loading="loadingPlanId === plan.id"
|
|
586
|
+
@click="handlePlanClick(plan)"
|
|
587
|
+
class="button"
|
|
588
|
+
>{{
|
|
589
|
+
currentPlan && currentPlan.id === plan.id
|
|
590
|
+
? translateMe("Selected Plan")
|
|
591
|
+
: (
|
|
592
|
+
!currentPlan
|
|
593
|
+
? translateMe("Choose Plan")
|
|
594
|
+
: (
|
|
595
|
+
plan.price > currentPlan.price
|
|
596
|
+
? translateMe("Upgrade")
|
|
597
|
+
: translateMe("Switch to this plan")
|
|
598
|
+
)
|
|
599
|
+
)
|
|
600
|
+
}}</VariantButton>
|
|
601
|
+
</div>
|
|
602
|
+
<template v-for="group in annualPlansFeatures">
|
|
603
|
+
<div v-if="group.name" class="feature-group-header plan-feature plan-feature-annually">
|
|
604
|
+
{{ group.name }}
|
|
605
|
+
</div>
|
|
606
|
+
<div
|
|
607
|
+
class="plan-feature plan-feature-annually"
|
|
608
|
+
v-for="feature in group.features"
|
|
609
|
+
:key="feature.uuid"
|
|
610
|
+
>
|
|
611
|
+
<div v-if="hasFeature(plan, feature)">
|
|
612
|
+
<div v-if="feature.value_type === 'boolean'">
|
|
613
|
+
<img
|
|
614
|
+
src="../../assets/CheckTrue.svg"
|
|
615
|
+
alt="Feature is included"
|
|
616
|
+
class="plan-table-checkmark"
|
|
617
|
+
v-if="hasFeature(plan, feature)"
|
|
618
|
+
/>
|
|
619
|
+
</div>
|
|
620
|
+
<div v-else>
|
|
621
|
+
<span>{{ translateMe(formatFeature(plan.features[feature.uuid])) }}</span>
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
<div v-else>
|
|
625
|
+
<img
|
|
626
|
+
src="../../assets/CheckFalse.svg"
|
|
627
|
+
alt="Feature is not included"
|
|
628
|
+
class="plan-table-checkmark"
|
|
629
|
+
/>
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
</template>
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
</div>
|
|
638
|
+
</template>
|
|
639
|
+
|
|
640
|
+
<style scoped>
|
|
641
|
+
.container {
|
|
642
|
+
width: 100%;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.pricing-table {
|
|
646
|
+
display: grid;
|
|
647
|
+
width: calc(100% + 2px);
|
|
648
|
+
grid-template-columns: repeat(3, 1fr);
|
|
649
|
+
background-color: white;
|
|
650
|
+
border-radius: 16px;
|
|
651
|
+
box-shadow: 0 4px 6px -1px #0000001a;
|
|
652
|
+
border: 1px solid #e5e5e5;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.pricing-table.monthly-table {
|
|
656
|
+
padding: 16px;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.plans {
|
|
660
|
+
grid-column: span 2;
|
|
661
|
+
max-height: 500px;
|
|
662
|
+
overflow-y: auto;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.annually-table {
|
|
666
|
+
visibility: hidden;
|
|
667
|
+
height: 0;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.pricing-table-inner__left {
|
|
671
|
+
display: flex;
|
|
672
|
+
flex-direction: column;
|
|
673
|
+
max-height: 500px;
|
|
674
|
+
overflow-y: auto;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.swiper {
|
|
678
|
+
width: 100%;
|
|
679
|
+
height: 100%;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.table-header {
|
|
683
|
+
display: flex;
|
|
684
|
+
align-items: center;
|
|
685
|
+
background-color: #f1f1f1;
|
|
686
|
+
padding: 16px;
|
|
687
|
+
border-bottom: 1px solid #e3e3e3;
|
|
688
|
+
position: sticky;
|
|
689
|
+
top: 0;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.table-header h3 {
|
|
693
|
+
font-size: 13px;
|
|
694
|
+
font-weight: 700;
|
|
695
|
+
line-height: 20px;
|
|
696
|
+
color: #303030;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.plan-header-wrapper {
|
|
700
|
+
padding: 16px;
|
|
701
|
+
border-bottom: 1px solid #e3e3e3;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.plan-feature {
|
|
705
|
+
display: flex;
|
|
706
|
+
align-items: center;
|
|
707
|
+
justify-content: center;
|
|
708
|
+
border-bottom: 1px solid #e3e3e3;
|
|
709
|
+
border-left: 1px solid #e3e3e3;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.last-slide .plan-feature {
|
|
713
|
+
border-right: 1px solid #e3e3e3;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.plan-feature-name {
|
|
717
|
+
font-size: 13px;
|
|
718
|
+
font-weight: 450;
|
|
719
|
+
padding: 12px;
|
|
720
|
+
border-bottom: 1px solid #e3e3e3;
|
|
721
|
+
border-left: 1px solid #e3e3e3;
|
|
722
|
+
}
|
|
723
|
+
.plan-table-checkmark {
|
|
724
|
+
width: 20px;
|
|
725
|
+
height: 20px;
|
|
726
|
+
}
|
|
727
|
+
.plan-header-wrapper {
|
|
728
|
+
display: flex;
|
|
729
|
+
flex-direction: column;
|
|
730
|
+
justify-content: center;
|
|
731
|
+
align-items: center;
|
|
732
|
+
gap: 12px;
|
|
733
|
+
background-color: #f1f1f1;
|
|
734
|
+
border-left: 1px solid #f1f1f1;
|
|
735
|
+
border-right: 1px solid #f1f1f1;
|
|
736
|
+
position: sticky;
|
|
737
|
+
top: 0;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.plan-header-wrapper .price-wrapper {
|
|
741
|
+
display: flex;
|
|
742
|
+
flex-direction: column;
|
|
743
|
+
justify-content: center;
|
|
744
|
+
align-items: center;
|
|
745
|
+
gap: 4px;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.plan-header-wrapper .price-wrapper.has-discount {
|
|
749
|
+
min-height: 44px;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.plan-header-wrapper .price-wrapper .main-price {
|
|
753
|
+
display: flex;
|
|
754
|
+
flex-direction: row;
|
|
755
|
+
gap: 8px;
|
|
756
|
+
justify-content: center;
|
|
757
|
+
align-items: center;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.plan-header-wrapper .price-wrapper .strike-price {
|
|
761
|
+
text-decoration: line-through;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.plan-header-wrapper .price-wrapper .plan-interval {
|
|
765
|
+
color: #999999;
|
|
766
|
+
font-weight: normal;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.plan-header-wrapper .price-wrapper h4 {
|
|
770
|
+
display: inline;
|
|
771
|
+
font-size: 16px;
|
|
772
|
+
font-weight: 700;
|
|
773
|
+
color: #303030;
|
|
774
|
+
width: max-content;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.plan-header-wrapper .price-wrapper h4 h6 {
|
|
778
|
+
display: inline;
|
|
779
|
+
font-size: 13px;
|
|
780
|
+
font-weight: 400;
|
|
781
|
+
color: #00000080;
|
|
782
|
+
margin-left: -4px;
|
|
783
|
+
line-height: 0;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.swiper-plan-navigation {
|
|
787
|
+
position: absolute;
|
|
788
|
+
margin-top: 32px;
|
|
789
|
+
display: flex;
|
|
790
|
+
justify-content: space-between;
|
|
791
|
+
padding: 16px 0px;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
.nav-annually-table {
|
|
795
|
+
display: none;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.swiper-plan-monthly-prev,
|
|
799
|
+
.swiper-plan-monthly-next,
|
|
800
|
+
.swiper-plan-annually-next,
|
|
801
|
+
.swiper-plan-annually-prev {
|
|
802
|
+
display: flex;
|
|
803
|
+
align-items: center;
|
|
804
|
+
justify-content: center;
|
|
805
|
+
width: 36px;
|
|
806
|
+
height: 36px;
|
|
807
|
+
background-color: #1a1a1a;
|
|
808
|
+
border-radius: 8px;
|
|
809
|
+
cursor: pointer;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.swiper-plan-monthly-prev:disabled,
|
|
813
|
+
.swiper-plan-monthly-next:disabled,
|
|
814
|
+
.swiper-plan-annually-next:disabled,
|
|
815
|
+
.swiper-plan-annually-prev:disabled {
|
|
816
|
+
visibility: hidden;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.choose-button {
|
|
820
|
+
background-color: white !important;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.choose-button:hover {
|
|
824
|
+
background-color: #f9f9f9 !important;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.choose-button.disabled {
|
|
828
|
+
background-color: rgba(0, 0, 0, 0.15) !important;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
#table-left {
|
|
832
|
+
scrollbar-width: none;
|
|
833
|
+
-ms-overflow-style: none;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
#table-left::-webkit-scrollbar {
|
|
837
|
+
display: none;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.plans-remaining {
|
|
841
|
+
position: absolute;
|
|
842
|
+
top: -30px;
|
|
843
|
+
background-color: white;
|
|
844
|
+
color: #333;
|
|
845
|
+
font-weight: 500;
|
|
846
|
+
padding: 6px 12px;
|
|
847
|
+
border-radius: 8px;
|
|
848
|
+
font-size: 14px;
|
|
849
|
+
white-space: nowrap;
|
|
850
|
+
box-shadow: 0 4px 8px #00000026;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.plans-remaining::after {
|
|
854
|
+
content: '';
|
|
855
|
+
position: absolute;
|
|
856
|
+
top: 100%;
|
|
857
|
+
left: 50%;
|
|
858
|
+
transform: translateX(-50%);
|
|
859
|
+
width: 0;
|
|
860
|
+
height: 0;
|
|
861
|
+
border-left: 8px solid transparent;
|
|
862
|
+
border-right: 8px solid transparent;
|
|
863
|
+
border-top: 8px solid white;
|
|
864
|
+
filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.1));
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
.feature-group-header.plan-feature-name {
|
|
868
|
+
font-size: 0 !important;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.feature-group-header {
|
|
872
|
+
font-size: 14px;
|
|
873
|
+
font-weight: 600;
|
|
874
|
+
background-color: #f5f5f5;
|
|
875
|
+
color: #303030;
|
|
876
|
+
max-height: 30px;
|
|
877
|
+
padding: 5px !important;
|
|
878
|
+
border-bottom: 1px solid #e3e3e3;
|
|
879
|
+
border-left: 1px solid #e3e3e3;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
.feature-group-header.plan-feature {
|
|
883
|
+
text-align: center;
|
|
884
|
+
border-left: 1px solid #e3e3e3;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.last-slide .feature-group-header.plan-feature {
|
|
888
|
+
border-right: 1px solid #e3e3e3;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
@media (max-width: 640px) {
|
|
892
|
+
.swiper-plan-navigation {
|
|
893
|
+
display: none !important;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.pricing-table {
|
|
897
|
+
width: calc(100% + -2px);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
</style>
|