@tapni/auth 1.0.72 → 1.0.74

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.
@@ -36,57 +36,177 @@
36
36
  <p class="center-text">{{ssoLang[appLanguage].billing_p }}</p>
37
37
 
38
38
  <div class="full-top">
39
- <!-- No subscriptions message -->
40
- <div v-if="subscriptions.length === 0" class="no-subscriptions center-text full-top">
41
- <p class="gray-text">There are no active subscriptions at this point.</p>
42
- </div>
39
+ <section class="billing-section">
40
+ <div class="section-heading">
41
+ <h3 class="section-title">Subscriptions</h3>
42
+ </div>
43
43
 
44
- <!-- Subscriptions list -->
45
- <div v-else>
46
- <div v-for="sub in subscriptions" :key="sub.subscriptionId" class="subscription-card half-bottom">
47
- <div class="subscription-header">
48
- <div class="subscription-info">
49
- <h3 class="subscription-name">{{ sub.subscriptionName }}</h3>
50
- <span class="subscription-status" :class="getStatusClass(sub.status)">
51
- {{ getStatusText(sub.status) }}
52
- </span>
44
+ <div v-if="subscriptions.length === 0" class="no-subscriptions center-text full-top">
45
+ <p class="gray-text">There are no active subscriptions at this point.</p>
46
+ </div>
47
+
48
+ <div v-else>
49
+ <div v-for="sub in subscriptions" :key="sub.subscriptionId" class="subscription-card half-bottom">
50
+ <div class="subscription-header">
51
+ <div class="subscription-info">
52
+ <h3 class="subscription-name">{{ sub.subscriptionName }}</h3>
53
+ <span class="subscription-status" :class="getStatusClass(sub.status)">
54
+ {{ getStatusText(sub.status) }}
55
+ </span>
56
+ </div>
53
57
  </div>
54
- </div>
55
58
 
56
- <div class="subscription-details">
57
- <div class="detail-row">
58
- <span class="detail-label">Amount:</span>
59
- <span class="detail-value">{{ formatCurrency(sub.amount, sub.currency) }}</span>
59
+ <div class="subscription-details">
60
+ <div class="detail-row">
61
+ <span class="detail-label">Amount:</span>
62
+ <span class="detail-value">{{ formatCurrency(sub.amount, sub.currency) }}</span>
63
+ </div>
64
+ <div class="detail-row">
65
+ <span class="detail-label">Billing:</span>
66
+ <span class="detail-value">{{ formatInterval(sub.interval) }}</span>
67
+ </div>
68
+ <div class="detail-row">
69
+ <span class="detail-label">Licenses:</span>
70
+ <span class="detail-value">{{ sub.licenses }}</span>
71
+ </div>
72
+ <div v-if="sub.isTrial" class="detail-row">
73
+ <span class="detail-label">Trial Ends:</span>
74
+ <span class="detail-value">{{ formatDate(sub.trialEnd) }}</span>
75
+ </div>
76
+ <div v-else-if="sub.endDate" class="detail-row">
77
+ <span class="detail-label">Next Billing:</span>
78
+ <span class="detail-value">{{ formatDate(sub.endDate) }}</span>
79
+ </div>
60
80
  </div>
61
- <div class="detail-row">
62
- <span class="detail-label">Billing:</span>
63
- <span class="detail-value">{{ formatInterval(sub.interval) }}</span>
81
+
82
+ <div class="subscription-actions">
83
+ <button
84
+ v-if="sub.status === 'canceled' || sub.status === 'expired'"
85
+ @click="resubscribe(sub)"
86
+ class="resubscribe-button"
87
+ :disabled="loading"
88
+ >
89
+ {{ loading ? 'Processing...' : 'Re-Subscribe' }}
90
+ </button>
91
+ <button
92
+ v-if="sub.status === 'past_due' || sub.status === 'unpaid'"
93
+ @click="retryPayment(sub)"
94
+ class="pay-now-button"
95
+ :disabled="loading"
96
+ >
97
+ {{ loading ? 'Processing...' : 'Pay Now' }}
98
+ </button>
99
+ <button
100
+ v-if="sub.status !== 'canceled' && sub.status !== 'expired' && sub.status !== 'past_due' && sub.status !== 'unpaid'"
101
+ @click="openCancelModal(sub)"
102
+ class="cancel-button"
103
+ :disabled="loading"
104
+ >
105
+ {{ loading ? 'Processing...' : 'Cancel Subscription' }}
106
+ </button>
64
107
  </div>
