-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9e4e059
commit 1bc0e83
Showing
6 changed files
with
168 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,15 @@ | ||
@using Newtonsoft.Json.Linq | ||
@using System.Text.RegularExpressions | ||
@using System.Text.RegularExpressions | ||
@using System.Globalization | ||
@using Newtonsoft.Json | ||
@using Newtonsoft.Json.Linq | ||
@inject IJSRuntime JS | ||
|
||
<form class="d-flex flex-column gap-4" @onsubmit="Submit"> | ||
<div class="d-flex flex-column gap-4"> | ||
<input type="hidden" name="amount" value="@GetTotal()"> | ||
<input type="hidden" name="posdata" value="@GetData()"> | ||
<div class="d-flex flex-column align-items-center px-4 mb-auto" @ref="_keypadTop"> | ||
<div class="fw-semibold text-muted" id="Currency">@CurrencyCode</div> | ||
<div class="fw-bold lh-sm" style="font-size:@($"{FontSize}px")" @ref="_keypadAmount">@FormatCurrency(GetTotal(), false)</div> | ||
<div class="fw-bold lh-sm" id="Amount" style="font-size:@($"{FontSize}px")" @ref="_keypadAmount">@FormatCurrency(GetTotal(), false)</div> | ||
<div class="text-muted text-center mt-2" id="Calculation">@Calculation(Model)</div> | ||
</div> | ||
@if (IsDiscountEnabled || IsTipEnabled) | ||
|
@@ -56,13 +59,14 @@ | |
} | ||
</div> | ||
<div id="ModeTablist" class="nav btcpay-pills align-items-center justify-content-center mb-n2 pb-1" role="tablist"> | ||
@{ var amount = GetAmount(); } | ||
@foreach (var mode in GetModes()) | ||
{ | ||
<input id="ModeTablist-@mode" name="mode" value="@mode" type="radio" role="tab" | ||
aria-controls="Mode-@mode" aria-selected="@(Mode == mode ? "true" : "false")" | ||
data-bs-toggle="pill" data-bs-target="#Mode-@mode" | ||
checked="@(Mode == mode)" | ||
disabled="@(Mode != InputMode.Amount && GetAmount() is 0)" | ||
disabled="@(mode != InputMode.Amount && amount == 0)" | ||
@onclick="() => Mode = mode"> | ||
<label for="ModeTablist-@mode">@mode</label> | ||
} | ||
|
@@ -74,7 +78,7 @@ | |
<button disabled="@(key == '+' && Mode != InputMode.Amount)" @onclick="@(e => KeyPress(key))" @onclick:preventDefault @ondblclick="@(e => DoublePress(key))" type="button" class="btn btn-secondary btn-lg" data-key="@key">@key</button> | ||
} | ||
</div> | ||
<button class="btn btn-lg btn-primary mx-3" type="submit" disabled="@IsSubmitting" id="pay-button"> | ||
<button class="btn btn-lg btn-primary mx-3" type="submit" disabled="@IsSubmitting" id="pay-button" @onclick="@(e => { IsSubmitting = true; })"> | ||
@if (IsSubmitting) | ||
{ | ||
<div class="spinner-border spinner-border-sm" role="status"> | ||
|
@@ -86,23 +90,80 @@ | |
<span>Charge</span> | ||
} | ||
</button> | ||
</form> | ||
@if (!string.IsNullOrEmpty(RecentTransactionsUrl)) | ||
{ | ||
<div class="modal" tabindex="-1" id="RecentTransactions" ref="RecentTransactions" data-bs-backdrop="static" data-url="@RecentTransactionsUrl"> | ||
<div class="modal-dialog modal-dialog-centered"> | ||
<div class="modal-content"> | ||
<div class="modal-header"> | ||
<h5 class="modal-title">Recent Transactions</h5> | ||
<button type="button" class="btn btn-link px-3 py-0" aria-label="Refresh" @onclick="@LoadRecentTransactions" disabled="@_recentTransactionsLoading" id="RecentTransactionsRefresh"> | ||
<vc:icon symbol="refresh" /> | ||
@if (_recentTransactionsLoading) | ||
{ | ||
<span class="visually-hidden">Loading...</span> | ||
} | ||
</button> | ||
<button type="button" class="btn-close py-3" aria-label="Close" v-on:click="closeModal"> | ||
<vc:icon symbol="close" /> | ||
</button> | ||
</div> | ||
<div class="modal-body"> | ||
@if (RecentTransactions?.Count > 0) | ||
{ | ||
<div class="list-group list-group-flush"> | ||
@foreach (var t in RecentTransactions) | ||
{ | ||
<a href="@t.Url" class="list-group-item list-group-item-action d-flex align-items-center gap-3 pe-1 py-3"> | ||
<div class="d-flex align-items-baseline justify-content-between flex-wrap flex-grow-1 gap-2"> | ||
<span class="flex-grow-1">@t.Date</span> | ||
<span class="flex-grow-1 text-end">@t.Price</span> | ||
<div class="badge-container"> | ||
<span class="badge [email protected]()">@t.Status</span> | ||
</div> | ||
</div> | ||
<vc:icon symbol="caret-right" /> | ||
</a> | ||
} | ||
</div> | ||
} | ||
else if (_recentTransactionsLoading) | ||
{ | ||
<p class="text-muted my-0">Loading...</p> | ||
} | ||
else | ||
{ | ||
<p class="text-muted my-0">No transactions, yet.</p> | ||
} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<button type="button" class="btn btn-link p-1" data-bs-toggle="modal" data-bs-target="#RecentTransactions" id="RecentTransactionsToggle"> | ||
<vc:icon symbol="manage-plugins" /> | ||
</button> | ||
} | ||
</div> | ||
|
||
@code { | ||
[Parameter] | ||
public string? CurrencyCode { get; set; } | ||
#nullable enable | ||
[Parameter, EditorRequired] | ||
public string CurrencyCode { get; set; } | ||
[Parameter] | ||
public string? CurrencySymbol { get; set; } | ||
public string RecentTransactionsUrl { get; set; } | ||
[Parameter] | ||
public int CurrencyDivisibility { get; set; } | ||
public NumberFormatInfo? CurrencyInfo { get; set; } | ||
[Parameter] | ||
public bool IsDiscountEnabled { get; set; } | ||
[Parameter] | ||
public bool IsTipEnabled { get; set; } | ||
[Parameter] | ||
public int[]? CustomTipPercentages { get; set; } | ||
[Parameter] | ||
public EventCallback<MouseEventArgs> OnClickCallback { get; set; } | ||
|
||
private bool IsSubmitting { get; set; } | ||
private List<RecentTransaction>? RecentTransactions { get; set; } | ||
|
||
const int DefaultFontSize = 64; | ||
static char[] Keys = { '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', '0', '+' }; | ||
|
@@ -117,8 +178,11 @@ | |
|
||
private ElementReference _keypadTop; | ||
private ElementReference _keypadAmount; | ||
|
||
private string? _currencySymbol; | ||
private int _currencyDivisibility; | ||
private bool _recentTransactionsLoading; | ||
private InputMode Mode { get; set; } = InputMode.Amount; | ||
private KeypadModel Model { get; set; } = new (); | ||
|
||
public class KeypadModel | ||
{ | ||
|
@@ -128,11 +192,12 @@ | |
public decimal? Tip { get; set; } | ||
} | ||
|
||
private KeypadModel Model { get; set; } = new (); | ||
|
||
private void Submit() | ||
protected override void OnInitialized() | ||
{ | ||
IsSubmitting = true; | ||
base.OnInitialized(); | ||
|
||
_currencySymbol = CurrencyInfo?.CurrencySymbol ?? CurrencyCode; | ||
_currencyDivisibility = CurrencyInfo?.CurrencyDecimalDigits ?? 0; | ||
} | ||
|
||
private async Task KeyPress(char key) | ||
|
@@ -154,9 +219,8 @@ | |
} else if (key == '+' && lastAmount != 0) { | ||
Model.Amounts.Add(0); | ||
} else { // Is a digit | ||
Model.Amounts[lastIndex] = Math.Min(ApplyKeyToValue(key, lastAmount, CurrencyDivisibility), decimal.MaxValue / 10); | ||
Model.Amounts[lastIndex] = Math.Min(ApplyKeyToValue(key, lastAmount, _currencyDivisibility), decimal.MaxValue / 10); | ||
} | ||
await UpdateFontSize(); | ||
} else { | ||
if (key == 'C') { | ||
if (Mode == InputMode.Tip) | ||
|
@@ -169,7 +233,7 @@ | |
Model.DiscountPercent = null; | ||
} | ||
} else { | ||
var divisibility = Mode == InputMode.Tip ? CurrencyDivisibility : 0; | ||
var divisibility = Mode == InputMode.Tip ? _currencyDivisibility : 0; | ||
if (Mode == InputMode.Tip) | ||
{ | ||
Model.Tip = Math.Min(ApplyKeyToValue(key, Model.Tip ?? 0, divisibility), decimal.MaxValue / 10); | ||
|
@@ -182,12 +246,12 @@ | |
} | ||
} | ||
} | ||
await UpdateFontSize(); | ||
} | ||
|
||
private decimal ApplyKeyToValue(char key, decimal value, int divisibility) | ||
{ | ||
var str = value is 0 ? "" : Formatted(value, divisibility); | ||
//var minDiv = str.Length < divisibility ? str.Length + 1 : divisibility; | ||
var str = value is 0 ? "" : FormattedInvariant(value, divisibility); | ||
str = (str + key).Replace(".", ""); | ||
if (divisibility > 0) | ||
{ | ||
|
@@ -215,7 +279,7 @@ | |
Mode = InputMode.Amount; | ||
} | ||
|
||
private JObject GetData() | ||
private string GetData() | ||
{ | ||
var data = new JObject | ||
{ | ||
|
@@ -242,7 +306,7 @@ | |
{ | ||
data["tipPercentage"] = Model.TipPercent; | ||
} | ||
return data; | ||
return JsonConvert.SerializeObject(data); | ||
} | ||
|
||
private List<InputMode> GetModes() | ||
|
@@ -262,17 +326,17 @@ | |
{ | ||
var amount = GetAmount(); | ||
return amount > 0 && Model.DiscountPercent is > 0 | ||
? Math.Round(amount * (Model.DiscountPercent.Value / 100.0m), CurrencyDivisibility) | ||
? Math.Round(amount * (Model.DiscountPercent.Value / 100.0m), _currencyDivisibility) | ||
: 0; | ||
} | ||
|
||
private decimal GetTip() | ||
{ | ||
if (Model.TipPercent is > 0) { | ||
var amount = GetAmount() - GetDiscount(); | ||
return Math.Round(amount * (Model.TipPercent.Value / 100.0m), CurrencyDivisibility); | ||
return Math.Round(amount * (Model.TipPercent.Value / 100.0m), _currencyDivisibility); | ||
} | ||
return Model.Tip is > 0 ? Math.Round(Model.Tip.Value, CurrencyDivisibility) : 0.0m; | ||
return Model.Tip is > 0 ? Math.Round(Model.Tip.Value, _currencyDivisibility) : 0.0m; | ||
} | ||
|
||
private decimal GetTotal() | ||
|
@@ -296,8 +360,8 @@ | |
{ | ||
if (CurrencyCode is "BTC" or "SATS") return FormatCrypto(value, withSymbol); | ||
try { | ||
var symbol = withSymbol ? $" {CurrencySymbol ?? CurrencyCode}" : ""; | ||
return $"{Formatted(value, CurrencyDivisibility)}{symbol}"; | ||
var formatted = value.ToString("C", CurrencyInfo); | ||
return withSymbol ? formatted : formatted.Replace(_currencySymbol, "").Trim(); | ||
} | ||
catch (Exception) | ||
{ | ||
|
@@ -306,11 +370,11 @@ | |
} | ||
|
||
private string FormatCrypto(decimal value, bool withSymbol) { | ||
var symbol = withSymbol ? $" {CurrencySymbol ?? CurrencyCode}" : ""; | ||
return $"{Formatted(value, CurrencyDivisibility)}{symbol}"; | ||
var symbol = withSymbol ? $" {_currencySymbol}" : ""; | ||
return $"{FormattedInvariant(value, _currencyDivisibility)}{symbol}"; | ||
} | ||
|
||
private string Formatted(decimal value, int divisibility) { | ||
private string FormattedInvariant(decimal value, int divisibility) { | ||
return string.Format(CultureInfo.InvariantCulture, $"{{0:0.{new string('0', divisibility)}}}", value); | ||
} | ||
|
||
|
@@ -326,4 +390,22 @@ | |
|
||
StateHasChanged(); | ||
} | ||
|
||
private async Task LoadRecentTransactions() | ||
{ | ||
_recentTransactionsLoading = true; | ||
StateHasChanged(); | ||
RecentTransactions = null; | ||
_recentTransactionsLoading = false; | ||
StateHasChanged(); | ||
} | ||
|
||
private class RecentTransaction | ||
{ | ||
public string Id { get; set; } | ||
public DateTimeOffset Date { get; set; } | ||
public string Url { get; set; } | ||
public string Price { get; set; } | ||
public string Status { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,21 @@ | ||
@attribute [Route(Routes.PointOfSale)] | ||
@layout SimpleLayout | ||
@using System.Globalization | ||
@inherits Fluxor.Blazor.Web.Components.FluxorComponent | ||
|
||
<PageTitle>Point Of Sale</PageTitle> | ||
|
||
<div class="public-page-wrap"> | ||
<Keypad CurrencyDivisibility="2" CurrencySymbol="€" CurrencyCode="EUR" | ||
<Keypad CurrencyInfo="@CurrencyInfo" CurrencyCode="EUR" | ||
IsDiscountEnabled="true" IsTipEnabled="true" CustomTipPercentages="new []{ 5, 10, 21 }" /> | ||
</div> | ||
|
||
@code { | ||
public NumberFormatInfo CurrencyInfo { get; set; } = new() | ||
{ | ||
CurrencySymbol = "\u20ac", | ||
CurrencyDecimalDigits = 2, | ||
CurrencyDecimalSeparator = ",", | ||
CurrencyGroupSeparator = "." | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.