Skip to content

Commit

Permalink
UI updates
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisreimann committed Jan 16, 2024
1 parent 9e4e059 commit 1bc0e83
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 40 deletions.
5 changes: 0 additions & 5 deletions BTCPayApp.Tests/BTCPayAppTestServer.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Playwright;
Expand Down
144 changes: 113 additions & 31 deletions BTCPayApp.UI/Components/Keypad.razor
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)
Expand Down Expand Up @@ -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>
}
Expand All @@ -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">
Expand All @@ -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', '+' };
Expand All @@ -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
{
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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);
Expand All @@ -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)
{
Expand Down Expand Up @@ -215,7 +279,7 @@
Mode = InputMode.Amount;
}

private JObject GetData()
private string GetData()
{
var data = new JObject
{
Expand All @@ -242,7 +306,7 @@
{
data["tipPercentage"] = Model.TipPercent;
}
return data;
return JsonConvert.SerializeObject(data);
}

private List<InputMode> GetModes()
Expand All @@ -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()
Expand All @@ -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)
{
Expand All @@ -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);
}

Expand All @@ -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; }
}
}
36 changes: 35 additions & 1 deletion BTCPayApp.UI/Components/Keypad.razor.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

/* modes */
#ModeTabs {
min-height: 2.75rem;
Expand All @@ -15,6 +16,7 @@
padding: 0;
position: relative;
border-radius: 0;
border-color: transparent !important;
font-weight: var(--btcpay-font-weight-semibold);
font-size: 24px;
min-height: 3.5rem;
Expand Down Expand Up @@ -55,9 +57,41 @@
min-height: 1.5rem;
}

#RecentTransactionsToggle {
position: absolute;
top: var(--btcpay-space-l);
left: var(--btcpay-space-m);
z-index: 1;
}

#RecentTransactionsToggle .icon {
--btn-icon-size: 2.25em;
}
#RecentTransactionsRefresh[disabled] .icon {
animation: 1.25s linear infinite spinner-border;
}
#RecentTransactions .list-group {
margin: calc(var(--btcpay-modal-padding) * -1);
width: calc(100% + var(--btcpay-modal-padding) * 2);
}

#RecentTransactions .list-group-item {
background-color: transparent;
}

#RecentTransactions .list-group .badge-container {
flex: 0 0 5.125rem;
text-align: right;
}

@media (max-width: 359px) {
#RecentTransactions .list-group .badge-container {
flex-grow: 1;
}
}

/* fix sticky hover effect on mobile browsers */
@media (hover: none) {
.keypad .btn-secondary:hover,
.actions .btn-secondary:hover {
border-color: var(--btcpay-secondary-border-active) !important;
}
Expand Down
13 changes: 12 additions & 1 deletion BTCPayApp.UI/Pages/PointOfSalePage.razor
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 = "."
};
}
4 changes: 4 additions & 0 deletions BTCPayApp.UI/wwwroot/bootstrap/bootstrap.css
Original file line number Diff line number Diff line change
Expand Up @@ -12255,6 +12255,10 @@ ul:not([class]) li {
color: var(--btcpay-body-text-muted) !important;
}

.text-warning {
color: rgba(var(--btcpay-body-text-warning-rgb), var(--btcpay-text-opacity)) !important;
}

/* Modals */
.modal-content {
box-shadow: 0 20px 20px rgba(0, 0, 0, 0.15);
Expand Down
Loading

0 comments on commit 1bc0e83

Please sign in to comment.