65
- <div class="detail-row">
66
- <span class="detail-label">Licenses:</span>
67
- <span class="detail-value">{{ sub.licenses }}</span>
108
+ </div>
109
+ </div>
110
+ </section>
111
+
112
+ <section class="billing-section orders-section">
113
+ <div class="section-heading">
114
+ <h3 class="section-title">Orders</h3>
115
+ <span v-if="orders.length" class="section-caption">{{ orders.length }} found</span>
116
+ </div>
117
+
118
+ <div v-if="ordersLoading" class="no-subscriptions center-text full-top">
119
+ <p class="gray-text">Loading your orders...</p>
120
+ </div>
121
+
122
+ <div v-else-if="orders.length === 0" class="no-subscriptions center-text full-top">
123
+ <p class="gray-text">No orders found for this account yet.</p>
124
+ </div>
125
+
126
+ <div v-else>
127
+ <div v-for="order in orders" :key="order.id" class="subscription-card order-card half-bottom">
128
+ <div class="subscription-header order-header">
129
+ <div class="subscription-info">
130
+ <h3 class="subscription-name">{{ order.orderNumber || order.orderId }}</h3>
131
+ <div class="order-meta">
132
+ <span class="order-date">{{ formatDateValue(order.createdAt) }}</span>
133
+ <span class="order-source">{{ formatSource(order.source) }}</span>
134
+ </div>
135
+ </div>
136
+
137
+ <div class="order-badges">
138
+ <span class="subscription-status" :class="getOrderStateClass(order.status)">
139
+ {{ formatOrderState(order.status, order.isDraft, order.canceled) }}
140
+ </span>
141
+ <span v-if="order.paymentStatus" class="subscription-status" :class="getPaymentStatusClass(order.paymentStatus)">
142
+ {{ formatPaymentStatus(order.paymentStatus) }}
143
+ </span>
144
+ <span v-if="order.fulfillmentStatus" class="subscription-status" :class="getFulfillmentStatusClass(order.fulfillmentStatus)">
145
+ {{ formatFulfillmentStatus(order.fulfillmentStatus) }}
146
+ </span>
147
+ </div>
68
148
  </div>
69
- <div v-if="sub.isTrial" class="detail-row">
70
- <span class="detail-label">Trial Ends:</span>
71
- <span class="detail-value">{{ formatDate(sub.trialEnd) }}</span>
149
+
150
+ <div class="subscription-details order-grid">
151
+ <div class="detail-row">
152
+ <span class="detail-label">Total:</span>
153
+ <span class="detail-value">{{ formatCurrency(order.total, order.currency) }}</span>
154
+ </div>
155
+ <div class="detail-row">
156
+ <span class="detail-label">Subtotal:</span>
157
+ <span class="detail-value">{{ formatCurrency(order.subtotal, order.currency) }}</span>
158
+ </div>
159
+ <div class="detail-row">
160
+ <span class="detail-label">Discount:</span>
161
+ <span class="detail-value">{{ formatSignedCurrency(order.discountAmount, order.currency) }}</span>
162
+ </div>
163
+ <div class="detail-row">
164
+ <span class="detail-label">Tax:</span>
165
+ <span class="detail-value">{{ formatCurrency(order.taxAmount, order.currency) }}</span>
166
+ </div>
167
+ <div class="detail-row">
168
+ <span class="detail-label">Shipping:</span>
169
+ <span class="detail-value">{{ order.shippingMethod || 'Not specified' }}</span>
170
+ </div>
171
+ <div class="detail-row">
172
+ <span class="detail-label">Invoice:</span>
173
+ <span class="detail-value">{{ order.invoiceStatus || 'Not available' }}</span>
174
+ </div>
72
175
  </div>
