@formata/limitr-ui 0.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.
- package/README.md +30 -0
- package/dist/current.d.ts +84 -0
- package/dist/current.d.ts.map +1 -0
- package/dist/current.js +1079 -0
- package/dist/current.js.map +1 -0
- package/dist/element.d.ts +90 -0
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +282 -0
- package/dist/element.js.map +1 -0
- package/dist/mod.d.ts +4 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +19 -0
- package/dist/mod.js.map +1 -0
- package/dist/table.d.ts +42 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +742 -0
- package/dist/table.js.map +1 -0
- package/package.json +42 -0
package/dist/current.js
ADDED
|
@@ -0,0 +1,1079 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 Formata, Inc. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
//
|
|
16
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
17
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
18
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
19
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
20
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
21
|
+
};
|
|
22
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
23
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
24
|
+
};
|
|
25
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
26
|
+
import { LimitrElement } from './element.js';
|
|
27
|
+
import { css, html, nothing } from "lit";
|
|
28
|
+
import './table.ts';
|
|
29
|
+
/**
|
|
30
|
+
* Show a customer's current plan & usage information (if any).
|
|
31
|
+
*
|
|
32
|
+
* For Stripe integration via Limitr Cloud, the following metadata fields are expected
|
|
33
|
+
* in customer.metadata:
|
|
34
|
+
*
|
|
35
|
+
* - stripe_subscription_id: string - Stripe subscription ID
|
|
36
|
+
* - stripe_subscription_status: string - Status (active, canceled, past_due, etc.)
|
|
37
|
+
* - stripe_current_period_start: number - Unix timestamp (ms)
|
|
38
|
+
* - stripe_current_period_end: number - Unix timestamp (ms)
|
|
39
|
+
* - stripe_cancel_at_period_end: boolean - Will cancel at end of period
|
|
40
|
+
* - stripe_customer_id: string - Stripe customer ID
|
|
41
|
+
* - stripe_payment_method_type: string - Payment method type (card, etc.)
|
|
42
|
+
* - stripe_payment_method_last4: string - Last 4 digits of payment method
|
|
43
|
+
* - stripe_payment_method_brand: string - Card brand (visa, mastercard, etc.)
|
|
44
|
+
*
|
|
45
|
+
* Coupon metadata fields (populated by Limitr when coupon is applied):
|
|
46
|
+
* - stripe_coupon_code: string - The coupon/promo code
|
|
47
|
+
* - stripe_coupon_status: string - 'pending' | 'applied' | 'invalid' | 'expired'
|
|
48
|
+
* - stripe_coupon_name: string - Display name for the coupon
|
|
49
|
+
* - stripe_coupon_percent_off: string - Percentage off (if percent-based)
|
|
50
|
+
* - stripe_coupon_amount_off: string - Amount off in cents (if amount-based)
|
|
51
|
+
* - stripe_coupon_currency: string - Currency for amount_off coupons
|
|
52
|
+
* - stripe_coupon_duration: string - 'once' | 'repeating' | 'forever'
|
|
53
|
+
* - stripe_coupon_duration_in_months: string - Number of months (if repeating)
|
|
54
|
+
*/
|
|
55
|
+
let LimitrCurrentPlan = class LimitrCurrentPlan extends LimitrElement {
|
|
56
|
+
constructor() {
|
|
57
|
+
super(...arguments);
|
|
58
|
+
this.showStripeInfo = false;
|
|
59
|
+
this.hideUsage = false;
|
|
60
|
+
this.hideCancel = false;
|
|
61
|
+
this.cancelImmediately = false;
|
|
62
|
+
this.theme = 'light';
|
|
63
|
+
this.denyPolicyChanges = false;
|
|
64
|
+
this.currentPlan = null;
|
|
65
|
+
this.currentCustomer = null;
|
|
66
|
+
this.loading = true;
|
|
67
|
+
this.showPricingTable = false;
|
|
68
|
+
this._internalHideCancel = false;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Styles.
|
|
72
|
+
*/
|
|
73
|
+
static get styles() {
|
|
74
|
+
return css `
|
|
75
|
+
:host {
|
|
76
|
+
display: block;
|
|
77
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
78
|
+
--limitr-bg-primary: #ffffff;
|
|
79
|
+
--limitr-bg-secondary: #f8f9fa;
|
|
80
|
+
--limitr-text-primary: #000000;
|
|
81
|
+
--limitr-text-secondary: #6c757d;
|
|
82
|
+
--limitr-border: #dee2e6;
|
|
83
|
+
--limitr-accent: #000000;
|
|
84
|
+
--limitr-accent-text: #ffffff;
|
|
85
|
+
--limitr-radius: 12px;
|
|
86
|
+
--limitr-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
:host([theme="dark"]) {
|
|
90
|
+
--limitr-bg-primary: #1a1a1a;
|
|
91
|
+
--limitr-bg-secondary: #2d2d2d;
|
|
92
|
+
--limitr-text-primary: #ffffff;
|
|
93
|
+
--limitr-text-secondary: #a0a0a0;
|
|
94
|
+
--limitr-border: #404040;
|
|
95
|
+
--limitr-accent: #ffffff;
|
|
96
|
+
--limitr-accent-text: #000000;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.container {
|
|
100
|
+
margin: 0 auto;
|
|
101
|
+
padding: 24px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.plan-card {
|
|
105
|
+
background: var(--limitr-bg-primary);
|
|
106
|
+
border: 2px solid var(--limitr-border);
|
|
107
|
+
border-radius: var(--limitr-radius);
|
|
108
|
+
padding: 32px;
|
|
109
|
+
box-shadow: var(--limitr-shadow);
|
|
110
|
+
margin-bottom: 24px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.plan-header {
|
|
114
|
+
display: flex;
|
|
115
|
+
justify-content: space-between;
|
|
116
|
+
align-items: flex-start;
|
|
117
|
+
margin-bottom: 24px;
|
|
118
|
+
padding-bottom: 24px;
|
|
119
|
+
border-bottom: 2px solid var(--limitr-border);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.plan-info {
|
|
123
|
+
flex: 1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.plan-label {
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
text-transform: uppercase;
|
|
129
|
+
letter-spacing: 0.5px;
|
|
130
|
+
color: var(--limitr-text-secondary);
|
|
131
|
+
margin-bottom: 8px;
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.plan-name {
|
|
136
|
+
font-size: 28px;
|
|
137
|
+
font-weight: 700;
|
|
138
|
+
color: var(--limitr-text-primary);
|
|
139
|
+
margin: 0 0 8px 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.plan-price {
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: baseline;
|
|
145
|
+
gap: 4px;
|
|
146
|
+
color: var(--limitr-text-secondary);
|
|
147
|
+
font-size: 18px;
|
|
148
|
+
font-weight: 500;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.price-amount {
|
|
152
|
+
font-size: 24px;
|
|
153
|
+
font-weight: 700;
|
|
154
|
+
color: var(--limitr-text-primary);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.change-plan-btn {
|
|
158
|
+
padding: 12px 24px;
|
|
159
|
+
border: 2px solid var(--limitr-accent);
|
|
160
|
+
background: transparent;
|
|
161
|
+
color: var(--limitr-accent);
|
|
162
|
+
font-size: 14px;
|
|
163
|
+
font-weight: 600;
|
|
164
|
+
border-radius: 8px;
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
transition: all 0.2s ease;
|
|
167
|
+
text-transform: uppercase;
|
|
168
|
+
letter-spacing: 0.5px;
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.change-plan-btn:hover {
|
|
173
|
+
background: var(--limitr-accent);
|
|
174
|
+
color: var(--limitr-accent-text);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.plan-actions {
|
|
178
|
+
display: flex;
|
|
179
|
+
flex-direction: column;
|
|
180
|
+
gap: 12px;
|
|
181
|
+
align-items: flex-end;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.cancel-plan-btn {
|
|
185
|
+
padding: 8px 16px;
|
|
186
|
+
border: 1px solid #dc2626;
|
|
187
|
+
background: transparent;
|
|
188
|
+
color: #dc2626;
|
|
189
|
+
font-size: 12px;
|
|
190
|
+
font-weight: 600;
|
|
191
|
+
border-radius: 6px;
|
|
192
|
+
cursor: pointer;
|
|
193
|
+
transition: all 0.2s ease;
|
|
194
|
+
text-transform: uppercase;
|
|
195
|
+
letter-spacing: 0.5px;
|
|
196
|
+
white-space: nowrap;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.cancel-plan-btn:hover {
|
|
200
|
+
background: #dc2626;
|
|
201
|
+
color: #ffffff;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.resume-plan-btn {
|
|
205
|
+
padding: 8px 16px;
|
|
206
|
+
border: 1px solid #10b981;
|
|
207
|
+
background: transparent;
|
|
208
|
+
color: #10b981;
|
|
209
|
+
font-size: 12px;
|
|
210
|
+
font-weight: 600;
|
|
211
|
+
border-radius: 6px;
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
transition: all 0.2s ease;
|
|
214
|
+
text-transform: uppercase;
|
|
215
|
+
letter-spacing: 0.5px;
|
|
216
|
+
white-space: nowrap;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.resume-plan-btn:hover {
|
|
220
|
+
background: #10b981;
|
|
221
|
+
color: #ffffff;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.price-original {
|
|
225
|
+
font-size: 16px;
|
|
226
|
+
color: var(--limitr-text-secondary);
|
|
227
|
+
text-decoration: line-through;
|
|
228
|
+
margin-right: 8px;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.coupon-badge {
|
|
232
|
+
display: inline-flex;
|
|
233
|
+
align-items: center;
|
|
234
|
+
gap: 6px;
|
|
235
|
+
margin-top: 6px;
|
|
236
|
+
padding: 4px 10px;
|
|
237
|
+
background: #eeffc9;
|
|
238
|
+
color: #4d7c0f;
|
|
239
|
+
border-radius: 20px;
|
|
240
|
+
font-size: 12px;
|
|
241
|
+
font-weight: 600;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
:host([theme="dark"]) .coupon-badge {
|
|
245
|
+
background: #2d4a0a;
|
|
246
|
+
color: #a3e635;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.coupon-badge .coupon-tag-icon {
|
|
250
|
+
font-size: 13px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.coupon-duration {
|
|
254
|
+
font-size: 11px;
|
|
255
|
+
color: var(--limitr-text-secondary);
|
|
256
|
+
margin-top: 2px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.usage-section {
|
|
260
|
+
margin-bottom: 32px;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.section-title {
|
|
264
|
+
font-size: 16px;
|
|
265
|
+
font-weight: 600;
|
|
266
|
+
color: var(--limitr-text-primary);
|
|
267
|
+
margin: 0 0 16px 0;
|
|
268
|
+
text-transform: uppercase;
|
|
269
|
+
letter-spacing: 0.5px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.usage-item {
|
|
273
|
+
margin-bottom: 20px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.usage-item:last-child {
|
|
277
|
+
margin-bottom: 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.usage-header {
|
|
281
|
+
display: flex;
|
|
282
|
+
justify-content: space-between;
|
|
283
|
+
align-items: center;
|
|
284
|
+
margin-bottom: 8px;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.usage-name {
|
|
288
|
+
font-size: 14px;
|
|
289
|
+
font-weight: 500;
|
|
290
|
+
color: var(--limitr-text-primary);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.usage-stats {
|
|
294
|
+
font-size: 14px;
|
|
295
|
+
font-weight: 600;
|
|
296
|
+
color: var(--limitr-text-secondary);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.usage-bar {
|
|
300
|
+
width: 100%;
|
|
301
|
+
height: 8px;
|
|
302
|
+
background: var(--limitr-bg-secondary);
|
|
303
|
+
border-radius: 4px;
|
|
304
|
+
overflow: hidden;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.usage-bar-fill {
|
|
308
|
+
height: 100%;
|
|
309
|
+
background: var(--limitr-accent);
|
|
310
|
+
transition: width 0.3s ease;
|
|
311
|
+
border-radius: 4px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.usage-bar-fill.warning {
|
|
315
|
+
background: #f59e0b;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.usage-bar-fill.danger {
|
|
319
|
+
background: #ef4444;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.stripe-section {
|
|
323
|
+
margin-bottom: 32px;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.stripe-info {
|
|
327
|
+
display: grid;
|
|
328
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
329
|
+
gap: 16px;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.info-item {
|
|
333
|
+
padding: 16px;
|
|
334
|
+
background: var(--limitr-bg-secondary);
|
|
335
|
+
border-radius: 8px;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.info-label {
|
|
339
|
+
font-size: 12px;
|
|
340
|
+
text-transform: uppercase;
|
|
341
|
+
letter-spacing: 0.5px;
|
|
342
|
+
color: var(--limitr-text-secondary);
|
|
343
|
+
margin-bottom: 4px;
|
|
344
|
+
font-weight: 600;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.info-value {
|
|
348
|
+
font-size: 16px;
|
|
349
|
+
font-weight: 600;
|
|
350
|
+
color: var(--limitr-text-primary);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.status-badge {
|
|
354
|
+
display: inline-block;
|
|
355
|
+
padding: 4px 12px;
|
|
356
|
+
border-radius: 12px;
|
|
357
|
+
font-size: 12px;
|
|
358
|
+
font-weight: 600;
|
|
359
|
+
text-transform: uppercase;
|
|
360
|
+
letter-spacing: 0.5px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.status-badge.active {
|
|
364
|
+
background: #10b981;
|
|
365
|
+
color: #ffffff;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.status-badge.canceled {
|
|
369
|
+
background: #6b7280;
|
|
370
|
+
color: #ffffff;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.status-badge.past_due {
|
|
374
|
+
background: #ef4444;
|
|
375
|
+
color: #ffffff;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.invoices-section {
|
|
379
|
+
margin-top: 32px;
|
|
380
|
+
padding-top: 24px;
|
|
381
|
+
border-top: 2px solid var(--limitr-border);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.invoices-list {
|
|
385
|
+
display: flex;
|
|
386
|
+
flex-direction: column;
|
|
387
|
+
gap: 12px;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.invoice-item {
|
|
391
|
+
display: flex;
|
|
392
|
+
align-items: center;
|
|
393
|
+
justify-content: space-between;
|
|
394
|
+
padding: 16px;
|
|
395
|
+
background: var(--limitr-bg-secondary);
|
|
396
|
+
border-radius: 8px;
|
|
397
|
+
gap: 16px;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.invoice-info {
|
|
401
|
+
flex: 1;
|
|
402
|
+
min-width: 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.invoice-number {
|
|
406
|
+
font-weight: 600;
|
|
407
|
+
color: var(--limitr-text-primary);
|
|
408
|
+
font-size: 14px;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.invoice-date {
|
|
412
|
+
font-size: 12px;
|
|
413
|
+
color: var(--limitr-text-secondary);
|
|
414
|
+
margin-top: 4px;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.invoice-amount {
|
|
418
|
+
font-weight: 600;
|
|
419
|
+
color: var(--limitr-text-primary);
|
|
420
|
+
white-space: nowrap;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.invoice-download {
|
|
424
|
+
padding: 8px 16px;
|
|
425
|
+
border: 2px solid var(--limitr-accent);
|
|
426
|
+
background: transparent;
|
|
427
|
+
color: var(--limitr-accent);
|
|
428
|
+
font-size: 13px;
|
|
429
|
+
font-weight: 600;
|
|
430
|
+
border-radius: 6px;
|
|
431
|
+
cursor: pointer;
|
|
432
|
+
transition: all 0.2s ease;
|
|
433
|
+
text-decoration: none;
|
|
434
|
+
white-space: nowrap;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.invoice-download:hover {
|
|
438
|
+
background: var(--limitr-accent);
|
|
439
|
+
color: var(--limitr-accent-text);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.loading {
|
|
443
|
+
text-align: center;
|
|
444
|
+
padding: 48px;
|
|
445
|
+
color: var(--limitr-text-secondary);
|
|
446
|
+
font-size: 16px;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.empty {
|
|
450
|
+
text-align: center;
|
|
451
|
+
padding: 48px;
|
|
452
|
+
color: var(--limitr-text-secondary);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.empty-title {
|
|
456
|
+
font-size: 20px;
|
|
457
|
+
font-weight: 600;
|
|
458
|
+
margin-bottom: 8px;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.modal-overlay {
|
|
462
|
+
position: fixed;
|
|
463
|
+
top: 0;
|
|
464
|
+
left: 0;
|
|
465
|
+
right: 0;
|
|
466
|
+
bottom: 0;
|
|
467
|
+
background: rgba(0, 0, 0, 0.5);
|
|
468
|
+
display: flex;
|
|
469
|
+
align-items: center;
|
|
470
|
+
justify-content: center;
|
|
471
|
+
z-index: 1000;
|
|
472
|
+
padding: 24px;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.modal-content {
|
|
476
|
+
background: var(--limitr-bg-primary);
|
|
477
|
+
border-radius: var(--limitr-radius);
|
|
478
|
+
max-width: 1200px;
|
|
479
|
+
width: 100%;
|
|
480
|
+
max-height: 90vh;
|
|
481
|
+
overflow-y: auto;
|
|
482
|
+
position: relative;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.modal-header {
|
|
486
|
+
display: flex;
|
|
487
|
+
justify-content: space-between;
|
|
488
|
+
align-items: center;
|
|
489
|
+
padding: 24px 24px 16px;
|
|
490
|
+
border-bottom: 2px solid var(--limitr-border);
|
|
491
|
+
position: sticky;
|
|
492
|
+
top: 0;
|
|
493
|
+
background: var(--limitr-bg-primary);
|
|
494
|
+
z-index: 1;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.modal-title {
|
|
498
|
+
font-size: 24px;
|
|
499
|
+
font-weight: 700;
|
|
500
|
+
color: var(--limitr-text-primary);
|
|
501
|
+
margin: 0;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.close-btn {
|
|
505
|
+
background: transparent;
|
|
506
|
+
border: none;
|
|
507
|
+
font-size: 28px;
|
|
508
|
+
color: var(--limitr-text-secondary);
|
|
509
|
+
cursor: pointer;
|
|
510
|
+
padding: 4px 8px;
|
|
511
|
+
line-height: 1;
|
|
512
|
+
transition: color 0.2s ease;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.close-btn:hover {
|
|
516
|
+
color: var(--limitr-text-primary);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
@media (max-width: 768px) {
|
|
520
|
+
.container {
|
|
521
|
+
padding: 16px;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.plan-card {
|
|
525
|
+
padding: 24px;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.plan-header {
|
|
529
|
+
flex-direction: column;
|
|
530
|
+
gap: 16px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.plan-actions {
|
|
534
|
+
width: 100%;
|
|
535
|
+
align-items: stretch;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.change-plan-btn,
|
|
539
|
+
.cancel-plan-btn,
|
|
540
|
+
.resume-plan-btn {
|
|
541
|
+
width: 100%;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.stripe-info {
|
|
545
|
+
grid-template-columns: 1fr;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
`;
|
|
549
|
+
}
|
|
550
|
+
connectedCallback() {
|
|
551
|
+
super.connectedCallback();
|
|
552
|
+
this.loadData();
|
|
553
|
+
}
|
|
554
|
+
async updated(changedProperties) {
|
|
555
|
+
await super.updated(changedProperties);
|
|
556
|
+
if (changedProperties.has('policy')) {
|
|
557
|
+
// Re-register handler if policy changed (including first time it's set)
|
|
558
|
+
//deno-lint-ignore no-explicit-any
|
|
559
|
+
const oldPolicy = changedProperties.get('policy');
|
|
560
|
+
if (oldPolicy)
|
|
561
|
+
oldPolicy.removeHandler(this.policyHandlerId);
|
|
562
|
+
if (this.policy) {
|
|
563
|
+
this.policy.addHandler(this.policyHandlerId, (key, _value) => {
|
|
564
|
+
if (key.includes('internal')) {
|
|
565
|
+
this.loadData();
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (changedProperties.has('policy') || changedProperties.has('customerId')) {
|
|
571
|
+
await this.loadData();
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
async loadData() {
|
|
575
|
+
this.loading = true;
|
|
576
|
+
this._internalHideCancel = false;
|
|
577
|
+
try {
|
|
578
|
+
this.currentPlan = await this.getCustomerPlan();
|
|
579
|
+
this.currentCustomer = await this.customer();
|
|
580
|
+
const defaultPlan = await this.policy.defaultPlan();
|
|
581
|
+
if (defaultPlan && this.currentPlan) {
|
|
582
|
+
this._internalHideCancel = defaultPlan.name === this.currentPlan.name;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
catch (e) {
|
|
586
|
+
console.error('Error loading current plan data:', e);
|
|
587
|
+
this.currentPlan = null;
|
|
588
|
+
this.currentCustomer = null;
|
|
589
|
+
}
|
|
590
|
+
finally {
|
|
591
|
+
this.loading = false;
|
|
592
|
+
}
|
|
593
|
+
this.requestUpdate();
|
|
594
|
+
}
|
|
595
|
+
handleChangePlan() {
|
|
596
|
+
this.showPricingTable = true;
|
|
597
|
+
}
|
|
598
|
+
handleClosePricingTable() {
|
|
599
|
+
this.showPricingTable = false;
|
|
600
|
+
}
|
|
601
|
+
async handlePlanSelected(e) {
|
|
602
|
+
// Re-emit the event for parent components to handle
|
|
603
|
+
const event = new CustomEvent('plan-change', {
|
|
604
|
+
detail: e.detail,
|
|
605
|
+
bubbles: true,
|
|
606
|
+
composed: true,
|
|
607
|
+
});
|
|
608
|
+
this.dispatchEvent(event);
|
|
609
|
+
this.showPricingTable = false;
|
|
610
|
+
await this.loadData();
|
|
611
|
+
}
|
|
612
|
+
handleCancelPlan() {
|
|
613
|
+
if (confirm("Are you sure you'd like to cancel your subscription?")) {
|
|
614
|
+
// Emit event for parent to handle the cancellation
|
|
615
|
+
const event = new CustomEvent('plan-cancel', {
|
|
616
|
+
detail: {
|
|
617
|
+
planName: this.currentPlan?.name,
|
|
618
|
+
customerId: this.customerId
|
|
619
|
+
},
|
|
620
|
+
bubbles: true,
|
|
621
|
+
composed: true,
|
|
622
|
+
});
|
|
623
|
+
this.dispatchEvent(event);
|
|
624
|
+
if (!this.denyPolicyChanges && this.policy) {
|
|
625
|
+
this.policy.wsSend(JSON.stringify({
|
|
626
|
+
type: 'cancel-subscription',
|
|
627
|
+
at_period_end: !this.cancelImmediately,
|
|
628
|
+
customer: this.currentCustomer,
|
|
629
|
+
}));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
handleResumeSubscription() {
|
|
634
|
+
if (confirm("Are you sure you'd like to resume your subscription?")) {
|
|
635
|
+
// Emit event for parent to handle resuming the subscription
|
|
636
|
+
const event = new CustomEvent('subscription-resume', {
|
|
637
|
+
detail: {
|
|
638
|
+
planName: this.currentPlan?.name,
|
|
639
|
+
customerId: this.customerId
|
|
640
|
+
},
|
|
641
|
+
bubbles: true,
|
|
642
|
+
composed: true,
|
|
643
|
+
});
|
|
644
|
+
this.dispatchEvent(event);
|
|
645
|
+
if (!this.denyPolicyChanges && this.policy && !this.cancelImmediately) {
|
|
646
|
+
this.policy.wsSend(JSON.stringify({
|
|
647
|
+
type: 'resume-stripe-subscription',
|
|
648
|
+
id: this.customerId,
|
|
649
|
+
}));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Pluralize a unit name if needed based on the value.
|
|
655
|
+
*/
|
|
656
|
+
pluralizeUnit(unit, value) {
|
|
657
|
+
const numValue = typeof value === 'string' ? parseFloat(value) : value;
|
|
658
|
+
if (numValue === 1)
|
|
659
|
+
return unit;
|
|
660
|
+
const nonPluralUnits = [
|
|
661
|
+
'GB', 'MB', 'KB', 'TB', 'MiB', 'GiB', 'KiB', 'TiB',
|
|
662
|
+
'ms', 'seconds', 'minutes', 'hours', 'days',
|
|
663
|
+
'storage', 'data', 'bandwidth'
|
|
664
|
+
];
|
|
665
|
+
if (nonPluralUnits.includes(unit)) {
|
|
666
|
+
return unit;
|
|
667
|
+
}
|
|
668
|
+
const irregularPlurals = {
|
|
669
|
+
'seat': 'seats',
|
|
670
|
+
'token': 'tokens',
|
|
671
|
+
'request': 'requests',
|
|
672
|
+
'call': 'calls',
|
|
673
|
+
'query': 'queries',
|
|
674
|
+
'credit': 'credits',
|
|
675
|
+
'user': 'users',
|
|
676
|
+
'member': 'members',
|
|
677
|
+
'item': 'items'
|
|
678
|
+
};
|
|
679
|
+
if (irregularPlurals[unit.toLowerCase()]) {
|
|
680
|
+
return irregularPlurals[unit.toLowerCase()];
|
|
681
|
+
}
|
|
682
|
+
if (!unit.endsWith('s')) {
|
|
683
|
+
return unit + 's';
|
|
684
|
+
}
|
|
685
|
+
return unit;
|
|
686
|
+
}
|
|
687
|
+
//deno-lint-ignore no-explicit-any
|
|
688
|
+
getCouponDetails(metadata) {
|
|
689
|
+
if (!metadata?.stripe_coupon_status || metadata.stripe_coupon_status !== 'applied') {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
const price = this.currentPlan?.price;
|
|
693
|
+
if (!price || price.amount === undefined)
|
|
694
|
+
return null;
|
|
695
|
+
const originalAmount = typeof price.amount === 'string' ? parseFloat(price.amount) : price.amount;
|
|
696
|
+
let discountedAmount = originalAmount;
|
|
697
|
+
let label = '';
|
|
698
|
+
if (metadata.stripe_coupon_percent_off) {
|
|
699
|
+
const percentOff = parseFloat(metadata.stripe_coupon_percent_off);
|
|
700
|
+
discountedAmount = originalAmount * (1 - percentOff / 100);
|
|
701
|
+
label = `${percentOff}% off`;
|
|
702
|
+
}
|
|
703
|
+
else if (metadata.stripe_coupon_amount_off) {
|
|
704
|
+
const amountOff = parseFloat(metadata.stripe_coupon_amount_off) / 100; // cents -> dollars
|
|
705
|
+
discountedAmount = Math.max(0, originalAmount - amountOff);
|
|
706
|
+
label = `$${amountOff.toFixed(2)} off`;
|
|
707
|
+
}
|
|
708
|
+
// Duration label
|
|
709
|
+
let durationLabel = '';
|
|
710
|
+
const duration = metadata.stripe_coupon_duration;
|
|
711
|
+
if (duration === 'once') {
|
|
712
|
+
durationLabel = 'First period only';
|
|
713
|
+
}
|
|
714
|
+
else if (duration === 'repeating' && metadata.stripe_coupon_duration_in_months) {
|
|
715
|
+
durationLabel = `First ${metadata.stripe_coupon_duration_in_months} months`;
|
|
716
|
+
}
|
|
717
|
+
// 'forever' = no duration label, it's just the price now
|
|
718
|
+
return {
|
|
719
|
+
hasDiscount: discountedAmount < originalAmount,
|
|
720
|
+
discountedAmount,
|
|
721
|
+
originalAmount,
|
|
722
|
+
label,
|
|
723
|
+
durationLabel,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Check if subscription status is active/valid (not terminated or never activated)
|
|
728
|
+
*/
|
|
729
|
+
//deno-lint-ignore no-explicit-any
|
|
730
|
+
hasActiveSubscription(metadata) {
|
|
731
|
+
if (!metadata?.stripe_subscription_id)
|
|
732
|
+
return false;
|
|
733
|
+
const status = metadata.stripe_subscription_status;
|
|
734
|
+
// Valid statuses: active, trialing, past_due (still active, just payment issue)
|
|
735
|
+
// Invalid: canceled, incomplete_expired, incomplete (never activated), unpaid
|
|
736
|
+
// TODO: remove incomplete - just here for weird testing state
|
|
737
|
+
return status && ['active', 'trialing', 'past_due', 'incomplete'].includes(status);
|
|
738
|
+
}
|
|
739
|
+
//deno-lint-ignore no-explicit-any
|
|
740
|
+
formatLimitValue(limit, creditName) {
|
|
741
|
+
if (!limit || limit.value === undefined)
|
|
742
|
+
return 'Unlimited';
|
|
743
|
+
const credit = this.getCredit(creditName);
|
|
744
|
+
const value = limit.value;
|
|
745
|
+
if (credit && credit.stof_units && credit.stof_units !== 'float' && credit.stof_units !== 'int') {
|
|
746
|
+
return `${value} ${credit.stof_units}`;
|
|
747
|
+
}
|
|
748
|
+
if (credit && credit.unit) {
|
|
749
|
+
const pluralizedUnit = this.pluralizeUnit(credit.unit, value);
|
|
750
|
+
return `${value} ${pluralizedUnit}`;
|
|
751
|
+
}
|
|
752
|
+
return `${value}`;
|
|
753
|
+
}
|
|
754
|
+
formatUsageValue(usage, creditName) {
|
|
755
|
+
const credit = this.getCredit(creditName);
|
|
756
|
+
if (credit && credit.stof_units && credit.stof_units !== 'float' && credit.stof_units !== 'int') {
|
|
757
|
+
return `${usage} ${credit.stof_units}`;
|
|
758
|
+
}
|
|
759
|
+
if (credit && credit.unit) {
|
|
760
|
+
const pluralizedUnit = this.pluralizeUnit(credit.unit, usage);
|
|
761
|
+
return `${usage} ${pluralizedUnit}`;
|
|
762
|
+
}
|
|
763
|
+
return `${usage}`;
|
|
764
|
+
}
|
|
765
|
+
//deno-lint-ignore no-explicit-any
|
|
766
|
+
getUsagePercentage(usage, limit) {
|
|
767
|
+
if (!limit || limit.value === undefined)
|
|
768
|
+
return 0;
|
|
769
|
+
const limitValue = typeof limit.value === 'string' ? parseFloat(limit.value) : limit.value;
|
|
770
|
+
return Math.min(100, (usage / limitValue) * 100);
|
|
771
|
+
}
|
|
772
|
+
getUsageClass(percentage) {
|
|
773
|
+
if (percentage >= 90)
|
|
774
|
+
return 'danger';
|
|
775
|
+
if (percentage >= 75)
|
|
776
|
+
return 'warning';
|
|
777
|
+
return '';
|
|
778
|
+
}
|
|
779
|
+
formatDate(timestamp) {
|
|
780
|
+
return new Date(timestamp).toLocaleDateString('en-US', {
|
|
781
|
+
year: 'numeric',
|
|
782
|
+
month: 'long',
|
|
783
|
+
day: 'numeric'
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
//deno-lint-ignore no-explicit-any
|
|
787
|
+
renderStripeInfo(metadata) {
|
|
788
|
+
if (!metadata || !metadata.stripe_subscription_id)
|
|
789
|
+
return null;
|
|
790
|
+
const status = metadata.stripe_subscription_status || 'unknown';
|
|
791
|
+
const periodStart = metadata.stripe_current_period_start;
|
|
792
|
+
const periodEnd = metadata.stripe_current_period_end;
|
|
793
|
+
const paymentType = metadata.stripe_payment_method_type;
|
|
794
|
+
const last4 = metadata.stripe_payment_method_last4;
|
|
795
|
+
const brand = metadata.stripe_payment_method_brand;
|
|
796
|
+
const cancelAtPeriodEnd = metadata.stripe_cancel_at_period_end;
|
|
797
|
+
return html `
|
|
798
|
+
<div class="stripe-section">
|
|
799
|
+
<h3 class="section-title">Subscription Details</h3>
|
|
800
|
+
<div class="stripe-info">
|
|
801
|
+
<div class="info-item">
|
|
802
|
+
<div class="info-label">Status</div>
|
|
803
|
+
<div class="info-value">
|
|
804
|
+
<span class="status-badge ${status}">${status}</span>
|
|
805
|
+
${cancelAtPeriodEnd ? html `<div style="font-size: 12px; margin-top: 4px; color: var(--limitr-text-secondary);">Cancels at period end</div>` : ''}
|
|
806
|
+
</div>
|
|
807
|
+
</div>
|
|
808
|
+
${periodStart ? html `
|
|
809
|
+
<div class="info-item">
|
|
810
|
+
<div class="info-label">Current Period</div>
|
|
811
|
+
<div class="info-value" style="font-size: 14px;">
|
|
812
|
+
${this.formatDate(periodStart)}<br/>
|
|
813
|
+
to ${this.formatDate(periodEnd)}
|
|
814
|
+
</div>
|
|
815
|
+
</div>
|
|
816
|
+
` : ''}
|
|
817
|
+
${paymentType && last4 ? html `
|
|
818
|
+
<div class="info-item">
|
|
819
|
+
<div class="info-label">Payment Method</div>
|
|
820
|
+
<div class="info-value">
|
|
821
|
+
${brand ? brand.charAt(0).toUpperCase() + brand.slice(1) : paymentType} •••• ${last4}
|
|
822
|
+
</div>
|
|
823
|
+
</div>
|
|
824
|
+
` : ''}
|
|
825
|
+
</div>
|
|
826
|
+
</div>
|
|
827
|
+
`;
|
|
828
|
+
}
|
|
829
|
+
//deno-lint-ignore no-explicit-any
|
|
830
|
+
renderUsage(plan, customer) {
|
|
831
|
+
if (this.hideUsage || !plan || !customer)
|
|
832
|
+
return null;
|
|
833
|
+
const entitlements = plan.entitlements || {};
|
|
834
|
+
const meters = customer.meters || {};
|
|
835
|
+
// Filter to visible entitlements with limits, usage, and the correct scope.
|
|
836
|
+
const customerType = customer.type;
|
|
837
|
+
//deno-lint-ignore no-explicit-any
|
|
838
|
+
const usageItems = Object.entries(entitlements).filter(([entName, ent]) => {
|
|
839
|
+
return !ent.hidden && ent.limit && meters[entName] !== undefined && (!ent.scope || ent.scope === customerType);
|
|
840
|
+
});
|
|
841
|
+
if (usageItems.length === 0)
|
|
842
|
+
return null;
|
|
843
|
+
return html `
|
|
844
|
+
<div class="usage-section">
|
|
845
|
+
<h3 class="section-title">Usage</h3>
|
|
846
|
+
${
|
|
847
|
+
//deno-lint-ignore no-explicit-any
|
|
848
|
+
usageItems.map(([entName, entitlement]) => {
|
|
849
|
+
const limit = entitlement.limit;
|
|
850
|
+
const usage = meters[entName]?.value || 0;
|
|
851
|
+
const percentage = this.getUsagePercentage(usage, limit);
|
|
852
|
+
const usageClass = this.getUsageClass(percentage);
|
|
853
|
+
return html `
|
|
854
|
+
<div class="usage-item">
|
|
855
|
+
<div class="usage-header">
|
|
856
|
+
<span class="usage-name">${entitlement.description || entName}</span>
|
|
857
|
+
<span class="usage-stats">
|
|
858
|
+
${this.formatUsageValue(usage, limit.credit)} / ${this.formatLimitValue(limit, limit.credit)}
|
|
859
|
+
</span>
|
|
860
|
+
</div>
|
|
861
|
+
<div class="usage-bar">
|
|
862
|
+
<div class="usage-bar-fill ${usageClass}" style="width: ${percentage}%"></div>
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
`;
|
|
866
|
+
})}
|
|
867
|
+
</div>
|
|
868
|
+
`;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Render invoices section if invoices are available in policy.
|
|
872
|
+
*/
|
|
873
|
+
renderInvoices() {
|
|
874
|
+
if (!this.policy || !this.customerId)
|
|
875
|
+
return null;
|
|
876
|
+
const policy = this.policyRecord;
|
|
877
|
+
if (!policy.invoices || !policy.invoices[this.customerId])
|
|
878
|
+
return null;
|
|
879
|
+
const responseObject = policy.invoices[this.customerId];
|
|
880
|
+
const invoices = responseObject?.data.invoices;
|
|
881
|
+
if (!invoices || !Array.isArray(invoices) || invoices.length === 0) {
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
return html `
|
|
885
|
+
<div class="invoices-section">
|
|
886
|
+
<h3 class="section-title">Recent Invoices</h3>
|
|
887
|
+
<div class="invoices-list">
|
|
888
|
+
${invoices.map((invoice) => html `
|
|
889
|
+
<div class="invoice-item">
|
|
890
|
+
<div class="invoice-info">
|
|
891
|
+
<div class="invoice-number">${invoice.number || invoice.id}</div>
|
|
892
|
+
<div class="invoice-date">${this.formatDate(invoice.created)}</div>
|
|
893
|
+
</div>
|
|
894
|
+
<div class="invoice-amount">
|
|
895
|
+
${this.formatCurrency(invoice.total, invoice.currency)}
|
|
896
|
+
</div>
|
|
897
|
+
<div class="invoice-status">
|
|
898
|
+
<span class="status-badge ${invoice.status}">${invoice.status}</span>
|
|
899
|
+
</div>
|
|
900
|
+
${invoice.invoice_pdf || invoice.hosted_invoice_url ? html `
|
|
901
|
+
<a
|
|
902
|
+
href="${invoice.invoice_pdf || invoice.hosted_invoice_url}"
|
|
903
|
+
target="_blank"
|
|
904
|
+
class="invoice-download"
|
|
905
|
+
>
|
|
906
|
+
Download
|
|
907
|
+
</a>
|
|
908
|
+
` : nothing}
|
|
909
|
+
</div>
|
|
910
|
+
`)}
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
`;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Format currency value
|
|
917
|
+
*/
|
|
918
|
+
formatCurrency(amountInCents, currency = 'usd') {
|
|
919
|
+
const amount = amountInCents / 100;
|
|
920
|
+
return new Intl.NumberFormat('en-US', {
|
|
921
|
+
style: 'currency',
|
|
922
|
+
currency: currency.toUpperCase(),
|
|
923
|
+
}).format(amount);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Render.
|
|
927
|
+
*/
|
|
928
|
+
render() {
|
|
929
|
+
if (this.loading) {
|
|
930
|
+
return html `<div class="loading">Loading plan information...</div>`;
|
|
931
|
+
}
|
|
932
|
+
if (!this.currentPlan) {
|
|
933
|
+
return html `
|
|
934
|
+
<div class="empty">
|
|
935
|
+
<div class="empty-title">No Plan Selected</div>
|
|
936
|
+
<p>You don't have an active plan yet.</p>
|
|
937
|
+
</div>
|
|
938
|
+
`;
|
|
939
|
+
}
|
|
940
|
+
const price = this.currentPlan.price;
|
|
941
|
+
const metadata = this.currentCustomer?.metadata || {};
|
|
942
|
+
const hasActiveSubscription = this.hasActiveSubscription(metadata);
|
|
943
|
+
const cancelAtPeriodEnd = metadata.stripe_cancel_at_period_end === true || metadata.stripe_cancel_at_period_end === 'true';
|
|
944
|
+
const coupon = this.getCouponDetails(metadata);
|
|
945
|
+
return html `
|
|
946
|
+
<div class="container">
|
|
947
|
+
<div class="plan-card">
|
|
948
|
+
<div class="plan-header">
|
|
949
|
+
<div class="plan-info">
|
|
950
|
+
<div class="plan-label">Current Plan</div>
|
|
951
|
+
<h2 class="plan-name">${this.currentPlan.label || this.currentPlan.name}</h2>
|
|
952
|
+
${price ? html `
|
|
953
|
+
<div class="plan-price">
|
|
954
|
+
${coupon?.hasDiscount ? html `
|
|
955
|
+
<span class="price-original">${price.prefix || ''}${typeof price.amount === 'number' ? price.amount.toFixed(2) : price.amount}</span>
|
|
956
|
+
` : ''}
|
|
957
|
+
${price.prefix || ''}
|
|
958
|
+
<span class="price-amount">${coupon?.hasDiscount ? coupon.discountedAmount.toFixed(2) : (typeof price.amount === 'number' ? price.amount.toFixed(2) : price.amount)}</span>
|
|
959
|
+
${price.suffix || ''}
|
|
960
|
+
</div>
|
|
961
|
+
${coupon?.hasDiscount ? html `
|
|
962
|
+
<div class="coupon-badge">
|
|
963
|
+
<span class="coupon-tag-icon">🏷️</span>
|
|
964
|
+
${metadata.stripe_coupon_name || metadata.stripe_coupon_code} — ${coupon.label}
|
|
965
|
+
</div>
|
|
966
|
+
${coupon.durationLabel ? html `
|
|
967
|
+
<div class="coupon-duration">${coupon.durationLabel}</div>
|
|
968
|
+
` : ''}
|
|
969
|
+
` : ''}
|
|
970
|
+
` : ''}
|
|
971
|
+
</div>
|
|
972
|
+
<div class="plan-actions">
|
|
973
|
+
<button class="change-plan-btn" @click=${this.handleChangePlan}>
|
|
974
|
+
Change Plan
|
|
975
|
+
</button>
|
|
976
|
+
${this.stripePortalUrl ? html `
|
|
977
|
+
<button class="change-plan-btn" @click=${() => globalThis.location.href = this.stripePortalUrl}>
|
|
978
|
+
Manage Billing
|
|
979
|
+
</button>
|
|
980
|
+
` : nothing}
|
|
981
|
+
${!this.hideCancel && !this._internalHideCancel && hasActiveSubscription ? html `
|
|
982
|
+
${cancelAtPeriodEnd ? html `
|
|
983
|
+
<button class="resume-plan-btn" @click=${this.handleResumeSubscription}>
|
|
984
|
+
Resume Subscription
|
|
985
|
+
</button>
|
|
986
|
+
` : html `
|
|
987
|
+
<button class="cancel-plan-btn" @click=${this.handleCancelPlan}>
|
|
988
|
+
Cancel Plan
|
|
989
|
+
</button>
|
|
990
|
+
`}
|
|
991
|
+
` : nothing}
|
|
992
|
+
</div>
|
|
993
|
+
</div>
|
|
994
|
+
|
|
995
|
+
${this.renderUsage(this.currentPlan, this.currentCustomer)}
|
|
996
|
+
${this.showStripeInfo && hasActiveSubscription ? this.renderStripeInfo(metadata) : ''}
|
|
997
|
+
${this.renderInvoices()}
|
|
998
|
+
</div>
|
|
999
|
+
</div>
|
|
1000
|
+
|
|
1001
|
+
${this.showPricingTable ? html `
|
|
1002
|
+
<div class="modal-overlay" @click=${this.handleClosePricingTable}>
|
|
1003
|
+
<div class="modal-content" @click=${(e) => e.stopPropagation()}>
|
|
1004
|
+
<div class="modal-header">
|
|
1005
|
+
<h2 class="modal-title">Select a Plan</h2>
|
|
1006
|
+
<button class="close-btn" @click=${this.handleClosePricingTable}>×</button>
|
|
1007
|
+
</div>
|
|
1008
|
+
<limitr-pricing-table
|
|
1009
|
+
.policy=${this.policy}
|
|
1010
|
+
.customerId=${this.customerId}
|
|
1011
|
+
.stripePortalUrl=${this.stripePortalUrl}
|
|
1012
|
+
?denyPolicyChanges=${this.denyPolicyChanges}
|
|
1013
|
+
theme=${this.theme}
|
|
1014
|
+
@plan-select=${this.handlePlanSelected}
|
|
1015
|
+
?requestStripeInvoices=${false}
|
|
1016
|
+
?requestStripePortalUrl=${false}
|
|
1017
|
+
?interactive=${true}
|
|
1018
|
+
></limitr-pricing-table>
|
|
1019
|
+
</div>
|
|
1020
|
+
</div>
|
|
1021
|
+
` : ''}
|
|
1022
|
+
`;
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
__decorate([
|
|
1026
|
+
property({ type: Boolean }),
|
|
1027
|
+
__metadata("design:type", Boolean)
|
|
1028
|
+
], LimitrCurrentPlan.prototype, "showStripeInfo", void 0);
|
|
1029
|
+
__decorate([
|
|
1030
|
+
property({ type: Boolean }),
|
|
1031
|
+
__metadata("design:type", Boolean)
|
|
1032
|
+
], LimitrCurrentPlan.prototype, "hideUsage", void 0);
|
|
1033
|
+
__decorate([
|
|
1034
|
+
property({ type: Boolean }),
|
|
1035
|
+
__metadata("design:type", Boolean)
|
|
1036
|
+
], LimitrCurrentPlan.prototype, "hideCancel", void 0);
|
|
1037
|
+
__decorate([
|
|
1038
|
+
property({ type: Boolean }),
|
|
1039
|
+
__metadata("design:type", Boolean)
|
|
1040
|
+
], LimitrCurrentPlan.prototype, "cancelImmediately", void 0);
|
|
1041
|
+
__decorate([
|
|
1042
|
+
property(),
|
|
1043
|
+
__metadata("design:type", String)
|
|
1044
|
+
], LimitrCurrentPlan.prototype, "theme", void 0);
|
|
1045
|
+
__decorate([
|
|
1046
|
+
property({ type: Boolean })
|
|
1047
|
+
/** When true, emit plan select events only, without setting customer plan on policy. */
|
|
1048
|
+
,
|
|
1049
|
+
__metadata("design:type", Boolean)
|
|
1050
|
+
], LimitrCurrentPlan.prototype, "denyPolicyChanges", void 0);
|
|
1051
|
+
__decorate([
|
|
1052
|
+
state()
|
|
1053
|
+
//deno-lint-ignore no-explicit-any
|
|
1054
|
+
,
|
|
1055
|
+
__metadata("design:type", Object)
|
|
1056
|
+
], LimitrCurrentPlan.prototype, "currentPlan", void 0);
|
|
1057
|
+
__decorate([
|
|
1058
|
+
state()
|
|
1059
|
+
//deno-lint-ignore no-explicit-any
|
|
1060
|
+
,
|
|
1061
|
+
__metadata("design:type", Object)
|
|
1062
|
+
], LimitrCurrentPlan.prototype, "currentCustomer", void 0);
|
|
1063
|
+
__decorate([
|
|
1064
|
+
state(),
|
|
1065
|
+
__metadata("design:type", Boolean)
|
|
1066
|
+
], LimitrCurrentPlan.prototype, "loading", void 0);
|
|
1067
|
+
__decorate([
|
|
1068
|
+
state(),
|
|
1069
|
+
__metadata("design:type", Boolean)
|
|
1070
|
+
], LimitrCurrentPlan.prototype, "showPricingTable", void 0);
|
|
1071
|
+
__decorate([
|
|
1072
|
+
state(),
|
|
1073
|
+
__metadata("design:type", Boolean)
|
|
1074
|
+
], LimitrCurrentPlan.prototype, "_internalHideCancel", void 0);
|
|
1075
|
+
LimitrCurrentPlan = __decorate([
|
|
1076
|
+
customElement('limitr-current-plan')
|
|
1077
|
+
], LimitrCurrentPlan);
|
|
1078
|
+
export { LimitrCurrentPlan };
|
|
1079
|
+
//# sourceMappingURL=current.js.map
|