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 @@
-

+

+
+
+
@@ -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
🗙
+
+
+
+