73
- <div v-else-if="sub.endDate" class="detail-row">
74
- <span class="detail-label">Next Billing:</span>
75
- <span class="detail-value">{{ formatDate(sub.endDate) }}</span>
176
+
177
+ <div v-if="order.items?.length" class="order-items">
178
+ <h4 class="subsection-title">Items</h4>
179
+ <div v-for="(item, index) in order.items" :key="`${order.id}-${index}`" class="order-item-row">
180
+ <span class="detail-label">{{ item.name || item.sku || 'Item' }} x{{ item.quantity }}</span>
181
+ <span class="detail-value">{{ formatCurrency(item.price * item.quantity, order.currency) }}</span>
182
+ </div>
183
+ </div>
184
+
185
+ <div class="order-addresses" v-if="order.billingAddress || order.shippingAddress">
186
+ <div v-if="order.billingAddress" class="address-card">
187
+ <h4 class="subsection-title">Billing Address</h4>
188
+ <p class="address-line">{{ formatAddress(order.billingAddress) }}</p>
189
+ </div>
190
+ <div v-if="order.shippingAddress" class="address-card">
191
+ <h4 class="subsection-title">Shipping Address</h4>
192
+ <p class="address-line">{{ formatAddress(order.shippingAddress) }}</p>
193
+ </div>
76
194
  </div>
77
- </div>
78
195
 
79
- <div class="subscription-actions" v-if="sub.status !== 'canceled'">
80
- <button
81
- @click="openCancelModal(sub)"
82
- class="cancel-button"
83
- :disabled="loading"
84
- >
85
- {{ loading ? 'Processing...' : 'Cancel Subscription' }}
86
- </button>
196
+ <div class="subscription-actions order-actions">
197
+ <a v-if="order.invoiceUrl" :href="order.invoiceUrl" target="_blank" rel="noopener noreferrer" class="resubscribe-button button-link">
198
+ View Invoice
199
+ </a>
200
+ <a v-if="order.designerUrl" :href="order.designerUrl" target="_blank" rel="noopener noreferrer" class="pay-now-button button-link">
201
+ Open Order
202
+ </a>
203
+ <a v-if="order.orderStatusUrl" :href="order.orderStatusUrl" target="_blank" rel="noopener noreferrer" class="cancel-button button-link">
204
+ Order Status
205
+ </a>
206
+ </div>
87
207
  </div>
88
208
  </div>
89
- </div>
209
+ </section>
90
210
  </div>
91
211
  </div>
92
212
 
@@ -136,6 +256,7 @@
136
256
  import AuthMixin from "../mixins/auth.mixin";
137
257
  import {EventBus} from "@/store/event-bus.js";
138
258
  import api from "@/services/Api.js";
259
+ import AuthService from "@/services/AuthService.js";
139
260
 
