Files
QRPayWeb/QRPay/Pages/Home.razor
T
Michal Vrba 373ca0cf9f Add "Copy QR Code" button with clipboard support
Implemented a copy-to-clipboard button for QR codes, including Blazor logic for feedback and disabled states. Injected IJSRuntime for JS interop and added supporting JS functions for image copying and fallback download. Reduced QR code image size for better compatibility. Added responsive and accessible CSS for the new button. Cleaned up timers and feedback state on component disposal.
2026-01-07 14:48:45 +01:00

253 lines
9.4 KiB
Plaintext

@page "/"
@using QRCoder
@using SixLabors.ImageSharp.Formats
@using System.ComponentModel.DataAnnotations
@inject IJSRuntime JSRuntime
<PageTitle>QRPay - QR Payment Generator</PageTitle>
<div class="qrpay-container">
@if (!_showQRCode)
{
<!-- Form View -->
<div class="qrpay-card">
<!-- Logo and Header -->
<div class="qrpay-header">
<div class="qrpay-logo">
<img src="logo.svg" alt="QRPay Logo" />
</div>
<h1 class="qrpay-title">QRPay</h1>
<p class="qrpay-subtitle">Generate QR payment codes instantly</p>
</div>
<!-- Form -->
<EditForm Model="@paymentModel" OnValidSubmit="@GenerateQRCode">
<DataAnnotationsValidator />
<div class="qrpay-form">
<div class="form-group">
<label for="name" class="form-label">Beneficiary Name</label>
<InputText @bind-Value="paymentModel.Name" class="form-control" id="name" placeholder="Enter beneficiary name" />
<ValidationMessage For="@(() => paymentModel.Name)" class="validation-message" />
</div>
<div class="form-group">
<label for="iban" class="form-label">IBAN</label>
<InputText @bind-Value="paymentModel.IBAN" class="form-control" id="iban" placeholder="Enter IBAN (e.g., SK89 7500 0000 0000 1234 5671)" />
<ValidationMessage For="@(() => paymentModel.IBAN)" class="validation-message" />
</div>
<div class="form-group">
<label for="amount" class="form-label">Amount (EUR)</label>
<InputNumber @bind-Value="paymentModel.Amount" class="form-control" id="amount" placeholder="0.00" step="0.01" />
<ValidationMessage For="@(() => paymentModel.Amount)" class="validation-message" />
</div>
<div class="form-group">
<label for="remark" class="form-label">Payment Reference (Optional)</label>
<InputText @bind-Value="paymentModel.Remark" class="form-control" id="remark" placeholder="Enter payment reference or note" />
<ValidationMessage For="@(() => paymentModel.Remark)" class="validation-message" />
</div>
<button type="submit" class="btn-base btn-generate" disabled="@_isGenerating">
@if (_isGenerating)
{
<span class="spinner"></span>
<span>Generating...</span>
}
else
{
<span>Generate QR Code</span>
}
</button>
</div>
</EditForm>
</div>
}
else
{
<!-- QR Code View -->
<div class="qrcode-container">
<div class="qrcode-header">
<h2>Payment QR Code</h2>
<p>Scan this code with your banking app</p>
</div>
<div class="qrcode-display">
<img id="qrcode-image" src="@QRCodeStr" alt="Payment QR Code" />
<!-- Copy Button -->
<button class="btn-copy @(_isCopying ? "copying" : "")" @onclick="@CopyQRCodeToClipboard" disabled="@_isCopying">
@if (_isCopying)
{
<span class="copy-spinner"></span>
<span>Copying...</span>
}
else if (_copySuccess)
{
<span>✓ Copied!</span>
}
else
{
<span>📋 Copy QR Code</span>
}
</button>
</div>
<div class="payment-details">
<div class="detail-row">
<span class="detail-label">To:</span>
<span class="detail-value">@paymentModel.Name</span>
</div>
<div class="detail-row">
<span class="detail-label">Amount:</span>
<span class="detail-value">€@paymentModel.Amount.ToString("F2")</span>
</div>
@if (!string.IsNullOrWhiteSpace(paymentModel.Remark))
{
<div class="detail-row">
<span class="detail-label">Reference:</span>
<span class="detail-value">@paymentModel.Remark</span>
</div>
}
</div>
<button class="btn-base btn-back" @onclick="@GoBack">
<span>← Create New Payment</span>
</button>
</div>
}
</div>
@code {
private PaymentModel paymentModel = new();
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
{
[Required(ErrorMessage = "Beneficiary name is required")]
[StringLength(70, ErrorMessage = "Name cannot exceed 70 characters")]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "IBAN is required")]
[RegularExpression(@"^[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}([A-Z0-9]?){0,16}$", ErrorMessage = "Please enter a valid IBAN")]
public string IBAN { get; set; } = string.Empty;
[Required(ErrorMessage = "Amount is required")]
[Range(0.01, 999999.99, ErrorMessage = "Amount must be between €0.01 and €999,999.99")]
public decimal Amount { get; set; } = 0;
[StringLength(140, ErrorMessage = "Remark cannot exceed 140 characters")]
public string? Remark { get; set; } = string.Empty;
}
public async Task GenerateQRCode()
{
_isGenerating = true;
StateHasChanged();
// Small delay to show loading state
await Task.Delay(500);
try
{
QRCodeStr = string.Empty;
if (!string.IsNullOrWhiteSpace(paymentModel.IBAN))
{
using (var ms = new MemoryStream())
{
QRCodeGenerator qRCodeGenerator = new QRCodeGenerator();
var payment = new PayBySquareTextGenerator.Payment(
paymentModel.IBAN.Replace(" ", ""),
paymentModel.Amount,
"EUR",
"1",
paymentModel.Remark ?? string.Empty);
payment.ConstantSymbol = "0308";
payment.BeneficiaryName = paymentModel.Name;
var payBySquare = new PayBySquareTextGenerator.PayBySquareOverkill(payment);
var stringData = payBySquare.GeneratePayBySquareOverkillString();
// 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);
// Reduced from 20 to 10 for smaller clipboard image
byte[] qrCodeImage = qrCode.GetGraphic(10);
QRCodeStr = $"data:image/png;base64,{Convert.ToBase64String(qrCodeImage)}";
}
_showQRCode = true;
}
}
finally
{
_isGenerating = false;
StateHasChanged();
}
}
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<bool>("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();
}
}