@kompasid/lit-web-components 0.9.28 → 0.9.29
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/demo/index.html +24 -1
- package/dist/src/components/kompasid-metered-paywall-personalize/KompasMeteredPaywallPersonalize.d.ts +67 -0
- package/dist/src/components/kompasid-metered-paywall-personalize/KompasMeteredPaywallPersonalize.js +507 -0
- package/dist/src/components/kompasid-metered-paywall-personalize/KompasMeteredPaywallPersonalize.js.map +1 -0
- package/dist/src/components/kompasid-metered-paywall-personalize/types.d.ts +23 -0
- package/dist/src/components/kompasid-metered-paywall-personalize/types.js +2 -0
- package/dist/src/components/kompasid-metered-paywall-personalize/types.js.map +1 -0
- package/dist/src/components/kompasid-paywall-body/KompasPaywallBody.js +2 -2
- package/dist/src/components/kompasid-paywall-body/KompasPaywallBody.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/tailwind/tailwind.js +142 -16
- package/dist/tailwind/tailwind.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/components/kompasid-metered-paywall-personalize/KompasMeteredPaywallPersonalize.ts +500 -0
- package/src/components/kompasid-metered-paywall-personalize/readme.md +155 -0
- package/src/components/kompasid-metered-paywall-personalize/types.ts +26 -0
- package/src/components/kompasid-paywall-body/KompasPaywallBody.ts +2 -2
- package/src/index.ts +1 -0
- package/tailwind/tailwind.css +151 -6
- package/tailwind/tailwind.ts +142 -16
package/src/components/kompasid-metered-paywall-personalize/KompasMeteredPaywallPersonalize.ts
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { html, css, LitElement, nothing } from 'lit'
|
|
2
|
+
import { customElement, state, property } from 'lit/decorators.js'
|
|
3
|
+
import { unsafeSVG } from 'lit/directives/unsafe-svg.js'
|
|
4
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js'
|
|
5
|
+
import { TWStyles } from '../../../tailwind/tailwind.js'
|
|
6
|
+
import { PackageData, OfferingItem } from './types.js'
|
|
7
|
+
import { getFontAwesomeIcon } from '../../utils/fontawesome-setup.js'
|
|
8
|
+
import { formatRupiah } from '../../utils/formatRupiah.js'
|
|
9
|
+
|
|
10
|
+
@customElement('kompasid-metered-paywall-personalize')
|
|
11
|
+
export class KompasMeteredPaywallPersonalize extends LitElement {
|
|
12
|
+
static styles = [
|
|
13
|
+
css`
|
|
14
|
+
.text-transition {
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 5rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.body {
|
|
20
|
+
position: sticky;
|
|
21
|
+
top: 0;
|
|
22
|
+
height: 100%;
|
|
23
|
+
width: 100%;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.message-collapse-engage-returners span {
|
|
27
|
+
color: #ff7a00;
|
|
28
|
+
font-weight: 700;
|
|
29
|
+
text-wrap: nowrap;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.message-collapse-engage-returners s {
|
|
33
|
+
color: #999999;
|
|
34
|
+
text-wrap: nowrap;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.font-lora {
|
|
38
|
+
font-family: 'Lora', 'Georgia', 'serif';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.icon {
|
|
42
|
+
height: 1rem;
|
|
43
|
+
color: #48bb78;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.icon.lg {
|
|
47
|
+
height: 1.5rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.icon.lg svg {
|
|
51
|
+
height: 1.5rem;
|
|
52
|
+
}
|
|
53
|
+
`,
|
|
54
|
+
TWStyles,
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
@state() private isLoading: Boolean = true
|
|
58
|
+
@state() private maxQuota: number = 3
|
|
59
|
+
@state() private packageData: PackageData | undefined
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Props
|
|
63
|
+
*/
|
|
64
|
+
/**
|
|
65
|
+
* prop countdownArticle untuk menghandle sudah berapa artikel gratis yang user baca.
|
|
66
|
+
* prop segment untuk menentukan paywall template dari segmen apa yang di pakai, bila tidak ada yang cocok jangan render paywall
|
|
67
|
+
* prop offering untuk handle offering yang akan di berikan, bila tidak di isi maka akan default menjadi Q1
|
|
68
|
+
* prop user_name untuk menerima nama user yang akan di tampilkan paywall specific
|
|
69
|
+
* prop paywall_location = The location where user encounter the paywall
|
|
70
|
+
* prop paywall_subscription_package = The name of the subscription package viewed by user
|
|
71
|
+
* prop paywall_subscription_id = The ID of the subscription package viewed by user
|
|
72
|
+
* prop paywall_subscription_price = The price of the subscriprtion package viewed by user
|
|
73
|
+
* prop paywall_position = The position of ther subscription package viewed by user
|
|
74
|
+
* prop tracker_page_type = Type of the page
|
|
75
|
+
* prop tracker_content_id = ID of article (slug)
|
|
76
|
+
* prop tracker_content_type = Whether it's free article or paid article
|
|
77
|
+
* prop tracker_content_title = The title of article
|
|
78
|
+
* prop tracker_content_categories = The category of the content
|
|
79
|
+
* prop tracker_user_type = Type of user based on their subscription
|
|
80
|
+
* prop tracker_subscription_status = Status of their subscription
|
|
81
|
+
* prop tracker_page_domain = Page Domain
|
|
82
|
+
* prop tracker_metered_wall_type = The type of Metered Wall
|
|
83
|
+
* prop tracker_epaper_edition = The edition of epaper viewed by user
|
|
84
|
+
* prop tracker_metered_wall_balance = The balance of their metered wall
|
|
85
|
+
*/
|
|
86
|
+
@property({ type: Number }) countdownArticle = 0
|
|
87
|
+
@property({ type: String }) segment = ''
|
|
88
|
+
@property({ type: String }) offering = ''
|
|
89
|
+
@property({ type: String }) user_name = ''
|
|
90
|
+
@property({ type: String }) paywall_location = ''
|
|
91
|
+
@property({ type: String }) paywall_subscription_package = ''
|
|
92
|
+
@property({ type: String }) paywall_subscription_id = ''
|
|
93
|
+
@property({ type: Number }) paywall_subscription_price = 0
|
|
94
|
+
@property({ type: Number }) paywall_position = 0
|
|
95
|
+
@property({ type: String }) tracker_page_type = ''
|
|
96
|
+
@property({ type: String }) tracker_content_id = ''
|
|
97
|
+
@property({ type: String }) tracker_content_title = ''
|
|
98
|
+
@property({ type: String }) tracker_content_categories = ''
|
|
99
|
+
@property({ type: String }) tracker_content_type = ''
|
|
100
|
+
@property({ type: String }) tracker_user_type = ''
|
|
101
|
+
@property({ type: String }) tracker_subscription_status = ''
|
|
102
|
+
@property({ type: String }) tracker_page_domain = ''
|
|
103
|
+
@property({ type: String }) tracker_metered_wall_type = ''
|
|
104
|
+
@property({ type: Number }) tracker_metered_wall_balance = 0
|
|
105
|
+
|
|
106
|
+
override async connectedCallback() {
|
|
107
|
+
super.connectedCallback()
|
|
108
|
+
await this.getMeteredPaywallData()
|
|
109
|
+
this.dataLayeronMeteredPaywall()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getMeteredPaywallData() {
|
|
113
|
+
try {
|
|
114
|
+
const getSegment = this.segment.toLocaleLowerCase().replace(' ', '_')
|
|
115
|
+
const response = await fetch(
|
|
116
|
+
`https://cdn-www.kompas.id/web-component/metered-paywall-personalize/${getSegment}.json`
|
|
117
|
+
)
|
|
118
|
+
const json = await response.json()
|
|
119
|
+
|
|
120
|
+
this.packageData = json
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw Error('Failed to get metered paywall data')
|
|
123
|
+
} finally {
|
|
124
|
+
this.isLoading = false
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private redirectToBerlangganan() {
|
|
129
|
+
this.dataLayeronLanggananButton()
|
|
130
|
+
|
|
131
|
+
// Pastikan data tersedia
|
|
132
|
+
const offeringKey = this.offering || 'Q1'
|
|
133
|
+
const offeringData = this.packageData?.offering?.[offeringKey]
|
|
134
|
+
|
|
135
|
+
const checkoutUrl = offeringData?.checkout_url || ''
|
|
136
|
+
|
|
137
|
+
// Encode referrer dengan aman
|
|
138
|
+
const { origin, pathname, search } = window.location
|
|
139
|
+
const referrer = encodeURIComponent(`${origin}${pathname}${search}`)
|
|
140
|
+
|
|
141
|
+
// Bangun URL dengan parameter tambahan aman
|
|
142
|
+
const url = new URL(checkoutUrl)
|
|
143
|
+
url.searchParams.set('referrer', referrer)
|
|
144
|
+
|
|
145
|
+
// Redirect ke URL akhir
|
|
146
|
+
window.location.href = url.toString()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private dataLayeronLanggananButton() {
|
|
150
|
+
window.dataLayer.push({
|
|
151
|
+
event: 'subscribe_button_clicked',
|
|
152
|
+
paywall_location: this.paywall_location || '',
|
|
153
|
+
paywall_subscription_package: this.paywall_subscription_package || '',
|
|
154
|
+
paywall_subscription_id: this.paywall_subscription_id || '',
|
|
155
|
+
paywall_subscription_price: this.paywall_subscription_price || 0,
|
|
156
|
+
paywall_position: this.paywall_position || 0,
|
|
157
|
+
page_type: this.tracker_page_type,
|
|
158
|
+
content_id: this.tracker_content_id,
|
|
159
|
+
content_title: this.tracker_content_title,
|
|
160
|
+
content_categories: this.tracker_content_categories,
|
|
161
|
+
content_type: this.tracker_content_type,
|
|
162
|
+
user_type: this.tracker_user_type || 'R',
|
|
163
|
+
subscription_status: this.tracker_subscription_status,
|
|
164
|
+
page_domain: this.tracker_page_domain || 'Kompas.id',
|
|
165
|
+
metered_wall_type: this.tracker_metered_wall_type || 'MP',
|
|
166
|
+
metered_wall_balance: this.maxQuota - this.countdownArticle + 1,
|
|
167
|
+
paywall_segment: this.segment || '',
|
|
168
|
+
checkout_url:
|
|
169
|
+
this.packageData?.offering[this.offering ? this.offering : 'Q1']
|
|
170
|
+
.checkout_url,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private dataLayeronMeteredPaywall() {
|
|
175
|
+
window.dataLayer.push({
|
|
176
|
+
event: 'paywall_viewed',
|
|
177
|
+
paywall_location: this.paywall_location || '',
|
|
178
|
+
paywall_subscription_package: this.paywall_subscription_package || '',
|
|
179
|
+
paywall_subscription_id: this.paywall_subscription_id || '',
|
|
180
|
+
paywall_subscription_price: this.paywall_subscription_price || 0,
|
|
181
|
+
paywall_position: this.paywall_position || 0,
|
|
182
|
+
page_type: this.tracker_page_type,
|
|
183
|
+
content_id: this.tracker_content_id,
|
|
184
|
+
content_title: this.tracker_content_title,
|
|
185
|
+
content_categories: this.tracker_content_categories,
|
|
186
|
+
content_type: this.tracker_content_type,
|
|
187
|
+
user_type: this.tracker_user_type || 'R',
|
|
188
|
+
subscription_status: this.tracker_subscription_status,
|
|
189
|
+
page_domain: this.tracker_page_domain || 'Kompas.id',
|
|
190
|
+
metered_wall_type: this.tracker_metered_wall_type || 'MP',
|
|
191
|
+
metered_wall_balance: this.maxQuota - this.countdownArticle + 1,
|
|
192
|
+
paywall_segment: this.segment || '',
|
|
193
|
+
checkout_url:
|
|
194
|
+
this.packageData?.offering[this.offering ? this.offering : 'Q1']
|
|
195
|
+
.checkout_url,
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private stateDefaultPaywall = false
|
|
200
|
+
private computedstateDefaultPaywall() {
|
|
201
|
+
this.stateDefaultPaywall = !this.stateDefaultPaywall
|
|
202
|
+
this.requestUpdate()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Ganti placeholder _key_ di template dengan nilai dari data offering
|
|
207
|
+
* Hanya replace jika placeholder cocok dan datanya valid
|
|
208
|
+
*/
|
|
209
|
+
private replacePlaceholdersFromOffering(
|
|
210
|
+
template: string,
|
|
211
|
+
offeringData: OfferingItem
|
|
212
|
+
): string {
|
|
213
|
+
if (!offeringData) return template
|
|
214
|
+
// regex cari placeholder seperti _harga_coret_, _offering_price_, dst.
|
|
215
|
+
return template.replace(/_([a-zA-Z0-9_]+)_/g, (match, key) => {
|
|
216
|
+
// cek apakah key ada di dalam data
|
|
217
|
+
if (key in offeringData) {
|
|
218
|
+
const value = (offeringData as any)[key]
|
|
219
|
+
|
|
220
|
+
// jika value number, auto format ke rupiah
|
|
221
|
+
if (typeof value === 'number') {
|
|
222
|
+
return formatRupiah(value)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// jika string, langsung return
|
|
226
|
+
return String(value)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// kalau placeholder tidak dikenal, biarkan apa adanya
|
|
230
|
+
return match
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private engageReturners() {
|
|
235
|
+
const textTemplateFormater = this.replacePlaceholdersFromOffering(
|
|
236
|
+
this.packageData?.paywall.messageTitleCollapse || '',
|
|
237
|
+
this.packageData?.offering[this.offering ? this.offering : 'Q1'] ||
|
|
238
|
+
({} as OfferingItem)
|
|
239
|
+
)
|
|
240
|
+
return !this.stateDefaultPaywall
|
|
241
|
+
? html`
|
|
242
|
+
<div
|
|
243
|
+
class="fixed w-full inset-0 px-4 lg:px-0 h-full flex justify-center items-center bg-black bg-opacity-75 z-[999]"
|
|
244
|
+
>
|
|
245
|
+
<div
|
|
246
|
+
class="bg-white max-w-[460px] w-full justify-center items-center flex flex-col p-6 lg:pb-6 lg:px-6 lg:pt-9 relative rounded"
|
|
247
|
+
>
|
|
248
|
+
<button
|
|
249
|
+
class="font-bold cursor-pointer text-grey-400 flex rounded text-base absolute right-0 top-0 w-6 h-6 lg:w-8 lg:h-8 justify-center items-center mt-4 mr-4"
|
|
250
|
+
@click=${this.computedstateDefaultPaywall}
|
|
251
|
+
>
|
|
252
|
+
${unsafeSVG(getFontAwesomeIcon('fa', 'times', 20, 20))}
|
|
253
|
+
</button>
|
|
254
|
+
<image
|
|
255
|
+
src="${this.packageData?.paywall.image}"
|
|
256
|
+
alt="content"
|
|
257
|
+
class="w-[248px] lg:w-[364px]"
|
|
258
|
+
/>
|
|
259
|
+
<div
|
|
260
|
+
class="font-lora font-bold text-grey-600 lg:text-2xl text-center"
|
|
261
|
+
>
|
|
262
|
+
<div>Halo ${this.user_name},</div>
|
|
263
|
+
<div>${this.packageData?.paywall.messageTitleExpand}</div>
|
|
264
|
+
</div>
|
|
265
|
+
<div
|
|
266
|
+
class="pt-3 text-center font-sans text-grey-600 text-sm lg:text-base leading-none"
|
|
267
|
+
>
|
|
268
|
+
<div>${this.packageData?.paywall.descriptionExpand}</div>
|
|
269
|
+
<div class="text-base lg:text-lg text-grey-400 py-3">
|
|
270
|
+
<s
|
|
271
|
+
>${formatRupiah(
|
|
272
|
+
this.packageData?.offering[
|
|
273
|
+
this.offering ? this.offering : 'Q1'
|
|
274
|
+
].harga_coret || 0
|
|
275
|
+
)}</s
|
|
276
|
+
>
|
|
277
|
+
<span class="text-lg lg:text-2xl text-orange-400 font-bold">
|
|
278
|
+
${formatRupiah(
|
|
279
|
+
this.packageData?.offering[
|
|
280
|
+
this.offering ? this.offering : 'Q1'
|
|
281
|
+
].offering_price || 0
|
|
282
|
+
)}
|
|
283
|
+
</span>
|
|
284
|
+
${this.packageData?.offering[
|
|
285
|
+
this.offering ? this.offering : 'Q1'
|
|
286
|
+
].duration
|
|
287
|
+
? html`
|
|
288
|
+
<span class="text-base lg:text-lg text-grey-600"
|
|
289
|
+
>${this.packageData?.offering[
|
|
290
|
+
this.offering ? this.offering : 'Q1'
|
|
291
|
+
].duration}</span
|
|
292
|
+
>
|
|
293
|
+
`
|
|
294
|
+
: nothing}
|
|
295
|
+
</div>
|
|
296
|
+
<div>untuk tahun pertama.</div>
|
|
297
|
+
</div>
|
|
298
|
+
<div class="w-full pt-6">
|
|
299
|
+
<button
|
|
300
|
+
@click="${this.redirectToBerlangganan}"
|
|
301
|
+
class="bg-brand-1 whitespace-nowrap rounded md:rounded h-8 lg:h-10 px-4 md:px-5 text-sm lg:text-base text-white font-bold leading-none w-full"
|
|
302
|
+
>
|
|
303
|
+
${this.packageData?.offering[
|
|
304
|
+
this.offering ? this.offering : 'Q1'
|
|
305
|
+
].checkout_text}
|
|
306
|
+
</button>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
`
|
|
311
|
+
: html`
|
|
312
|
+
<div class="sticky bottom-0 w-full h-full">
|
|
313
|
+
<div
|
|
314
|
+
class="flex flex-col lg:flex-row w-full bg-blue-100 py-4 justify-center gap-4 px-4 lg:px-0 bottom-0"
|
|
315
|
+
>
|
|
316
|
+
<div
|
|
317
|
+
class="text-grey-600 text-base self-center text-left message-collapse-engage-returners font-bold"
|
|
318
|
+
>
|
|
319
|
+
${unsafeHTML(textTemplateFormater)}
|
|
320
|
+
</div>
|
|
321
|
+
<div>
|
|
322
|
+
<button
|
|
323
|
+
@click="${this.redirectToBerlangganan}"
|
|
324
|
+
class="bg-brand-1 whitespace-nowrap rounded md:rounded h-10 px-4 md:px-5 text-base text-white font-bold leading-[18px] w-full lg:w-auto"
|
|
325
|
+
>
|
|
326
|
+
${this.packageData?.offering[
|
|
327
|
+
this.offering ? this.offering : 'Q1'
|
|
328
|
+
].checkout_text}
|
|
329
|
+
</button>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
`
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private passiveFaders() {
|
|
337
|
+
const messageTitleCollapse = this.replacePlaceholdersFromOffering(
|
|
338
|
+
this.packageData?.paywall.messageTitleCollapse || '',
|
|
339
|
+
this.packageData?.offering[this.offering ? this.offering : 'Q1'] ||
|
|
340
|
+
({} as OfferingItem)
|
|
341
|
+
)
|
|
342
|
+
const descriptionExpand = this.replacePlaceholdersFromOffering(
|
|
343
|
+
this.packageData?.paywall.descriptionExpand || '',
|
|
344
|
+
this.packageData?.offering[this.offering ? this.offering : 'Q1'] ||
|
|
345
|
+
({} as OfferingItem)
|
|
346
|
+
)
|
|
347
|
+
return html`
|
|
348
|
+
<div class="sticky bottom-0 w-full h-full">
|
|
349
|
+
<div
|
|
350
|
+
class="flex flex-col lg:flex-row w-full bg-blue-100 py-4 gap-4 px-4 bottom-0"
|
|
351
|
+
>
|
|
352
|
+
<div
|
|
353
|
+
class="flex flex-col lg:flex-row w-full justify-between max-w-[1200px] gap-4 mx-auto "
|
|
354
|
+
>
|
|
355
|
+
${!this.stateDefaultPaywall
|
|
356
|
+
? html`
|
|
357
|
+
<div class="h-9 w-9 hidden lg:flex"></div>
|
|
358
|
+
<div
|
|
359
|
+
class="flex flex-col lg:flex-row justify-center items-center lg:items-start lg:justify-between w-full max-w-5xl"
|
|
360
|
+
>
|
|
361
|
+
<div
|
|
362
|
+
class="text-grey-600 font-lora message-collapse-engage-returners order-2"
|
|
363
|
+
>
|
|
364
|
+
<div
|
|
365
|
+
class="text-xl lg:text-2xl font-bold mb-3 text-center lg:text-start"
|
|
366
|
+
>
|
|
367
|
+
${this.packageData?.paywall.messageTitleExpand}
|
|
368
|
+
</div>
|
|
369
|
+
<div class="flex flex-col font-sans gap-3 mb-3">
|
|
370
|
+
${this.packageData?.paywall.listBenefits
|
|
371
|
+
? this.packageData?.paywall.listBenefits.map(
|
|
372
|
+
item =>
|
|
373
|
+
html`
|
|
374
|
+
<div
|
|
375
|
+
class="flex items-baseline align-baseline"
|
|
376
|
+
>
|
|
377
|
+
<div
|
|
378
|
+
class="bg-white text-green-500 h-3 w-3 mr-1 rounded-full flex justify-center items-center"
|
|
379
|
+
>
|
|
380
|
+
${unsafeSVG(
|
|
381
|
+
getFontAwesomeIcon(
|
|
382
|
+
'fas',
|
|
383
|
+
'circle-check',
|
|
384
|
+
12,
|
|
385
|
+
12
|
|
386
|
+
)
|
|
387
|
+
)}
|
|
388
|
+
</div>
|
|
389
|
+
<h6 class="text-base ml-1">${item}</h6>
|
|
390
|
+
</div>
|
|
391
|
+
`
|
|
392
|
+
)
|
|
393
|
+
: nothing}
|
|
394
|
+
</div>
|
|
395
|
+
<div
|
|
396
|
+
class="text-grey-600 font-sans text-sm lg:text-base self-center text-left message-collapse-engage-returners mb-4"
|
|
397
|
+
>
|
|
398
|
+
${unsafeHTML(descriptionExpand)}
|
|
399
|
+
</div>
|
|
400
|
+
<div>
|
|
401
|
+
<button
|
|
402
|
+
@click="${this.redirectToBerlangganan}"
|
|
403
|
+
class="bg-brand-1 flex whitespace-nowrap items-center justify-center rounded md:rounded h-10 px-4 md:px-5 text-base text-white font-bold leading-[18px] w-full lg:w-auto"
|
|
404
|
+
>
|
|
405
|
+
${this.packageData?.offering[
|
|
406
|
+
this.offering ? this.offering : 'Q1'
|
|
407
|
+
].checkout_text}
|
|
408
|
+
</button>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
<div
|
|
412
|
+
class="flex self-center mt-10 mb-3 lg:mb-0 lg:mt-0 lg:ml-6 order-1 lg:order-2"
|
|
413
|
+
>
|
|
414
|
+
<image
|
|
415
|
+
src="${this.packageData?.paywall.image}"
|
|
416
|
+
alt="content"
|
|
417
|
+
class="w-[112px] lg:w-[202px]"
|
|
418
|
+
/>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
<div>
|
|
422
|
+
<button
|
|
423
|
+
@click="${this.computedstateDefaultPaywall}"
|
|
424
|
+
class="h-9 w-9 absolute lg:static flex items-center justify-center text-blue-500 rounded bg-blue-200 right-4 top-4"
|
|
425
|
+
>
|
|
426
|
+
<div>
|
|
427
|
+
${unsafeSVG(getFontAwesomeIcon('fas', 'chevron-down'))}
|
|
428
|
+
</div>
|
|
429
|
+
</button>
|
|
430
|
+
</div>
|
|
431
|
+
`
|
|
432
|
+
: html`
|
|
433
|
+
<div class="h-9 w-9 flex"></div>
|
|
434
|
+
<div class="flex justify-between">
|
|
435
|
+
<div
|
|
436
|
+
class="text-grey-600 text-base self-center text-left message-collapse-engage-returners font-bold"
|
|
437
|
+
>
|
|
438
|
+
${unsafeHTML(messageTitleCollapse)}
|
|
439
|
+
</div>
|
|
440
|
+
<div class="hidden lg:flex ml-6">
|
|
441
|
+
<button
|
|
442
|
+
@click="${this.redirectToBerlangganan}"
|
|
443
|
+
class="bg-brand-1 whitespace-nowrap rounded md:rounded h-10 px-4 md:px-5 text-base text-white font-bold leading-[18px] w-full lg:w-auto"
|
|
444
|
+
>
|
|
445
|
+
${this.packageData?.offering[
|
|
446
|
+
this.offering ? this.offering : 'Q1'
|
|
447
|
+
].checkout_text}
|
|
448
|
+
</button>
|
|
449
|
+
</div>
|
|
450
|
+
<div class="flex lg:hidden ml-4">
|
|
451
|
+
<button
|
|
452
|
+
@click="${this.computedstateDefaultPaywall}"
|
|
453
|
+
class="h-9 w-9 flex items-center justify-center text-blue-500 rounded bg-blue-200"
|
|
454
|
+
>
|
|
455
|
+
<div>
|
|
456
|
+
${unsafeSVG(getFontAwesomeIcon('fas', 'chevron-up'))}
|
|
457
|
+
</div>
|
|
458
|
+
</button>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
<div>
|
|
462
|
+
<button
|
|
463
|
+
@click="${this.computedstateDefaultPaywall}"
|
|
464
|
+
class="h-9 w-9 hidden lg:flex items-center justify-center text-blue-500 rounded bg-blue-200"
|
|
465
|
+
>
|
|
466
|
+
<div>
|
|
467
|
+
${unsafeSVG(getFontAwesomeIcon('fas', 'chevron-up'))}
|
|
468
|
+
</div>
|
|
469
|
+
</button>
|
|
470
|
+
<button
|
|
471
|
+
@click="${this.redirectToBerlangganan}"
|
|
472
|
+
class="bg-brand-1 lg:hidden flex whitespace-nowrap items-center justify-center rounded md:rounded h-10 px-4 md:px-5 text-base text-white font-bold leading-[18px] w-full"
|
|
473
|
+
>
|
|
474
|
+
${this.packageData?.offering[
|
|
475
|
+
this.offering ? this.offering : 'Q1'
|
|
476
|
+
].checkout_text}
|
|
477
|
+
</button>
|
|
478
|
+
</div>
|
|
479
|
+
`}
|
|
480
|
+
</div>
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
`
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private pickTemplate() {
|
|
487
|
+
switch (this.segment) {
|
|
488
|
+
case 'Engaged Returners':
|
|
489
|
+
return this.engageReturners()
|
|
490
|
+
case 'Passive Faders':
|
|
491
|
+
return this.passiveFaders()
|
|
492
|
+
default:
|
|
493
|
+
return nothing
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
render() {
|
|
498
|
+
return !this.isLoading ? this.pickTemplate() : nothing
|
|
499
|
+
}
|
|
500
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# kompasid-metered-paywall-personalize
|
|
2
|
+
|
|
3
|
+
Komponen Web Component yang digunakan untuk menampilkan **Metered Paywall Personalize** pada situs **Kompas.id**.
|
|
4
|
+
Komponen ini menyesuaikan tampilan paywall berdasarkan **segmentasi pengguna** seperti *Engaged Returners* dan *Passive Faders*, serta mengirim event tracking ke **Google Tag Manager (GTM)**.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📦 Import
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
import 'kompasid-metered-paywall-personalize'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🧩 Deskripsi Umum
|
|
17
|
+
|
|
18
|
+
Komponen ini akan:
|
|
19
|
+
- Menampilkan tampilan paywall berdasarkan segmentasi pengguna (`segment`).
|
|
20
|
+
- Mengambil data paywall dari endpoint CDN Kompas.
|
|
21
|
+
- Mengirim event `paywall_viewed` dan `subscribe_button_clicked` ke `window.dataLayer`.
|
|
22
|
+
- Melakukan personalisasi tampilan dan teks berdasarkan data offering (paket langganan).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## ⚙️ Properties
|
|
27
|
+
|
|
28
|
+
| Property | Attribute | Description | Type | Default |
|
|
29
|
+
| ------------------------------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- |
|
|
30
|
+
| `countdownArticle` | `countdown-article` | Menghitung berapa artikel gratis yang telah dibaca user (untuk GTM tracking, tidak ditampilkan di UI). | `number` | `0` |
|
|
31
|
+
| `segment` | `segment` | Menentukan template paywall berdasarkan segmentasi user (`Engaged Returners`, `Passive Faders`, dll). | `string` | `''` |
|
|
32
|
+
| `offering` | `offering` | Menentukan paket penawaran langganan (default: `Q1`). | `string` | `''` |
|
|
33
|
+
| `user_name` | `user_name` | Nama pengguna untuk menampilkan sapaan personal (“Halo, [nama]”). | `string` | `''` |
|
|
34
|
+
| `paywall_location` | `paywall_location` | Lokasi di mana user menemukan paywall. | `string` | `''` |
|
|
35
|
+
| `paywall_subscription_package` | `paywall_subscription_package` | Nama paket langganan yang ditampilkan ke user. | `string` | `''` |
|
|
36
|
+
| `paywall_subscription_id` | `paywall_subscription_id` | ID paket langganan yang ditampilkan ke user. | `string` | `''` |
|
|
37
|
+
| `paywall_subscription_price` | `paywall_subscription_price` | Harga paket langganan yang ditampilkan ke user. | `number` | `0` |
|
|
38
|
+
| `paywall_position` | `paywall_position` | Posisi dari paket langganan yang sedang ditampilkan. | `number` | `0` |
|
|
39
|
+
| `tracker_page_type` | `tracker_page_type` | Jenis halaman (misalnya: `article`, `homepage`). | `string` | `''` |
|
|
40
|
+
| `tracker_content_id` | `tracker_content_id` | ID atau slug artikel. | `string` | `''` |
|
|
41
|
+
| `tracker_content_title` | `tracker_content_title` | Judul artikel yang sedang dibaca. | `string` | `''` |
|
|
42
|
+
| `tracker_content_categories` | `tracker_content_categories` | Kategori dari artikel yang sedang dibaca. | `string` | `''` |
|
|
43
|
+
| `tracker_content_type` | `tracker_content_type` | Jenis artikel (`free` atau `paid`). | `string` | `''` |
|
|
44
|
+
| `tracker_user_type` | `tracker_user_type` | Tipe user berdasarkan status langganan (`R` untuk regon, `S` untuk subscriber). | `string` | `''` |
|
|
45
|
+
| `tracker_subscription_status` | `tracker_subscription_status` | Status langganan user. | `string` | `''` |
|
|
46
|
+
| `tracker_page_domain` | `tracker_page_domain` | Domain halaman (default: `Kompas.id`). | `string` | `''` |
|
|
47
|
+
| `tracker_metered_wall_type` | `tracker_metered_wall_type` | Tipe paywall yang digunakan (contoh: `MP` = Metered Paywall). | `string` | `''` |
|
|
48
|
+
| `tracker_metered_wall_balance` | `tracker_metered_wall_balance` | Sisa kuota artikel gratis (calculated: `maxQuota - countdownArticle + 1`). | `number` | `0` |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🧠 State Internal
|
|
53
|
+
|
|
54
|
+
| State Name | Type | Default | Description |
|
|
55
|
+
| ----------------- | --------- | -------- | --------------------------------------------------------- |
|
|
56
|
+
| `isLoading` | `boolean` | `true` | Indikator apakah data JSON sudah dimuat. |
|
|
57
|
+
| `maxQuota` | `number` | `3` | Jumlah maksimum artikel gratis sebelum paywall muncul. |
|
|
58
|
+
| `packageData` | `object` | `undefined` | Data JSON hasil fetch dari CDN. |
|
|
59
|
+
| `stateDefaultPaywall` | `boolean` | `false` | Mengatur tampilan collapse/expand paywall. |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🧾 Lifecycle
|
|
64
|
+
|
|
65
|
+
### `connectedCallback()`
|
|
66
|
+
- Dipanggil saat komponen dimasukkan ke DOM.
|
|
67
|
+
- Memanggil:
|
|
68
|
+
- `getMeteredPaywallData()` untuk fetch JSON paywall.
|
|
69
|
+
- `dataLayeronMeteredPaywall()` untuk push event “paywall_viewed”.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 🔄 Method
|
|
74
|
+
|
|
75
|
+
| Method Name | Description |
|
|
76
|
+
| ------------ | ------------ |
|
|
77
|
+
| **`getMeteredPaywallData()`** | Fetch data JSON berdasarkan segment dari endpoint CDN. |
|
|
78
|
+
| **`redirectToBerlangganan()`** | Mengarahkan user ke halaman checkout berdasarkan URL di `offering`. |
|
|
79
|
+
| **`dataLayeronLanggananButton()`** | Mengirim event `subscribe_button_clicked` ke `window.dataLayer`. |
|
|
80
|
+
| **`dataLayeronMeteredPaywall()`** | Mengirim event `paywall_viewed` ke `window.dataLayer`. |
|
|
81
|
+
| **`replacePlaceholdersFromOffering(template, offeringData)`** | Melakukan replace placeholder seperti `_harga_coret_` di template dengan nilai dari `offeringData`. |
|
|
82
|
+
| **`engageReturners()`** | Template tampilan paywall untuk segment “Engaged Returners”. |
|
|
83
|
+
| **`passiveFaders()`** | Template tampilan paywall untuk segment “Passive Faders”. |
|
|
84
|
+
| **`pickTemplate()`** | Menentukan tampilan yang digunakan berdasarkan `segment`. |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🎨 Tampilan
|
|
89
|
+
|
|
90
|
+
Komponen menggunakan kombinasi **TailwindCSS** (`TWStyles`) dan CSS custom untuk gaya visual seperti:
|
|
91
|
+
- `.font-lora` → Font Lora / Georgia.
|
|
92
|
+
- `.icon` → Ikon Font Awesome dengan ukuran dinamis.
|
|
93
|
+
- `.message-collapse-engage-returners` → Penyorotan teks utama di banner bawah.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 🌐 JSON Endpoint Example
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"paywall": {
|
|
102
|
+
"messageTitleExpand": "Dapatkan akses penuh ke Kompas.id",
|
|
103
|
+
"messageTitleCollapse": "Langganan hanya _offering_price_ per bulan",
|
|
104
|
+
"descriptionExpand": "Nikmati berita premium dan eksklusif hanya di Kompas.id",
|
|
105
|
+
"image": "https://cdn-www.kompas.id/images/paywall-image.jpg"
|
|
106
|
+
},
|
|
107
|
+
"offering": {
|
|
108
|
+
"Q1": {
|
|
109
|
+
"harga_coret": 100000,
|
|
110
|
+
"offering_price": 75000,
|
|
111
|
+
"duration": "per bulan",
|
|
112
|
+
"checkout_url": "https://www.kompas.id/berlangganan?package=Q1",
|
|
113
|
+
"checkout_text": "Langganan Sekarang"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 📊 Event Tracking (GTM)
|
|
122
|
+
|
|
123
|
+
| Event | Description | Data Sent |
|
|
124
|
+
| ------ | ------------ | ---------- |
|
|
125
|
+
| `paywall_viewed` | Dikirim saat komponen dimuat pertama kali. | `paywall_location`, `segment`, `content_id`, `subscription_status`, dll. |
|
|
126
|
+
| `subscribe_button_clicked` | Dikirim saat user klik tombol langganan. | `checkout_url`, `offering_price`, `paywall_position`, dll. |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 🧱 Contoh Penggunaan
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<kompasid-metered-paywall-personalize
|
|
134
|
+
segment="Engaged Returners"
|
|
135
|
+
offering="Q1"
|
|
136
|
+
user_name="Andi"
|
|
137
|
+
countdown-article="2"
|
|
138
|
+
paywall_location="Article Page"
|
|
139
|
+
tracker_content_id="artikel-luar-biasa"
|
|
140
|
+
tracker_page_type="article"
|
|
141
|
+
>
|
|
142
|
+
</kompasid-metered-paywall-personalize>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 🪄 Catatan
|
|
148
|
+
- Komponen ini bergantung pada file JSON di CDN:
|
|
149
|
+
`https://cdn-www.kompas.id/web-component/metered-paywall-personalize/{segment}.json`
|
|
150
|
+
- Pastikan properti `segment` sesuai dengan file JSON yang tersedia.
|
|
151
|
+
- Jika `segment` tidak ditemukan, komponen tidak akan menampilkan apa pun (`nothing`).
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
**Dibuat oleh tim Front-End Kompas.id**
|
|
155
|
+
_© Kompas.id, 2025_
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface OfferingItem {
|
|
2
|
+
offering_price: number
|
|
3
|
+
harga_coret: number
|
|
4
|
+
duration?: string
|
|
5
|
+
checkout_url: string
|
|
6
|
+
checkout_text: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface OfferingList {
|
|
10
|
+
[key: string]: OfferingItem
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PaywallData {
|
|
14
|
+
messageTitleExpand: string
|
|
15
|
+
descriptionExpand: string
|
|
16
|
+
messageTitleCollapse: string
|
|
17
|
+
descriptionCollapse?: string
|
|
18
|
+
listBenefits?: string[]
|
|
19
|
+
image: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PackageData {
|
|
23
|
+
segment_name: string
|
|
24
|
+
paywall: PaywallData
|
|
25
|
+
offering: OfferingList
|
|
26
|
+
}
|