@mundogamernetwork/shared-ui 1.1.4 → 1.1.9

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.
@@ -0,0 +1,211 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * MgCouponInput — standalone coupon input component.
4
+ *
5
+ * Can be used in two modes:
6
+ * 1. Wired mode: inject useMgCheckout context (call provideMgCheckout in parent)
7
+ * 2. Prop/event mode: pass couponInfo, couponLoading, couponError as props
8
+ * and listen to apply-coupon / remove-coupon events.
9
+ */
10
+ import { ref, computed } from "vue";
11
+ import { injectMgCheckout } from "../../composables/useMgCheckout";
12
+
13
+ const props = defineProps<{
14
+ /** Applied coupon object (prop mode). If null, shows the input. */
15
+ couponInfo?: { code: string; discount?: number; name?: string } | null;
16
+ loading?: boolean;
17
+ error?: string | null;
18
+ disabled?: boolean;
19
+ /** If true, the component manages its own state via injectMgCheckout */
20
+ autoWire?: boolean;
21
+ }>();
22
+
23
+ const emit = defineEmits<{
24
+ "apply-coupon": [code: string];
25
+ "remove-coupon": [];
26
+ }>();
27
+
28
+ // Try to auto-wire to parent checkout context
29
+ const checkout = injectMgCheckout();
30
+
31
+ const couponInput = ref("");
32
+ const _localLoading = ref(false);
33
+ const _localError = ref<string | null>(null);
34
+
35
+ const activeCoupon = computed(() =>
36
+ props.autoWire ? checkout?.couponInfo.value : props.couponInfo
37
+ );
38
+ const isLoading = computed(() =>
39
+ props.autoWire ? checkout?.couponLoading.value : (props.loading ?? _localLoading.value)
40
+ );
41
+ const couponError = computed(() =>
42
+ props.autoWire ? checkout?.couponError.value : (props.error ?? _localError.value)
43
+ );
44
+ const isDisabled = computed(() => props.disabled || isLoading.value);
45
+
46
+ async function handleApply() {
47
+ const code = couponInput.value.trim().toUpperCase();
48
+ if (!code || isDisabled.value) return;
49
+
50
+ if (props.autoWire && checkout) {
51
+ checkout.couponCode.value = code;
52
+ await checkout.applyCoupon(code);
53
+ } else {
54
+ emit("apply-coupon", code);
55
+ }
56
+ }
57
+
58
+ async function handleRemove() {
59
+ if (props.autoWire && checkout) {
60
+ await checkout.removeCoupon();
61
+ couponInput.value = "";
62
+ } else {
63
+ couponInput.value = "";
64
+ emit("remove-coupon");
65
+ }
66
+ }
67
+
68
+ // Clear input after coupon applied successfully
69
+ watch(
70
+ () => activeCoupon.value,
71
+ (v) => { if (v) couponInput.value = ""; }
72
+ );
73
+ </script>
74
+
75
+ <template>
76
+ <div class="mg-coupon">
77
+ <!-- Applied state -->
78
+ <div v-if="activeCoupon" class="mg-coupon__applied">
79
+ <span class="mg-coupon__badge">
80
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
81
+ <polyline points="20 6 9 17 4 12" />
82
+ </svg>
83
+ {{ activeCoupon.code }}
84
+ <span v-if="activeCoupon.discount">(-{{ activeCoupon.discount }}%)</span>
85
+ </span>
86
+ <button class="mg-coupon__remove" :disabled="isLoading" @click="handleRemove" :title="$t?.('checkout.coupon.remove') ?? 'Remove coupon'">
87
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
88
+ <line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
89
+ </svg>
90
+ </button>
91
+ </div>
92
+
93
+ <!-- Input state -->
94
+ <div v-else class="mg-coupon__form">
95
+ <input
96
+ v-model="couponInput"
97
+ type="text"
98
+ class="mg-coupon__input"
99
+ :class="{ 'mg-coupon__input--error': couponError }"
100
+ :placeholder="$t?.('checkout.coupon.placeholder') ?? 'Discount code'"
101
+ :disabled="isDisabled"
102
+ style="text-transform: uppercase"
103
+ @keyup.enter="handleApply"
104
+ />
105
+ <button
106
+ class="mg-coupon__btn"
107
+ :disabled="!couponInput.trim() || isDisabled"
108
+ @click="handleApply"
109
+ >
110
+ <span v-if="isLoading" class="mg-coupon__spinner" />
111
+ <template v-else>{{ $t?.('checkout.coupon.apply') ?? 'Apply' }}</template>
112
+ </button>
113
+ </div>
114
+
115
+ <p v-if="couponError" class="mg-coupon__error">{{ couponError }}</p>
116
+ </div>
117
+ </template>
118
+
119
+ <style lang="scss" scoped>
120
+ .mg-coupon {
121
+ width: 100%;
122
+
123
+ &__form {
124
+ display: flex;
125
+ gap: 8px;
126
+ }
127
+
128
+ &__input {
129
+ flex: 1;
130
+ padding: 10px 12px;
131
+ border: 1px solid var(--inactive, #808080);
132
+ background: transparent;
133
+ color: var(--active, #fff);
134
+ font-size: 14px;
135
+ outline: none;
136
+ transition: border-color 0.15s;
137
+
138
+ &::placeholder { color: var(--inactive, #9ca3af); text-transform: none; }
139
+ &:focus { border-color: var(--highlight-color, #4f46e5); }
140
+ &:disabled { opacity: 0.5; cursor: not-allowed; }
141
+ &--error { border-color: #ee3831; }
142
+ }
143
+
144
+ &__btn {
145
+ padding: 10px 18px;
146
+ background: var(--highlight-color, #4f46e5);
147
+ color: #fff;
148
+ border: none;
149
+ font-size: 14px;
150
+ font-weight: 600;
151
+ cursor: pointer;
152
+ white-space: nowrap;
153
+ transition: opacity 0.15s;
154
+
155
+ &:disabled { opacity: 0.4; cursor: not-allowed; }
156
+ &:hover:not(:disabled) { opacity: 0.85; }
157
+ }
158
+
159
+ &__applied {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: 8px;
163
+ }
164
+
165
+ &__badge {
166
+ display: inline-flex;
167
+ align-items: center;
168
+ gap: 5px;
169
+ padding: 6px 12px;
170
+ background: rgba(34, 197, 94, 0.12);
171
+ color: #22c55e;
172
+ font-size: 13px;
173
+ font-weight: 600;
174
+ border: 1px solid rgba(34, 197, 94, 0.3);
175
+
176
+ svg { flex-shrink: 0; }
177
+ }
178
+
179
+ &__remove {
180
+ background: none;
181
+ border: none;
182
+ color: var(--inactive, #6b7280);
183
+ cursor: pointer;
184
+ padding: 4px;
185
+ display: flex;
186
+ align-items: center;
187
+ transition: color 0.15s;
188
+
189
+ &:hover { color: #ee3831; }
190
+ &:disabled { opacity: 0.4; cursor: not-allowed; }
191
+ }
192
+
193
+ &__error {
194
+ color: #ee3831;
195
+ font-size: 12px;
196
+ margin-top: 4px;
197
+ }
198
+
199
+ &__spinner {
200
+ display: inline-block;
201
+ width: 14px;
202
+ height: 14px;
203
+ border: 2px solid rgba(255,255,255,0.3);
204
+ border-top-color: #fff;
205
+ border-radius: 50%;
206
+ animation: mg-coupon-spin 0.6s linear infinite;
207
+ }
208
+ }
209
+
210
+ @keyframes mg-coupon-spin { to { transform: rotate(360deg); } }
211
+ </style>
@@ -41,7 +41,6 @@ function handleInput(e: Event) {
41
41
  width: 100%;
42
42
  padding: 16px;
43
43
  border: 1px solid #f59e0b;
44
- border-radius: 8px;
45
44
  background: rgba(245, 158, 11, 0.05);
46
45
 
47
46
  &__info {
@@ -76,7 +75,6 @@ function handleInput(e: Event) {
76
75
  width: 100%;
77
76
  padding: 10px 12px;
78
77
  border: 1px solid var(--inactive, #d1d5db);
79
- border-radius: 6px;
80
78
  font-size: 14px;
81
79
  background: var(--body-bg-card, #fff);
82
80
  color: var(--active, #111827);
@@ -1,12 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import { onMounted, watch } from "vue";
3
3
  import { usePaymentMethods } from "../../composables/usePaymentMethods";
4
+ import MgPaymentMethods from "../ui/MgPaymentMethods.vue";
4
5
  import type { AxiosInstance } from "axios";
5
6
 
6
7
  const props = defineProps<{
7
8
  httpService: AxiosInstance;
8
9
  context?: "checkout" | "subscription";
9
10
  modelValue?: string;
11
+ theme?: string;
10
12
  }>();
11
13
 
12
14
  const emit = defineEmits<{
@@ -42,18 +44,21 @@ function select(name: string) {
42
44
  selectedMethod.value = name.toLowerCase();
43
45
  }
44
46
 
45
- function getLogoSrc(name: string): string {
46
- const key = name.toLowerCase();
47
- const logos: Record<string, string> = {
48
- paypal: "/imgs/payments/paypal.svg",
49
- stripe: "/imgs/payments/stripe.svg",
50
- mercadopago: "/imgs/payments/pix.svg",
51
- };
52
- return logos[key] ?? "/imgs/payments/default.svg";
47
+ // Canonical numeric ids consumed by <MgPaymentMethods> inline SVG logos.
48
+ // PayPal = 2, Stripe = 3 (matches api-main + useConfirmation).
49
+ function getMethodId(m: { id: number; name: string }): number {
50
+ const key = name2key(m.name);
51
+ if (key === "paypal") return 2;
52
+ if (key === "stripe") return 3;
53
+ return m.id;
54
+ }
55
+
56
+ function name2key(name: string): string {
57
+ return (name ?? "").toLowerCase();
53
58
  }
54
59
 
55
60
  function getLabel(name: string): string {
56
- const key = name.toLowerCase();
61
+ const key = name2key(name);
57
62
  const labels: Record<string, string> = {
58
63
  paypal: "PayPal",
59
64
  stripe: "Stripe",
@@ -82,11 +87,9 @@ function getLabel(name: string): string {
82
87
  :class="{ 'mg-payment-method--active': selectedMethod === method.name.toLowerCase() }"
83
88
  @click="select(method.name)"
84
89
  >
85
- <img
86
- :src="getLogoSrc(method.name)"
87
- :alt="getLabel(method.name)"
88
- class="mg-payment-method__logo"
89
- />
90
+ <span class="mg-payment-method__logo">
91
+ <MgPaymentMethods :method="getMethodId(method)" :theme="theme" />
92
+ </span>
90
93
  <span class="mg-payment-method__label">{{ getLabel(method.name) }}</span>
91
94
  </button>
92
95
  </div>
@@ -119,7 +122,6 @@ function getLabel(name: string): string {
119
122
  gap: 8px;
120
123
  padding: 12px 16px;
121
124
  border: 2px solid var(--inactive, #e5e7eb);
122
- border-radius: 8px;
123
125
  background: transparent;
124
126
  cursor: pointer;
125
127
  transition: all 0.2s ease;
@@ -151,7 +153,6 @@ function getLabel(name: string): string {
151
153
 
152
154
  .skeleton-block {
153
155
  background: var(--inactive, #e5e7eb);
154
- border-radius: 8px;
155
156
  animation: pulse 1.5s ease-in-out infinite;
156
157
  }
157
158
 
@@ -143,7 +143,6 @@ onUnmounted(() => {
143
143
  align-items: center;
144
144
  padding: 24px;
145
145
  border: 1px solid var(--inactive, #e5e7eb);
146
- border-radius: 12px;
147
146
  background: var(--body-bg-card, #fff);
148
147
 
149
148
  &__header {
@@ -191,7 +190,6 @@ onUnmounted(() => {
191
190
  gap: 8px;
192
191
  padding: 10px 20px;
193
192
  border: 1px solid #4f46e5;
194
- border-radius: 8px;
195
193
  background: transparent;
196
194
  color: #4f46e5;
197
195
  font-size: 14px;