diff --git a/QRPay/Pages/Home.razor b/QRPay/Pages/Home.razor index 55a60a2..81cee84 100644 --- a/QRPay/Pages/Home.razor +++ b/QRPay/Pages/Home.razor @@ -2,6 +2,7 @@ @using QRCoder @using SixLabors.ImageSharp.Formats @using System.ComponentModel.DataAnnotations +@inject IJSRuntime JSRuntime QRPay - QR Payment Generator
@@ -72,7 +73,24 @@
- Payment QR Code + Payment QR Code + + +
@@ -105,6 +123,9 @@ private string QRCodeStr = string.Empty; private bool _showQRCode = false; private bool _isGenerating = false; + private bool _isCopying = false; + private bool _copySuccess = false; + private System.Threading.Timer? _copySuccessTimer; public class PaymentModel { @@ -154,10 +175,11 @@ var payBySquare = new PayBySquareTextGenerator.PayBySquareOverkill(payment); var stringData = payBySquare.GeneratePayBySquareOverkillString(); - // Generate QR code data + // Generate QR code data - smaller size for better clipboard handling using var qrCodeData = QRCodeGenerator.GenerateQrCode(stringData, QRCodeGenerator.ECCLevel.Q); using var qrCode = new PngByteQRCode(qrCodeData); - byte[] qrCodeImage = qrCode.GetGraphic(20); + // Reduced from 20 to 10 for smaller clipboard image + byte[] qrCodeImage = qrCode.GetGraphic(10); QRCodeStr = $"data:image/png;base64,{Convert.ToBase64String(qrCodeImage)}"; } @@ -172,11 +194,60 @@ } } + public async Task CopyQRCodeToClipboard() + { + try + { + _isCopying = true; + _copySuccess = false; + StateHasChanged(); + + // Use JavaScript to copy the QR code image to clipboard with smaller size + var success = await JSRuntime.InvokeAsync("copyImageToClipboardSmall", "qrcode-image"); + + _isCopying = false; + + if (success) + { + _copySuccess = true; + StateHasChanged(); + + // Reset the success state after 3 seconds + _copySuccessTimer?.Dispose(); + _copySuccessTimer = new System.Threading.Timer(_ => + { + InvokeAsync(() => + { + _copySuccess = false; + StateHasChanged(); + }); + }, null, TimeSpan.FromSeconds(3), Timeout.InfiniteTimeSpan); + } + else + { + StateHasChanged(); + // Could show an error message here if needed + } + } + catch (Exception) + { + _isCopying = false; + StateHasChanged(); + } + } + public void GoBack() { _showQRCode = false; QRCodeStr = string.Empty; paymentModel = new PaymentModel(); // Reset the form + _copySuccess = false; + _copySuccessTimer?.Dispose(); StateHasChanged(); } + + public void Dispose() + { + _copySuccessTimer?.Dispose(); + } } \ No newline at end of file diff --git a/QRPay/Pages/Home.razor.css b/QRPay/Pages/Home.razor.css index 9854742..3681990 100644 --- a/QRPay/Pages/Home.razor.css +++ b/QRPay/Pages/Home.razor.css @@ -174,6 +174,63 @@ background: rgba(74, 124, 89, 0.2); } +/* Copy Button Styles */ +.btn-copy { + background: rgba(16, 185, 129, 0.1); + color: #065f46; + border: 2px solid #10b981; + padding: 12px 20px; + border-radius: 10px; + font-size: 0.95rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + margin-top: 16px; + min-height: 48px; + width: auto; + max-width: 200px; + margin-left: auto; + margin-right: auto; +} + +.btn-copy:hover:not(:disabled) { + background: rgba(16, 185, 129, 0.15); + border-color: #059669; + color: #047857; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2); +} + +.btn-copy:active:not(:disabled) { + transform: translateY(0); + box-shadow: 0 2px 6px rgba(16, 185, 129, 0.15); +} + +.btn-copy:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +.btn-copy.copying { + background: rgba(16, 185, 129, 0.05); + border-color: #6ee7b7; + color: #059669; +} + +.copy-spinner { + width: 16px; + height: 16px; + border: 2px solid rgba(6, 95, 70, 0.3); + border-radius: 50%; + border-top-color: #065f46; + animation: spin 1s ease-in-out infinite; +} + .spinner { width: 20px; height: 20px; @@ -230,6 +287,8 @@ max-width: 100%; height: auto; aspect-ratio: 1; + display: block; + margin: 0 auto 16px auto; } /* Payment Details */ @@ -323,6 +382,12 @@ .form-control { font-size: 16px; /* Prevents zoom on iOS */ } + + .btn-copy { + font-size: 0.9rem; + padding: 10px 16px; + max-width: 180px; + } } /* Portrait tablets and large phones */ @@ -376,6 +441,13 @@ height: 240px; } + .btn-copy { + font-size: 0.85rem; + padding: 10px 14px; + max-width: 160px; + margin-top: 12px; + } + .payment-details { padding: 20px; margin-bottom: 24px; @@ -471,6 +543,13 @@ height: 200px; } + .btn-copy { + font-size: 0.8rem; + padding: 8px 12px; + max-width: 140px; + margin-top: 10px; + } + .payment-details { padding: 16px; margin-bottom: 20px; @@ -518,6 +597,12 @@ height: 180px; } + .btn-copy { + font-size: 0.75rem; + padding: 8px 10px; + max-width: 120px; + } + .form-control { padding: 12px 14px; } @@ -557,6 +642,12 @@ height: 160px; } + .btn-copy { + font-size: 0.8rem; + padding: 6px 10px; + max-width: 110px; + } + .payment-details { margin-bottom: 20px; } @@ -565,7 +656,8 @@ /* Touch improvements for mobile */ @media (max-width: 768px) { .btn-generate:hover, - .btn-back:hover { + .btn-back:hover, + .btn-copy:hover { transform: none; /* Disable hover transforms on mobile */ } @@ -575,8 +667,9 @@ /* Larger touch targets */ .btn-generate, - .btn-back { - min-height: 48px; /* WCAG recommended minimum touch target */ + .btn-back, + .btn-copy { + min-height: 44px; /* WCAG recommended minimum touch target */ } } @@ -591,7 +684,8 @@ animation: none; } - .spinner { + .spinner, + .copy-spinner { animation: none; } diff --git a/QRPay/wwwroot/index.html b/QRPay/wwwroot/index.html index bff5d8b..1ce9077 100644 --- a/QRPay/wwwroot/index.html +++ b/QRPay/wwwroot/index.html @@ -54,6 +54,154 @@ Reload 🗙
+ + + +