@apolopay-sdk/ui 1.0.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 +54 -0
- package/dist/assets/icon_error.d.ts +1 -0
- package/dist/assets/icon_error.js +1 -0
- package/dist/assets/logo_apolo.d.ts +1 -0
- package/dist/assets/logo_apolo.js +1 -0
- package/dist/components/payment-modal.d.ts +48 -0
- package/dist/components/payment-modal.js +668 -0
- package/dist/components/payment-timer.d.ts +18 -0
- package/dist/components/payment-timer.js +84 -0
- package/dist/components/trigger-button.d.ts +16 -0
- package/dist/components/trigger-button.js +151 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/payment-button.d.ts +51 -0
- package/dist/payment-button.js +371 -0
- package/dist/styles/modal-base.d.ts +1 -0
- package/dist/styles/modal-base.js +75 -0
- package/dist/styles/qr-base.d.ts +1 -0
- package/dist/styles/qr-base.js +48 -0
- package/dist/styles/shared-styles.d.ts +1 -0
- package/dist/styles/shared-styles.js +28 -0
- package/dist/styles/spinner-styles.d.ts +1 -0
- package/dist/styles/spinner-styles.js +29 -0
- package/dist/styles/text-field-base.d.ts +1 -0
- package/dist/styles/text-field-base.js +44 -0
- package/dist/utils/image_error.d.ts +1 -0
- package/dist/utils/image_error.js +5 -0
- package/package.json +34 -0
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, html, css } from 'lit';
|
|
8
|
+
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
9
|
+
import { I18n, ModalStep } from '@apolopay-sdk/core';
|
|
10
|
+
import { modalBaseStyles } from '../styles/modal-base';
|
|
11
|
+
import { sharedStyles } from '../styles/shared-styles';
|
|
12
|
+
import { textFieldBaseStyles } from '../styles/text-field-base';
|
|
13
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
14
|
+
import { logoApolo } from '../assets/logo_apolo';
|
|
15
|
+
import { qrBaseStyles } from '../styles/qr-base';
|
|
16
|
+
import { handleImageError } from '../utils/image_error';
|
|
17
|
+
import { spinnerStyles } from '../styles/spinner-styles';
|
|
18
|
+
import './payment-timer.js';
|
|
19
|
+
let PaymentModal = class PaymentModal extends LitElement {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(...arguments);
|
|
22
|
+
// --- Props Received from Parent ---
|
|
23
|
+
this.isOpen = false;
|
|
24
|
+
this.barrierDismissible = false;
|
|
25
|
+
this.lang = 'es';
|
|
26
|
+
this.productTitle = '';
|
|
27
|
+
this.currentStep = ModalStep.SELECT_ASSET;
|
|
28
|
+
this.status = 'idle';
|
|
29
|
+
this.error = null;
|
|
30
|
+
this.isLoadingData = true; // For initial asset/network load
|
|
31
|
+
this.assets = [];
|
|
32
|
+
this.selectedAsset = null;
|
|
33
|
+
this.selectedNetwork = null;
|
|
34
|
+
this.qrCodeUrl = null;
|
|
35
|
+
this.paymentAddress = null;
|
|
36
|
+
this.amount = 0;
|
|
37
|
+
this.email = '';
|
|
38
|
+
this.qrCodeExpiresAt = null;
|
|
39
|
+
this.paymentUrl = null;
|
|
40
|
+
this.isAddressCopied = false;
|
|
41
|
+
// Handle clicks potentially on the backdrop
|
|
42
|
+
this.handleBackdropClick = (event) => {
|
|
43
|
+
if (event.target !== this.dialogElement)
|
|
44
|
+
return;
|
|
45
|
+
// Prevent closing if already animating closed
|
|
46
|
+
if (this.dialogElement.classList.contains('closing'))
|
|
47
|
+
return;
|
|
48
|
+
if (!this.barrierDismissible)
|
|
49
|
+
return;
|
|
50
|
+
const rect = this.dialogElement.getBoundingClientRect();
|
|
51
|
+
const clickedOutside = (event.clientY < rect.top || event.clientY > rect.bottom ||
|
|
52
|
+
event.clientX < rect.left || event.clientX > rect.right);
|
|
53
|
+
if (clickedOutside) {
|
|
54
|
+
this.requestClose();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
disconnectedCallback() {
|
|
59
|
+
super.disconnectedCallback();
|
|
60
|
+
// 🛡️ SEGURIDAD CRÍTICA:
|
|
61
|
+
// Si el componente se desmonta del DOM mientras el diálogo está abierto,
|
|
62
|
+
// forzamos el cierre nativo inmediatamente para eliminar el backdrop.
|
|
63
|
+
const dialog = this.dialogElement;
|
|
64
|
+
if (dialog && dialog.open) {
|
|
65
|
+
dialog.close();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// --- Lifecycle: Manage Dialog State ---
|
|
69
|
+
async updated(changedProperties) {
|
|
70
|
+
super.updated(changedProperties);
|
|
71
|
+
// Wait for Lit's rendering cycle to complete
|
|
72
|
+
await this.updateComplete;
|
|
73
|
+
// Timer is now managed by <payment-timer> component
|
|
74
|
+
if (changedProperties.has('isOpen')) {
|
|
75
|
+
const dialog = this.dialogElement;
|
|
76
|
+
if (!dialog)
|
|
77
|
+
return; // Guard clause
|
|
78
|
+
if (this.isOpen) {
|
|
79
|
+
// --- Opening ---
|
|
80
|
+
dialog.classList.remove('closing'); // Remove closing class if present
|
|
81
|
+
if (!dialog.open) {
|
|
82
|
+
dialog.showModal(); // Use showModal() for true modal behavior
|
|
83
|
+
}
|
|
84
|
+
dialog.addEventListener('click', this.handleBackdropClick); // Add backdrop listener
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// --- LÓGICA DE CIERRE MEJORADA ---
|
|
88
|
+
dialog.removeEventListener('click', this.handleBackdropClick);
|
|
89
|
+
// Si ya está cerrado, no hacemos nada
|
|
90
|
+
if (!dialog.open)
|
|
91
|
+
return;
|
|
92
|
+
dialog.classList.add('closing');
|
|
93
|
+
const onAnimationEnd = (e) => {
|
|
94
|
+
// 🛡️ FILTRO: Asegurarse de que el evento viene del dialog y no de un hijo (spinner, etc)
|
|
95
|
+
if (e.target === dialog) {
|
|
96
|
+
this.closeDialogFinal(dialog, onAnimationEnd);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
dialog.addEventListener('animationend', onAnimationEnd);
|
|
100
|
+
// 🛡️ TIMEOUT DE SEGURIDAD REDUCIDO:
|
|
101
|
+
// Si la animación falla o el navegador se congela, forzamos cierre en 200ms
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
if (dialog.open)
|
|
104
|
+
this.closeDialogFinal(dialog, onAnimationEnd);
|
|
105
|
+
}, 200);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Helper actualizado
|
|
110
|
+
closeDialogFinal(dialog, listener) {
|
|
111
|
+
dialog.removeEventListener('animationend', listener);
|
|
112
|
+
dialog.classList.remove('closing');
|
|
113
|
+
// Verificamos de nuevo si sigue abierto antes de cerrar
|
|
114
|
+
if (dialog.open)
|
|
115
|
+
dialog.close();
|
|
116
|
+
}
|
|
117
|
+
// --- Event Dispatchers (Emit events to parent) ---
|
|
118
|
+
// Request to close the modal (triggered by X, backdrop, Escape)
|
|
119
|
+
requestClose() {
|
|
120
|
+
this.dispatchEvent(new CustomEvent('closeRequest'));
|
|
121
|
+
}
|
|
122
|
+
// Handle the native 'close' event (fired by Escape key)
|
|
123
|
+
handleDialogNativeClose(event) {
|
|
124
|
+
event.preventDefault(); // Prevent the default immediate close
|
|
125
|
+
this.requestClose(); // Trigger our animated close flow
|
|
126
|
+
}
|
|
127
|
+
handleTimerExpired() {
|
|
128
|
+
this.status = 'error';
|
|
129
|
+
this.error = {
|
|
130
|
+
code: 'PAYMENT_TIMEOUT',
|
|
131
|
+
message: I18n.t.errors.timeout
|
|
132
|
+
};
|
|
133
|
+
this.changeStep(ModalStep.RESULT);
|
|
134
|
+
this.dispatchEvent(new CustomEvent('expired', { detail: { error: this.error } }));
|
|
135
|
+
}
|
|
136
|
+
// Emit event when a asset is selected
|
|
137
|
+
selectAsset(assetId) {
|
|
138
|
+
this.dispatchEvent(new CustomEvent('assetSelect', { detail: { assetId } }));
|
|
139
|
+
}
|
|
140
|
+
// Emit event when a network is selected
|
|
141
|
+
selectNetwork(networkId) {
|
|
142
|
+
this.dispatchEvent(new CustomEvent('networkSelect', { detail: { networkId } }));
|
|
143
|
+
}
|
|
144
|
+
// Emit event to request changing step (for "Back" buttons)
|
|
145
|
+
changeStep(step, e) {
|
|
146
|
+
e?.stopPropagation(); // Prevent event bubbling if from a button click
|
|
147
|
+
this.dispatchEvent(new CustomEvent('changeStep', { detail: step }));
|
|
148
|
+
}
|
|
149
|
+
copyAddress(event) {
|
|
150
|
+
if (!this.paymentAddress)
|
|
151
|
+
return;
|
|
152
|
+
event.stopPropagation();
|
|
153
|
+
navigator.clipboard.writeText(this.paymentAddress);
|
|
154
|
+
this.isAddressCopied = true;
|
|
155
|
+
setTimeout(() => this.isAddressCopied = false, 2000);
|
|
156
|
+
}
|
|
157
|
+
handlePayFromDevice() {
|
|
158
|
+
if (this.paymentUrl) {
|
|
159
|
+
window.open(this.paymentUrl, '_blank');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
get currentAsset() {
|
|
163
|
+
return this.assets.find(asset => asset.id === this.selectedAsset);
|
|
164
|
+
}
|
|
165
|
+
get currentNetwork() {
|
|
166
|
+
return this.currentAsset?.networks.find(network => network.id === this.selectedNetwork);
|
|
167
|
+
}
|
|
168
|
+
getFormattedTimeWindow() {
|
|
169
|
+
if (!this.qrCodeExpiresAt || isNaN(this.qrCodeExpiresAt))
|
|
170
|
+
return '30 min';
|
|
171
|
+
const endTime = this.qrCodeExpiresAt;
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
const diffMs = endTime - now;
|
|
174
|
+
if (diffMs <= 0)
|
|
175
|
+
return '0 min';
|
|
176
|
+
// Convertimos ms a minutos y redondeamos hacia arriba
|
|
177
|
+
const minutes = Math.ceil(diffMs / (1000 * 60));
|
|
178
|
+
return `${minutes} min`;
|
|
179
|
+
}
|
|
180
|
+
// --- RENDERIZADO DEL QR (Lógica bifurcada) ---
|
|
181
|
+
renderQRStep(t) {
|
|
182
|
+
const timeWindow = this.getFormattedTimeWindow();
|
|
183
|
+
const warningTokenHTML = I18n.interpolate(t.modal.warnings.onlyToken, {
|
|
184
|
+
symbol: this.currentAsset?.symbol || ''
|
|
185
|
+
});
|
|
186
|
+
const warningTimerHTML = I18n.interpolate(t.modal.warnings.timer, {
|
|
187
|
+
time: timeWindow
|
|
188
|
+
});
|
|
189
|
+
const network = this.currentNetwork;
|
|
190
|
+
// 1. Caso Apolo Pay
|
|
191
|
+
if (network?.network === 'apolopay') {
|
|
192
|
+
return html `
|
|
193
|
+
<payment-timer class="timer" .expiresAt=${this.qrCodeExpiresAt} @expired=${this.handleTimerExpired}></payment-timer>
|
|
194
|
+
|
|
195
|
+
<div class="qr-frame">
|
|
196
|
+
<div class="qr-wrapper">
|
|
197
|
+
<img src="${this.qrCodeUrl}" class="qr-code-img" alt="QR Apolo Pay" @error=${handleImageError} />
|
|
198
|
+
|
|
199
|
+
<img src="${logoApolo}" class="qr-overlay-icon" alt="Network Icon" style="padding: 4px;" />
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<span class="qr-badge">${this.amount} ${this.currentAsset?.symbol}</span>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div class="warning-text">
|
|
206
|
+
<ul>
|
|
207
|
+
<li>${unsafeHTML(t.modal.warnings.networkMatch)}</li>
|
|
208
|
+
<li>${unsafeHTML(t.modal.warnings.noNFT)}</li>
|
|
209
|
+
<li>${unsafeHTML(warningTokenHTML)}</li>
|
|
210
|
+
</ul>
|
|
211
|
+
<p>${unsafeHTML(warningTimerHTML)}</p>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<button class="btn-dark">${unsafeHTML(t.modal.actions.scanApp)}</button>
|
|
215
|
+
${this.paymentUrl ? html `
|
|
216
|
+
<button class="btn-primary" style="width: 100%; margin-top: 0.5rem;" @click=${this.handlePayFromDevice}>
|
|
217
|
+
${t.modal.actions.payFromDevice}
|
|
218
|
+
</button>
|
|
219
|
+
` : ''}
|
|
220
|
+
`;
|
|
221
|
+
}
|
|
222
|
+
// 2. Caso Red Externa
|
|
223
|
+
return html `
|
|
224
|
+
<payment-timer class="timer" .expiresAt=${this.qrCodeExpiresAt} @expired=${this.handleTimerExpired}></payment-timer>
|
|
225
|
+
|
|
226
|
+
<div class="qr-frame">
|
|
227
|
+
<div class="qr-wrapper">
|
|
228
|
+
<img src="${this.qrCodeUrl}" class="qr-code-img" alt="QR Wallet" @error=${handleImageError} />
|
|
229
|
+
|
|
230
|
+
${network
|
|
231
|
+
? html `<img src="${network.image}" class="qr-overlay-icon" alt="Network Icon" @error=${handleImageError} />`
|
|
232
|
+
: ''}
|
|
233
|
+
</div>
|
|
234
|
+
<span class="qr-badge">${this.amount} ${this.currentAsset?.symbol}</span>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div class="text-field">
|
|
238
|
+
<label class="text-field-label">${t.modal.labels.network}</label>
|
|
239
|
+
<input class="text-field-input" readonly value="${this.currentNetwork?.name}" />
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<div class="text-field">
|
|
243
|
+
<label class="text-field-label">${t.modal.labels.address}</label>
|
|
244
|
+
<input class="text-field-input" readonly value="${this.paymentAddress}" @click=${this.copyAddress} />
|
|
245
|
+
${this.paymentAddress ? html `
|
|
246
|
+
<button class="btn-secondary" @click=${this.copyAddress}>${this.isAddressCopied ? t.modal.actions.copied : t.modal.actions.copy}</button>
|
|
247
|
+
` : ''}
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div class="warning-text">
|
|
251
|
+
<ul>
|
|
252
|
+
<li>${unsafeHTML(t.modal.warnings.networkMatch)}</li>
|
|
253
|
+
<li>${unsafeHTML(t.modal.warnings.noNFT)}</li>
|
|
254
|
+
<li>${unsafeHTML(warningTokenHTML)}</li>
|
|
255
|
+
</ul>
|
|
256
|
+
<p>${unsafeHTML(warningTimerHTML)}</p>
|
|
257
|
+
</div>
|
|
258
|
+
${this.paymentUrl ? html `
|
|
259
|
+
<button class="btn-primary" style="width: 100%; margin-top: 1rem;" @click=${this.handlePayFromDevice}>
|
|
260
|
+
${t.modal.actions.payFromDevice}
|
|
261
|
+
</button>
|
|
262
|
+
` : ''}
|
|
263
|
+
`;
|
|
264
|
+
}
|
|
265
|
+
// --- Render Method ---
|
|
266
|
+
render() {
|
|
267
|
+
const t = I18n.t;
|
|
268
|
+
let content;
|
|
269
|
+
// Header simple con navegación
|
|
270
|
+
const header = html `
|
|
271
|
+
<div class="modal-header">
|
|
272
|
+
${this.currentStep > ModalStep.SELECT_ASSET && this.currentStep < ModalStep.RESULT
|
|
273
|
+
? html `<button class="back-button" @click=${() => this.changeStep(this.currentStep - 1)} >←</button>`
|
|
274
|
+
: ''}
|
|
275
|
+
<button class="close-button" @click=${this.requestClose}>×</button>
|
|
276
|
+
</div>
|
|
277
|
+
`;
|
|
278
|
+
// Selección de Asset
|
|
279
|
+
if (this.currentStep === ModalStep.SELECT_ASSET) {
|
|
280
|
+
content = html `
|
|
281
|
+
<h2>${unsafeHTML(t.modal.titles.selectAsset)}</h2>
|
|
282
|
+
<p class="subtitle">${t.modal.subtitles.selectAsset}</p>
|
|
283
|
+
|
|
284
|
+
<div class="selection-list">
|
|
285
|
+
${this.assets.map(asset => html `
|
|
286
|
+
<div class="selection-card" @click=${() => this.selectAsset(asset.id)}>
|
|
287
|
+
<img src="${asset.image}" class="coin-icon" @error=${handleImageError} />
|
|
288
|
+
<div class="card-text">
|
|
289
|
+
<span class="card-title">${asset.symbol}</span>
|
|
290
|
+
<span class="card-sub">${asset.name}</span>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
`)}
|
|
294
|
+
</div>
|
|
295
|
+
<p class="warning-text" style="font-size: 0.9rem; text-align: center; margin-top: 1.5rem">
|
|
296
|
+
${t.modal.warnings.selectNetworkLater}
|
|
297
|
+
</p>
|
|
298
|
+
`;
|
|
299
|
+
}
|
|
300
|
+
// Selección de Red
|
|
301
|
+
else if (this.currentStep === ModalStep.SELECT_NETWORK) {
|
|
302
|
+
content = html `
|
|
303
|
+
<h2>${unsafeHTML(t.modal.titles.selectNetwork)}</h2>
|
|
304
|
+
<p class="subtitle">${t.modal.subtitles.selectNetwork}</p>
|
|
305
|
+
|
|
306
|
+
<div class="selection-list">
|
|
307
|
+
${this.currentAsset?.networks.map((network) => html `
|
|
308
|
+
<div class="selection-card" @click=${() => this.selectNetwork(network.id)}>
|
|
309
|
+
<img src="${network.network === 'apolopay' ? logoApolo : network.image}" class="coin-icon" @error=${handleImageError} />
|
|
310
|
+
<div class="card-text">
|
|
311
|
+
<span class="card-title">${network.name}</span>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
`)}
|
|
315
|
+
</div>
|
|
316
|
+
`;
|
|
317
|
+
}
|
|
318
|
+
// QR
|
|
319
|
+
else if (this.currentStep === ModalStep.SHOW_QR) {
|
|
320
|
+
content = html `
|
|
321
|
+
<h2>${unsafeHTML(I18n.interpolate(t.modal.titles.scanQr, { symbol: this.currentAsset?.symbol || '' }))}</h2>
|
|
322
|
+
${this.productTitle ? html `<p class="subtitle">${this.productTitle}</p>` : ''}
|
|
323
|
+
${this.renderQRStep(t)}
|
|
324
|
+
`;
|
|
325
|
+
}
|
|
326
|
+
// Resultado
|
|
327
|
+
else if (this.currentStep === ModalStep.RESULT) {
|
|
328
|
+
// Display final success or error message
|
|
329
|
+
if (this.status === 'success') {
|
|
330
|
+
content = html `
|
|
331
|
+
<div class="result-container">
|
|
332
|
+
<div class="success-icon">
|
|
333
|
+
<svg viewBox="0 0 52 52">
|
|
334
|
+
<circle class="checkmark-circle" cx="26" cy="26" r="25" fill="none"/>
|
|
335
|
+
<path class="checkmark-check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
|
|
336
|
+
</svg>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<h2 class="result-title">${unsafeHTML(t.modal.titles.success)}</h2>
|
|
340
|
+
|
|
341
|
+
<p class="result-desc">
|
|
342
|
+
${t.modal.success.message} ${this.email ? html `<span class="highlight">${this.email}</span>` : ''} ${t.modal.success.message2}
|
|
343
|
+
</p>
|
|
344
|
+
|
|
345
|
+
<div class="purchase-details">
|
|
346
|
+
<h3 class="details-title">${t.modal.success.details}</h3>
|
|
347
|
+
|
|
348
|
+
${this.productTitle ? html `
|
|
349
|
+
<div class="text-field">
|
|
350
|
+
<label class="text-field-label">${t.modal.labels.product}</label>
|
|
351
|
+
<input class="text-field-input" readonly value=${this.productTitle} />
|
|
352
|
+
</div>
|
|
353
|
+
` : ''}
|
|
354
|
+
|
|
355
|
+
<div class="text-field">
|
|
356
|
+
<label class="text-field-label">${t.modal.labels.amount}</label>
|
|
357
|
+
<input class="text-field-input" readonly value="${this.amount} ${this.currentAsset?.symbol || ''}" />
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
`;
|
|
362
|
+
}
|
|
363
|
+
else if (this.status === 'error') {
|
|
364
|
+
content = html `
|
|
365
|
+
<div class="result-container">
|
|
366
|
+
<div class="error-icon">❌</div>
|
|
367
|
+
<h2 class="result-title">${t.modal.titles.error}</h2>
|
|
368
|
+
<p class="result-desc">${this.error?.message || t.errors.generic}</p>
|
|
369
|
+
<button class="btn-primary" @click=${this.requestClose}>${t.modal.actions.close}</button>
|
|
370
|
+
</div>
|
|
371
|
+
`;
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
content = html `
|
|
375
|
+
<div class="result-container">
|
|
376
|
+
<div class="error-icon">⏳</div>
|
|
377
|
+
<h2 class="result-title">${t.modal.titles.idle}</h2>
|
|
378
|
+
<p class="result-desc">${t.modal.subtitles.idle}</p>
|
|
379
|
+
<button class="btn-primary" @click=${this.requestClose}>${t.modal.actions.close}</button>
|
|
380
|
+
</div>
|
|
381
|
+
`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const showOverlay = this.isLoadingData ||
|
|
385
|
+
(this.currentStep === ModalStep.SHOW_QR && !this.qrCodeUrl);
|
|
386
|
+
return html `
|
|
387
|
+
<dialog @close=${this.handleDialogNativeClose}>
|
|
388
|
+
${showOverlay
|
|
389
|
+
? html `
|
|
390
|
+
<div class="spinner-overlay">
|
|
391
|
+
<div class="spinner"></div>
|
|
392
|
+
</div>`
|
|
393
|
+
: ''}
|
|
394
|
+
${header}
|
|
395
|
+
<div class="modal-body">
|
|
396
|
+
${content}
|
|
397
|
+
</div>
|
|
398
|
+
</dialog>
|
|
399
|
+
`;
|
|
400
|
+
} // End render
|
|
401
|
+
}; // End class
|
|
402
|
+
// --- Styles ---
|
|
403
|
+
PaymentModal.styles = [
|
|
404
|
+
sharedStyles,
|
|
405
|
+
modalBaseStyles,
|
|
406
|
+
textFieldBaseStyles,
|
|
407
|
+
qrBaseStyles,
|
|
408
|
+
spinnerStyles,
|
|
409
|
+
css `
|
|
410
|
+
/* --- HEADER --- */
|
|
411
|
+
.modal-header {
|
|
412
|
+
position: relative; /* Para posicionar el botón de cerrar */
|
|
413
|
+
padding: 1.5rem 1.5rem 0.5rem;
|
|
414
|
+
display: flex;
|
|
415
|
+
justify-content: center; /* Título centrado si lo hubiera */
|
|
416
|
+
align-items: center;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.close-button, .back-button {
|
|
420
|
+
position: absolute;
|
|
421
|
+
top: 1.5rem;
|
|
422
|
+
background: none;
|
|
423
|
+
border: none;
|
|
424
|
+
cursor: pointer;
|
|
425
|
+
color: #9ca3af;
|
|
426
|
+
transition: color 0.2s;
|
|
427
|
+
padding: 5px;
|
|
428
|
+
}
|
|
429
|
+
.close-button { right: 1.5rem; font-size: 1.5rem; }
|
|
430
|
+
.back-button { left: 1.5rem; font-size: 1.2rem; }
|
|
431
|
+
.close-button:hover, .back-button:hover { color: #374151; }
|
|
432
|
+
|
|
433
|
+
/* --- BODY --- */
|
|
434
|
+
.modal-body {
|
|
435
|
+
padding: 1rem 2rem 2.5rem; /* Padding generoso abajo */
|
|
436
|
+
text-align: center;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Títulos */
|
|
440
|
+
h2 {
|
|
441
|
+
font-size: 1.25rem;
|
|
442
|
+
font-weight: 700;
|
|
443
|
+
margin: 0 0 0.5rem;
|
|
444
|
+
}
|
|
445
|
+
.highlight { color: var(--apolo-accent); } /* Naranja de tus imágenes */
|
|
446
|
+
|
|
447
|
+
p.subtitle {
|
|
448
|
+
font-size: 0.9rem;
|
|
449
|
+
color: #6b7280;
|
|
450
|
+
margin: 0 0 1rem;
|
|
451
|
+
line-height: 1.4;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* --- LISTAS DE SELECCIÓN (Botones grandes blancos) --- */
|
|
455
|
+
.selection-list {
|
|
456
|
+
display: flex;
|
|
457
|
+
flex-direction: column;
|
|
458
|
+
gap: 1rem;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.selection-card {
|
|
462
|
+
display: flex;
|
|
463
|
+
align-items: center;
|
|
464
|
+
background: white;
|
|
465
|
+
border: 1px solid #f3f4f6; /* Borde muy sutil */
|
|
466
|
+
border-radius: 16px;
|
|
467
|
+
padding: 1rem;
|
|
468
|
+
cursor: pointer;
|
|
469
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
|
470
|
+
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.selection-card:hover {
|
|
474
|
+
transform: translateY(-2px);
|
|
475
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.08);
|
|
476
|
+
border-color: var(--apolo-accent); /* Hover naranja */
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.coin-icon {
|
|
480
|
+
width: 40px;
|
|
481
|
+
height: 40px;
|
|
482
|
+
margin-right: 1rem;
|
|
483
|
+
object-fit: cover;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.card-text {
|
|
487
|
+
text-align: left;
|
|
488
|
+
display: flex;
|
|
489
|
+
flex-direction: column;
|
|
490
|
+
}
|
|
491
|
+
.card-title { font-weight: 600; font-size: 1rem; color: var(--apolo-text); }
|
|
492
|
+
.card-sub { font-size: 0.8rem; color: var(--apolo-text-muted); text-transform: uppercase;}
|
|
493
|
+
|
|
494
|
+
/* --- QR SCREENS --- */
|
|
495
|
+
.timer {
|
|
496
|
+
color: var(--apolo-accent);
|
|
497
|
+
font-weight: 600;
|
|
498
|
+
font-size: 0.9rem;
|
|
499
|
+
margin-bottom: 1rem;
|
|
500
|
+
display: block;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/* Botón Naranja Grande */
|
|
504
|
+
.btn-primary {
|
|
505
|
+
background-color: var(--apolo-accent); /* Naranja Apolo */
|
|
506
|
+
color: white;
|
|
507
|
+
padding: 0.5rem 1.5rem;
|
|
508
|
+
border-radius: var(--apolo-radius-lg); /* Pill shape */
|
|
509
|
+
border: none;
|
|
510
|
+
font-weight: 400;
|
|
511
|
+
font-size: .9rem;
|
|
512
|
+
cursor: pointer;
|
|
513
|
+
box-shadow: 0 4px 10px rgba(234, 88, 12, 0.3);
|
|
514
|
+
transition: transform 0.1s, box-shadow 0.1s;
|
|
515
|
+
}
|
|
516
|
+
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 6px 15px rgba(234, 88, 12, 0.4); }
|
|
517
|
+
|
|
518
|
+
/* Botón Azul Oscuro (Apolo Pay QR) */
|
|
519
|
+
.btn-dark {
|
|
520
|
+
background-color: var(--apolo-primary-darkest);
|
|
521
|
+
color: white;
|
|
522
|
+
width: 100%;
|
|
523
|
+
padding: 1rem;
|
|
524
|
+
border-radius: var(--apolo-radius);
|
|
525
|
+
border: none;
|
|
526
|
+
font-weight: 600;
|
|
527
|
+
cursor: pointer;
|
|
528
|
+
margin-block: 0.25rem 1.25rem;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.warning-text {
|
|
532
|
+
font-size: 0.75rem;
|
|
533
|
+
text-align: left;
|
|
534
|
+
margin-top: 1.5rem;
|
|
535
|
+
line-height: 1.5;
|
|
536
|
+
}
|
|
537
|
+
.warning-text strong { color: var(--apolo-accent); }
|
|
538
|
+
|
|
539
|
+
.warning-text ul {
|
|
540
|
+
padding-left: 1.5rem;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
/* --- PANTALLA DE RESULTADO --- */
|
|
545
|
+
.result-container {
|
|
546
|
+
text-align: center;
|
|
547
|
+
animation: fadeIn 0.5s ease-out;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/* Animación simple de entrada */
|
|
551
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
|
552
|
+
|
|
553
|
+
.success-icon {
|
|
554
|
+
width: 80px;
|
|
555
|
+
height: 80px;
|
|
556
|
+
margin: 0 auto 1.5rem;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/* Animación del Check SVG */
|
|
560
|
+
.checkmark-circle {
|
|
561
|
+
stroke-dasharray: 166;
|
|
562
|
+
stroke-dashoffset: 166;
|
|
563
|
+
stroke-width: 2;
|
|
564
|
+
stroke: #22c55e; /* Verde éxito */
|
|
565
|
+
fill: none;
|
|
566
|
+
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
|
567
|
+
}
|
|
568
|
+
.checkmark-check {
|
|
569
|
+
transform-origin: 50% 50%;
|
|
570
|
+
stroke-dasharray: 48;
|
|
571
|
+
stroke-dashoffset: 48;
|
|
572
|
+
stroke: #22c55e;
|
|
573
|
+
stroke-width: 4;
|
|
574
|
+
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.6s forwards;
|
|
575
|
+
}
|
|
576
|
+
@keyframes stroke { 100% { stroke-dashoffset: 0; } }
|
|
577
|
+
|
|
578
|
+
.result-title {
|
|
579
|
+
font-size: 1.5rem;
|
|
580
|
+
margin-bottom: 1rem;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.result-desc {
|
|
584
|
+
font-size: 0.95rem;
|
|
585
|
+
margin-bottom: 1.5rem;
|
|
586
|
+
line-height: 1.5;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.purchase-details {
|
|
590
|
+
text-align: left;
|
|
591
|
+
margin-bottom: 1.5rem;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.details-title {
|
|
595
|
+
font-size: 1rem;
|
|
596
|
+
font-weight: 700;
|
|
597
|
+
text-decoration: underline;
|
|
598
|
+
text-decoration-color: var(--apolo-text);
|
|
599
|
+
text-underline-offset: 4px;
|
|
600
|
+
text-align: center;
|
|
601
|
+
margin-bottom: 1.5rem;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/* Estilo Error */
|
|
605
|
+
.error-icon { font-size: 4rem; margin-bottom: 1rem; }
|
|
606
|
+
`
|
|
607
|
+
];
|
|
608
|
+
__decorate([
|
|
609
|
+
property({ type: Boolean })
|
|
610
|
+
], PaymentModal.prototype, "isOpen", void 0);
|
|
611
|
+
__decorate([
|
|
612
|
+
property({ type: Boolean })
|
|
613
|
+
], PaymentModal.prototype, "barrierDismissible", void 0);
|
|
614
|
+
__decorate([
|
|
615
|
+
property({ type: String })
|
|
616
|
+
], PaymentModal.prototype, "lang", void 0);
|
|
617
|
+
__decorate([
|
|
618
|
+
property({ type: String })
|
|
619
|
+
], PaymentModal.prototype, "productTitle", void 0);
|
|
620
|
+
__decorate([
|
|
621
|
+
property({ type: Number })
|
|
622
|
+
], PaymentModal.prototype, "currentStep", void 0);
|
|
623
|
+
__decorate([
|
|
624
|
+
property({ type: String })
|
|
625
|
+
], PaymentModal.prototype, "status", void 0);
|
|
626
|
+
__decorate([
|
|
627
|
+
property({ type: Object })
|
|
628
|
+
], PaymentModal.prototype, "error", void 0);
|
|
629
|
+
__decorate([
|
|
630
|
+
property({ type: Boolean })
|
|
631
|
+
], PaymentModal.prototype, "isLoadingData", void 0);
|
|
632
|
+
__decorate([
|
|
633
|
+
property({ type: Array })
|
|
634
|
+
], PaymentModal.prototype, "assets", void 0);
|
|
635
|
+
__decorate([
|
|
636
|
+
property({ type: String })
|
|
637
|
+
], PaymentModal.prototype, "selectedAsset", void 0);
|
|
638
|
+
__decorate([
|
|
639
|
+
property({ type: String })
|
|
640
|
+
], PaymentModal.prototype, "selectedNetwork", void 0);
|
|
641
|
+
__decorate([
|
|
642
|
+
property({ type: String })
|
|
643
|
+
], PaymentModal.prototype, "qrCodeUrl", void 0);
|
|
644
|
+
__decorate([
|
|
645
|
+
property({ type: String })
|
|
646
|
+
], PaymentModal.prototype, "paymentAddress", void 0);
|
|
647
|
+
__decorate([
|
|
648
|
+
property({ type: Number })
|
|
649
|
+
], PaymentModal.prototype, "amount", void 0);
|
|
650
|
+
__decorate([
|
|
651
|
+
property({ type: String })
|
|
652
|
+
], PaymentModal.prototype, "email", void 0);
|
|
653
|
+
__decorate([
|
|
654
|
+
property({ type: Number })
|
|
655
|
+
], PaymentModal.prototype, "qrCodeExpiresAt", void 0);
|
|
656
|
+
__decorate([
|
|
657
|
+
property({ type: String })
|
|
658
|
+
], PaymentModal.prototype, "paymentUrl", void 0);
|
|
659
|
+
__decorate([
|
|
660
|
+
state()
|
|
661
|
+
], PaymentModal.prototype, "isAddressCopied", void 0);
|
|
662
|
+
__decorate([
|
|
663
|
+
query('dialog')
|
|
664
|
+
], PaymentModal.prototype, "dialogElement", void 0);
|
|
665
|
+
PaymentModal = __decorate([
|
|
666
|
+
customElement('payment-modal')
|
|
667
|
+
], PaymentModal);
|
|
668
|
+
export { PaymentModal };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
export declare class PaymentTimer extends LitElement {
|
|
3
|
+
expiresAt: number;
|
|
4
|
+
private timerString;
|
|
5
|
+
private _interval;
|
|
6
|
+
connectedCallback(): void;
|
|
7
|
+
disconnectedCallback(): void;
|
|
8
|
+
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
9
|
+
private startTimer;
|
|
10
|
+
private stopTimer;
|
|
11
|
+
static styles: import("lit").CSSResult;
|
|
12
|
+
render(): import("lit").TemplateResult<1>;
|
|
13
|
+
}
|
|
14
|
+
declare global {
|
|
15
|
+
interface HTMLElementTagNameMap {
|
|
16
|
+
'payment-timer': PaymentTimer;
|
|
17
|
+
}
|
|
18
|
+
}
|