140
261
  export default {
141
262
  name: "AuthBilling",
@@ -150,6 +271,8 @@ export default {
150
271
  return {
151
272
  loading: false,
152
273
  subscriptions: [],
274
+ orders: [],
275
+ ordersLoading: false,
153
276
  showCancelModal: false,
154
277
  selectedSubscription: null,
155
278
  cancelFeedback: ''
@@ -157,13 +280,17 @@ export default {
157
280
  },
158
281
  async mounted() {
159
282
  if (!this.isLoggedIn) this.$router.push('/login');
160
- await this.getAccountSettings();
161
- this.loadSubscriptions();
283
+ await this.refreshBillingData();
162
284
  },
163
285
  methods: {
164
286
  close () {
165
287
  EventBus.$emit('ssoEvent', {name: 'toggleAuthModal', data: true})
166
288
  },
289
+ async refreshBillingData() {
290
+ await this.getAccountSettings();
291
+ this.loadSubscriptions();
292
+ await this.loadOrders();
293
+ },
167
294
  loadSubscriptions() {
168
295
  // Extract subscriptions from account.billing
169
296
  if (this.account.billing) {
@@ -178,6 +305,26 @@ export default {
178
305
  }
179
306
 
180
307
  this.subscriptions = subs;
308
+ return;
309
+ }
310
+
311
+ this.subscriptions = [];
312
+ },
313
+ async loadOrders() {
314
+ this.ordersLoading = true;
315
+
316
+ try {
317
+ const response = await AuthService.getAccountOrders({ limit: 25 });
318
+ this.orders = response?.data?.data || [];
319
+ } catch (error) {
320
+ console.error('Error loading account orders:', error);
321
+ this.orders = [];
322
+ EventBus.$emit('showToast', {
323
+ type: 'error',
324
+ message: error.response?.data?.error || 'Failed to load orders.'
325
+ });
326
+ } finally {
327
+ this.ordersLoading = false;
181
328
  }
182
329
  },
183
330
  formatCurrency(amount, currency) {
@@ -187,7 +334,11 @@ export default {
187
334
  'GBP': '£'
188
335
  };
189
336
  const symbol = currencySymbols[currency?.toUpperCase()] || currency || '';
190
- return `${symbol}${amount?.toFixed(2) || '0.00'}`;
337
+ return `${symbol}${Number(amount || 0).toFixed(2)}`;
338
+ },
339
+ formatSignedCurrency(amount, currency) {
340
+ const numericAmount = Number(amount || 0);
341
+ return numericAmount > 0 ? `-${this.formatCurrency(numericAmount, currency)}` : this.formatCurrency(numericAmount, currency);
191
342
  },
192
343
  formatInterval(interval) {
193
344
  return interval ? `per ${interval}` : '';
@@ -197,19 +348,71 @@ export default {
197
348
  const date = new Date(timestamp * 1000);
198
349
  return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
199
350
  },
351
+ formatDateValue(value) {
352
+ if (!value) return '';
353
+ const date = new Date(value);
354
+ if (Number.isNaN(date.getTime())) return '';
355
+ return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
356
+ },
357
+ formatSource(source) {
358
+ if (!source) return 'Unknown source';
359
+ if (source.includes('myshopify.com')) return 'Shopify';
360
+ if (source === 'tapstack.erp') return 'Tapstack';
361
+ return source;
362
+ },
200
363
  getStatusText(status) {
201
364
  const statusMap = {
202
365
  'trialing': 'Trial',
203
366
  'active': 'Active',
204
367
  'past_due': 'Past Due',
205
368
  'canceled': 'Canceled',
206
- 'unpaid': 'Unpaid'
369
+ 'unpaid': 'Unpaid',
370
+ 'expired': 'Expired'
207
371
  };
208
372
  return statusMap[status] || status;
209
373
  },
210
374
  getStatusClass(status) {
211
375
  return `status-${status}`;
212
376
  },
377
+ getOrderStateClass(status) {
378
+ return `status-${String(status || 'created').toLowerCase().replace(/_/g, '-')}`;
379
+ },
380
+ getPaymentStatusClass(status) {
381
+ return `status-${String(status || '').toLowerCase().replace(/_/g, '-')}`;
382
+ },
383
+ getFulfillmentStatusClass(status) {
384
+ return `status-${String(status || '').toLowerCase().replace(/_/g, '-')}`;
385
+ },
386
+ formatOrderState(status, isDraft, canceled) {
387
+ if (canceled) return 'Canceled';
388
+ if (isDraft) return 'Draft';
389
+ return this.formatStatusLabel(status || 'created');
390
+ },
391
+ formatPaymentStatus(status) {
392
+ return this.formatStatusLabel(status);
393
+ },
394
+ formatFulfillmentStatus(status) {
395
+ return this.formatStatusLabel(status);
396
+ },
397
+ formatStatusLabel(status) {
398
+ return String(status || '')
399
+ .replace(/_/g, ' ')
400
+ .replace(/\b\w/g, (char) => char.toUpperCase());
401
+ },
402
+ formatAddress(address) {
403
+ if (!address) return '';
404
+ return [
405
+ address.name,
406
+ address.email,
407
+ address.company,
408
+ address.line1,
409
+ address.line2,
410
+ [address.postalCode, address.city].filter(Boolean).join(' '),
411
+ address.state,
412
+ address.country,
413
+ address.phone
414
+ ].filter(Boolean).join(', ');
415
+ },
213
416
  openCancelModal(subscription) {
214
417
  this.selectedSubscription = subscription;
215
418
  this.showCancelModal = true;
@@ -257,8 +460,7 @@ export default {
257
460
  });
258
461
 
259
462
  // Refresh account settings to get updated billing info
260
- await this.getAccountSettings();
261
- this.loadSubscriptions();
463
+ await this.refreshBillingData();
262
464
 
263
465
  // Close modal
264
466
  this.closeCancelModal();
@@ -274,11 +476,394 @@ export default {
274
476
  } finally {
275
477
  this.loading = false;
276
478
  }
479
+ },
480
+ async resubscribe(sub) {
481
+ if (!sub?.subscriptionId) return;
482
+ if (sub.paymentGateway === 'revenuecat') {
483
+ EventBus.$emit('ssoEvent', {
484
+ name: 'resubscribe',
485
+ data: { subscriptionId: sub.subscriptionId, paymentGateway: sub.paymentGateway }
486
+ });
487
+ return;
488
+ }
489
+ this.loading = true;
490
+ try {
491
+ const response = await api(false, 'v2').post('checkout/resubscribe/' + sub.subscriptionId);
492
+ if (response.data.success) {
493
+ EventBus.$emit('showToast', { type: 'success', message: response.data.message || 'Subscription reactivated successfully' });
494
+ await this.refreshBillingData();
495
+ if (response.data.clientSecret) {
496
+ EventBus.$emit('ssoEvent', { name: 'resubscribePaymentRequired', data: { clientSecret: response.data.clientSecret } });
497
+ }
498
+ } else {
499
+ throw new Error(response.data.message || 'Failed to resubscribe');
500
+ }
501
+ } catch (error) {
502
+ EventBus.$emit('showToast', {
503
+ type: 'error',
504
+ message: error.response?.data?.error || error.response?.data?.message || 'Failed to resubscribe. Please try again.'
505
+ });
506
+ } finally {
507
+ this.loading = false;
508
+ }
509
+ },
510
+ async retryPayment(sub) {
511
+ if (!sub?.subscriptionId) return;
512
+ this.loading = true;
513
+ try {
514
+ const response = await api(false, 'v2').post('checkout/retry-payment/' + sub.subscriptionId);
515
+ const data = response.data;
516
+ if (data.paid) {
517
+ EventBus.$emit('showToast', { type: 'success', message: data.message || 'Payment successful' });
518
+ await this.refreshBillingData();
519
+ } else if (data.hostedInvoiceUrl) {
520
+ window.open(data.hostedInvoiceUrl, '_blank', 'noopener,noreferrer');
521
+ EventBus.$emit('showToast', { type: 'info', message: data.message || 'Please complete payment in the opened window.' });
522
+ } else {
523
+ throw new Error(data.message || 'Payment could not be processed');
524
+ }
525
+ } catch (error) {
526
+ EventBus.$emit('showToast', {
527
+ type: 'error',
528
+ message: error.response?.data?.error || error.response?.data?.message || 'Failed to retry payment. Please try again.'
529
+ });
530
+ } finally {
531
+ this.loading = false;
532
+ }
277
533
  }
278
534
  }
279
535
  };
280
536
  </script>
281
537
 
282
- <style>
538
+ <style scoped>
539
+ .billing-section {
540
+ margin-top: 24px;
541
+ }
542
+
543
+ .section-heading {
544
+ display: flex;
545
+ align-items: center;
546
+ justify-content: space-between;
547
+ margin-bottom: 14px;
548
+ }
549
+
550
+ .section-title {
551
+ margin: 0;
552
+ font-size: 18px;
553
+ font-weight: 600;
554
+ }
555
+
556
+ .section-caption {
557
+ color: #7d7d7d;
558
+ font-size: 13px;
559
+ }
560
+
561
+ .subscription-card {
562
+ background: #ffffff;
563
+ border: 1px solid #ececec;
564
+ border-radius: 18px;
565
+ padding: 18px;
566
+ }
567
+
568
+ .subscription-header {
569
+ display: flex;
570
+ justify-content: space-between;
571
+ gap: 16px;
572
+ align-items: flex-start;
573
+ }
574
+
575
+ .subscription-info {
576
+ display: flex;
577
+ flex-direction: column;
578
+ gap: 8px;
579
+ }
580
+
581
+ .subscription-name {
582
+ margin: 0;
583
+ font-size: 18px;
584
+ font-weight: 600;
585
+ color: #111111;
586
+ }
587
+
588
+ .subscription-status {
589
+ display: inline-flex;
590
+ align-items: center;
591
+ width: fit-content;
592
+ padding: 6px 10px;
593
+ border-radius: 999px;
594
+ font-size: 12px;
595
+ font-weight: 600;
596
+ background: #f1f5f9;
597
+ color: #334155;
598
+ }
599
+
600
+ .subscription-details {
601
+ margin-top: 16px;
602
+ display: grid;
603
+ gap: 10px;
604
+ }
605
+
606
+ .detail-row {
607
+ display: flex;
608
+ justify-content: space-between;
609
+ gap: 16px;
610
+ align-items: flex-start;
611
+ }
612
+
613
+ .detail-label {
614
+ color: #6b7280;
615
+ font-size: 14px;
616
+ }
617
+
618
+ .detail-value {
619
+ color: #111111;
620
+ font-size: 14px;
621
+ text-align: right;
622
+ word-break: break-word;
623
+ }
624
+
625
+ .subscription-actions {
626
+ margin-top: 18px;
627
+ display: flex;
628
+ flex-wrap: wrap;
629
+ gap: 10px;
630
+ }
631
+
632
+ .cancel-button,
633
+ .resubscribe-button,
634
+ .pay-now-button,
635
+ .button-secondary,
636
+ .button-danger {
637
+ border: 0;
638
+ border-radius: 999px;
639
+ padding: 10px 16px;
640
+ font-size: 14px;
641
+ font-weight: 600;
642
+ cursor: pointer;
643
+ transition: opacity 0.2s ease;
644
+ }
645
+
646
+ .cancel-button:disabled,
647
+ .resubscribe-button:disabled,
648
+ .pay-now-button:disabled,
649
+ .button-secondary:disabled,
650
+ .button-danger:disabled {
651
+ opacity: 0.6;
652
+ cursor: not-allowed;
653
+ }
654
+
655
+ .cancel-button,
656
+ .button-secondary {
657
+ background: #111111;
658
+ color: #ffffff;
659
+ }
660
+
661
+ .resubscribe-button {
662
+ background: #111111;
663
+ color: #ffffff;
664
+ }
665
+
666
+ .pay-now-button,
667
+ .button-danger {
668
+ background: #0f766e;
669
+ color: #ffffff;
670
+ }
671
+
672
+ .button-link {
673
+ text-decoration: none;
674
+ }
675
+
676
+ .order-card {
677
+ background: #fafafa;
678
+ }
283
679
 
680
+ .order-header {
681
+ align-items: center;
682
+ }
683
+
684
+ .order-meta {
685
+ display: flex;
686
+ flex-wrap: wrap;
687
+ gap: 8px;
688
+ color: #6b7280;
689
+ font-size: 13px;
690
+ }
691
+
692
+ .order-source {
693
+ text-transform: capitalize;
694
+ }
695
+
696
+ .order-badges {
697
+ display: flex;
698
+ flex-wrap: wrap;
699
+ justify-content: flex-end;
700
+ gap: 8px;
701
+ }
702
+
703
+ .order-grid {
704
+ grid-template-columns: repeat(2, minmax(0, 1fr));
705
+ }
706
+
707
+ .order-items,
708
+ .order-addresses {
709
+ margin-top: 18px;
710
+ }
711
+
712
+ .subsection-title {
713
+ margin: 0 0 10px 0;
714
+ font-size: 14px;
715
+ font-weight: 600;
716
+ color: #111111;
717
+ }
718
+
719
+ .order-item-row {
720
+ display: flex;
721
+ justify-content: space-between;
722
+ gap: 16px;
723
+ padding: 8px 0;
724
+ border-top: 1px solid #ececec;
725
+ }
726
+
727
+ .order-item-row:first-of-type {
728
+ border-top: 0;
729
+ padding-top: 0;
730
+ }
731
+
732
+ .order-addresses {
733
+ display: grid;
734
+ grid-template-columns: repeat(2, minmax(0, 1fr));
735
+ gap: 14px;
736
+ }
737
+
738
+ .address-card {
739
+ background: #ffffff;
740
+ border: 1px solid #ececec;
741
+ border-radius: 14px;
742
+ padding: 14px;
743
+ }
744
+
745
+ .address-line {
746
+ margin: 0;
747
+ color: #374151;
748
+ font-size: 14px;
749
+ line-height: 1.5;
750
+ word-break: break-word;
751
+ }
752
+
753
+ .modal-overlay {
754
+ position: fixed;
755
+ inset: 0;
756
+ background: rgba(17, 17, 17, 0.5);
757
+ display: flex;
758
+ align-items: center;
759
+ justify-content: center;
760
+ z-index: 20;
761
+ padding: 20px;
762
+ }
763
+
764
+ .modal-content {
765
+ width: min(100%, 520px);
766
+ background: #ffffff;
767
+ border-radius: 20px;
768
+ padding: 20px;
769
+ }
770
+
771
+ .modal-header,
772
+ .modal-footer {
773
+ display: flex;
774
+ align-items: center;
775
+ justify-content: space-between;
776
+ gap: 12px;
777
+ }
778
+
779
+ .modal-header {
780
+ margin-bottom: 16px;
781
+ }
782
+
783
+ .modal-body {
784
+ display: grid;
785
+ gap: 16px;
786
+ }
787
+
788
+ .modal-text,
789
+ .feedback-label {
790
+ color: #374151;
791
+ font-size: 14px;
792
+ }
793
+
794
+ .feedback-textarea {
795
+ width: 100%;
796
+ border: 1px solid #d1d5db;
797
+ border-radius: 14px;
798
+ padding: 12px;
799
+ font: inherit;
800
+ resize: vertical;
801
+ min-height: 110px;
802
+ }
803
+
804
+ .close-button {
805
+ background: transparent;
806
+ border: 0;
807
+ font-size: 26px;
808
+ line-height: 1;
809
+ cursor: pointer;
810
+ }
811
+
812
+ .status-active,
813
+ .status-paid,
814
+ .status-success,
815
+ .status-created,
816
+ .status-submitted,
817
+ .status-fulfilled {
818
+ background: #dcfce7;
819
+ color: #166534;
820
+ }
821
+
822
+ .status-trialing,
823
+ .status-open,
824
+ .status-unfulfilled,
825
+ .status-processing,
826
+ .status-draft {
827
+ background: #fef3c7;
828
+ color: #92400e;
829
+ }
830
+
831
+ .status-canceled,
832
+ .status-cancelled,
833
+ .status-expired,
834
+ .status-unpaid,
835
+ .status-past-due,
836
+ .status-void,
837
+ .status-failure,
838
+ .status-failed {
839
+ background: #fee2e2;
840
+ color: #991b1b;
841
+ }
842
+
843
+ @media (max-width: 720px) {
844
+ .subscription-header,
845
+ .modal-header,
846
+ .modal-footer {
847
+ flex-direction: column;
848
+ align-items: stretch;
849
+ }
850
+
851
+ .order-badges {
852
+ justify-content: flex-start;
853
+ }
854
+
855
+ .order-grid,
856
+ .order-addresses {
857
+ grid-template-columns: 1fr;
858
+ }
859
+
860
+ .detail-row,
861
+ .order-item-row {
862
+ flex-direction: column;
863
+ }
864
+
865
+ .detail-value {
866
+ text-align: left;
867
+ }
868
+ }
284
869
  </style>