Add /qr route and refactor payment QR code generation
Introduce a dedicated /qr route for QR code preview via query params. Add PaymentRequest model for payment data and validation, replacing the old inline model. Extract QR code generation to PaymentQrCodeService for reuse. Refactor Home.razor to use new model/service and improve validation/UI. Add EmptyLayout for minimal layout needs. Update .gitignore and _Imports.razor for new structure.
This commit is contained in:
+25
-75
@@ -1,16 +1,15 @@
|
||||
@page "/"
|
||||
@using QRCoder
|
||||
@using SixLabors.ImageSharp.Formats
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@page "/"
|
||||
@inject IJSRuntime JSRuntime
|
||||
@using QRPay.Models
|
||||
@using QRPay.Services
|
||||
@implements IDisposable
|
||||
|
||||
<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" />
|
||||
@@ -19,10 +18,9 @@
|
||||
<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>
|
||||
@@ -65,17 +63,15 @@
|
||||
}
|
||||
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)
|
||||
{
|
||||
@@ -92,15 +88,15 @@
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="payment-details">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">To:</span>
|
||||
<span class="detail-value">@paymentModel.Name</span>
|
||||
<span class="detail-value">@(string.IsNullOrWhiteSpace(paymentModel.Name) ? "Not provided" : paymentModel.Name)</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Amount:</span>
|
||||
<span class="detail-value">€@paymentModel.Amount.ToString("F2")</span>
|
||||
<span class="detail-value">€@paymentModel.Amount?.ToString("F2")</span>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(paymentModel.Remark))
|
||||
{
|
||||
@@ -110,7 +106,7 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<button class="btn-base btn-back" @onclick="@GoBack">
|
||||
<span>← Create New Payment</span>
|
||||
</button>
|
||||
@@ -119,71 +115,28 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private PaymentModel paymentModel = new();
|
||||
private PaymentRequest paymentModel = new();
|
||||
private string QRCodeStr = string.Empty;
|
||||
private bool _showQRCode = false;
|
||||
private bool _isGenerating = false;
|
||||
private bool _isCopying = false;
|
||||
private bool _copySuccess = false;
|
||||
private bool _showQRCode;
|
||||
private bool _isGenerating;
|
||||
private bool _isCopying;
|
||||
private bool _copySuccess;
|
||||
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)}";
|
||||
}
|
||||
|
||||
QRCodeStr = PaymentQrCodeService.GenerateDataUri(paymentModel);
|
||||
_showQRCode = true;
|
||||
}
|
||||
}
|
||||
@@ -202,17 +155,15 @@
|
||||
_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(_ =>
|
||||
{
|
||||
@@ -226,7 +177,6 @@
|
||||
else
|
||||
{
|
||||
StateHasChanged();
|
||||
// Could show an error message here if needed
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -240,7 +190,7 @@
|
||||
{
|
||||
_showQRCode = false;
|
||||
QRCodeStr = string.Empty;
|
||||
paymentModel = new PaymentModel(); // Reset the form
|
||||
paymentModel = new PaymentRequest();
|
||||
_copySuccess = false;
|
||||
_copySuccessTimer?.Dispose();
|
||||
StateHasChanged();
|
||||
@@ -250,4 +200,4 @@
|
||||
{
|
||||
_copySuccessTimer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user