mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-10 09:19:24 +01:00
Keypad: List recent transactions (#5478)
* Keypad: List recent transactions Closes #5379. * UI updates * Optional: No border * Fix class * Decrease keypad max-width
This commit is contained in:
parent
b9b3860e6b
commit
bdf56c0a6f
7 changed files with 134 additions and 13 deletions
|
@ -47,6 +47,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
AppService appService,
|
||||
CurrencyNameTable currencies,
|
||||
StoreRepository storeRepository,
|
||||
InvoiceRepository invoiceRepository,
|
||||
UIInvoiceController invoiceController,
|
||||
FormDataService formDataService,
|
||||
DisplayFormatter displayFormatter)
|
||||
|
@ -54,12 +55,14 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
_currencies = currencies;
|
||||
_appService = appService;
|
||||
_storeRepository = storeRepository;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_invoiceController = invoiceController;
|
||||
_displayFormatter = displayFormatter;
|
||||
FormDataService = formDataService;
|
||||
}
|
||||
|
||||
private readonly CurrencyNameTable _currencies;
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly AppService _appService;
|
||||
private readonly UIInvoiceController _invoiceController;
|
||||
|
@ -521,6 +524,37 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
return View("Views/UIForms/View", viewModel);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpGet("/apps/{appId}/pos/recent-transactions")]
|
||||
public async Task<IActionResult> RecentTransactions(string appId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
var from = DateTimeOffset.UtcNow - TimeSpan.FromDays(3);
|
||||
var invoices = await AppService.GetInvoicesForApp(_invoiceRepository, app, from, new[]
|
||||
{
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.New),
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Paid),
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Confirmed),
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Complete),
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Expired),
|
||||
InvoiceState.ToString(InvoiceStatusLegacy.Invalid)
|
||||
});
|
||||
var recent = invoices
|
||||
.Take(10)
|
||||
.Select(i => new JObject
|
||||
{
|
||||
["id"] = i.Id,
|
||||
["date"] = i.InvoiceTime,
|
||||
["price"] = _displayFormatter.Currency(i.Price, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
["status"] = i.GetInvoiceState().Status.ToModernStatus().ToString(),
|
||||
["url"] = Url.Action(nameof(UIInvoiceController.Invoice), "UIInvoice", new { invoiceId = i.Id })
|
||||
});
|
||||
return Json(recent);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpGet("{appId}/settings/pos")]
|
||||
public async Task<IActionResult> UpdatePointOfSale(string appId)
|
||||
|
|
|
@ -51,10 +51,45 @@
|
|||
<div class="keypad">
|
||||
<button v-for="k in keys" :key="k" :disabled="k === '+' && mode !== 'amounts'" v-on:click.prevent="keyPressed(k)" v-on:dblclick.prevent="doubleClick(k)" type="button" class="btn btn-secondary btn-lg" :data-key="k">{{ k }}</button>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-primary mx-3" type="submit" :disabled="payButtonLoading" id="pay-button">
|
||||
<button class="btn btn-lg btn-primary mx-3" type="submit" :disabled="payButtonLoading || totalNumeric <= 0" id="pay-button">
|
||||
<div v-if="payButtonLoading" class="spinner-border spinner-border-sm" id="pay-button-spinner" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<template v-else>Charge</template>
|
||||
</button>
|
||||
<div class="modal" tabindex="-1" id="RecentTransactions" ref="RecentTransactions" data-bs-backdrop="static" data-url="@Url.Action("RecentTransactions", "UIPointOfSale", new { appId = Model.AppId })">
|
||||
<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" v-on:click="loadRecentTransactions" :disabled="recentTransactionsLoading" id="RecentTransactionsRefresh">
|
||||
<vc:icon symbol="refresh"/>
|
||||
<span v-if="recentTransactionsLoading" 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">
|
||||
<div v-if="recentTransactions.length" class="list-group list-group-flush">
|
||||
<a v-for="t in recentTransactions" :key="t.id" :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">{{displayDate(t.date)}}</span>
|
||||
<span class="flex-grow-1 text-end">{{t.price}}</span>
|
||||
<div class="badge-container">
|
||||
<span class="badge" :class="`badge-${t.status.toLowerCase()}`">{{t.status}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<vc:icon symbol="caret-right" />
|
||||
</a>
|
||||
</div>
|
||||
<p v-else-if="recentTransactionsLoading" class="text-muted my-0">Loading...</p>
|
||||
<p v-else 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>
|
||||
</form>
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
<symbol id="pos-print" viewBox="0 0 24 24" fill="none"><path d="M5 6v13.543l2.333-.914L9.667 20 12 18.629 14.333 20l2.334-1.371 2.333.914V6a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2Z" stroke="currentColor" stroke-width="1.6"/><path d="M8.5 8h7M8.5 11.5h7M8.5 15h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></symbol>
|
||||
<symbol id="pos-static" viewBox="0 0 24 24" fill="none"><path d="M19.05 10.266v6.265a2.244 2.244 0 0 1-2.238 2.238H7.246a2.244 2.244 0 0 1-2.238-2.238v-6.265" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10"/><path d="M9.455 5.262v3.833c0 1.174-.951 2.153-2.126 2.153h-.42a2.613 2.613 0 0 1-2.405-3.664l.56-1.315A1.702 1.702 0 0 1 6.63 5.234l2.825.028ZM14.547 5.258V9.09c0 1.175.95 2.154 2.126 2.154h.42c1.901 0 3.16-1.93 2.405-3.665l-.56-1.314a1.721 1.721 0 0 0-1.566-1.007h-2.825ZM12.002 11.499A2.525 2.525 0 0 1 9.484 8.98V5.29h5.063v3.692c0 1.399-1.147 2.518-2.545 2.518Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10"/></symbol>
|
||||
<symbol id="pull-payments" viewBox="0 0 24 24" fill="none"><path d="M12 20a8 8 0 1 1 0-16 8 8 0 0 1 0 16Zm0-15.19a7.2 7.2 0 0 0 0 14.38A7.2 7.2 0 0 0 12 4.8Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/><path d="M9.48 14.85a.44.44 0 0 1-.3-.14c-.14-.16-.14-.43.05-.57l5.02-4.31c.16-.14.43-.14.57.05.14.17.14.44-.05.57l-5.05 4.29c-.05.08-.16.1-.24.1Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/><path d="M14.39 14.28a.4.4 0 0 1-.41-.4l.1-3.42-3.08-.17a.4.4 0 0 1-.38-.43c0-.22.19-.4.43-.38l3.47.19c.22 0 .38.19.38.4l-.13 3.83c.02.19-.17.38-.38.38Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/></symbol>
|
||||
<symbol id="refresh" viewBox="0 0 24 24" fill="none"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></g></symbol>
|
||||
<symbol id="remove" viewBox="0 0 24 24" fill="none"><path d="M17 11H13V7C13 6.45 12.55 6 12 6C11.45 6 11 6.45 11 7V11H7C6.45 11 6 11.45 6 12C6 12.55 6.45 13 7 13H11V17C11 17.55 11.45 18 12 18C12.55 18 13 17.55 13 17V13H17C17.55 13 18 12.55 18 12C18 11.45 17.55 11 17 11Z" fill="currentColor" transform="rotate(45 12 12)"/></symbol>
|
||||
<symbol id="scan-qr" viewBox="0 0 32 32" fill="none"><path d="M20 .875h10c.621 0 1.125.504 1.125 1.125v10m0 8v10c0 .621-.504 1.125-1.125 1.125H20m-8 0H2A1.125 1.125 0 01.875 30V20m0-8V2C.875 1.379 1.379.875 2 .875h10" stroke="currentColor" stroke-width="1.75" fill="none" fill-rule="evenodd"/></symbol>
|
||||
<symbol id="search" viewBox="0 0 24 24" fill="none"><circle cx="11.5" cy="11.75" r="6.25" stroke="currentColor" stroke-width="1.5"/><path d="M16.5 16.25L19.5 19.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></symbol>
|
||||
|
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
@ -205,7 +205,7 @@ document.addEventListener("DOMContentLoaded",function () {
|
|||
|
||||
window.addEventListener('pagehide', () => {
|
||||
if (this.payButtonLoading) {
|
||||
this.unsetPayButtonLoading();
|
||||
this.payButtonLoading = false;
|
||||
localStorage.removeItem(storageKey('cart'));
|
||||
}
|
||||
})
|
||||
|
|
|
@ -85,9 +85,6 @@ const posCommon = {
|
|||
? percentage
|
||||
: null;
|
||||
},
|
||||
unsetPayButtonLoading () {
|
||||
this.payButtonLoading = false
|
||||
},
|
||||
formatCrypto (value, withSymbol) {
|
||||
const symbol = withSymbol ? ` ${this.currencySymbol || this.currencyCode}` : ''
|
||||
const { divisibility } = this.currencyInfo
|
||||
|
|
|
@ -1,5 +1,40 @@
|
|||
#PosKeypad {
|
||||
--wrap-max-width: 575px;
|
||||
--wrap-max-width: 500px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
/* modes */
|
||||
|
@ -19,6 +54,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;
|
||||
|
@ -79,7 +115,6 @@
|
|||
}
|
||||
/* 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;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,10 @@ document.addEventListener("DOMContentLoaded",function () {
|
|||
fontSize: displayFontSize,
|
||||
defaultFontSize: displayFontSize,
|
||||
keys: ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', '0', '+'],
|
||||
amounts: [null]
|
||||
amounts: [null],
|
||||
recentTransactions: [],
|
||||
recentTransactionsLoading: false,
|
||||
dateFormatter: new Intl.DateTimeFormat('default', { dateStyle: 'short', timeStyle: 'short' })
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -117,15 +120,31 @@ document.addEventListener("DOMContentLoaded",function () {
|
|||
// clear completely
|
||||
this.clear();
|
||||
}
|
||||
},
|
||||
closeModal() {
|
||||
bootstrap.Modal.getInstance(this.$refs.RecentTransactions).hide();
|
||||
},
|
||||
displayDate(val) {
|
||||
const date = new Date(val);
|
||||
return this.dateFormatter.format(date);
|
||||
},
|
||||
async loadRecentTransactions() {
|
||||
this.recentTransactionsLoading = true;
|
||||
const { url } = this.$refs.RecentTransactions.dataset;
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
this.recentTransactions = await response.json();
|
||||
}
|
||||
this.recentTransactionsLoading = false;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
/** We need to unset state in case user clicks the browser back button */
|
||||
window.addEventListener('pagehide', this.unsetPayButtonLoading)
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('pagehide', this.unsetPayButtonLoading)
|
||||
// We need to unset state in case user clicks the browser back button
|
||||
window.addEventListener('pagehide', () => { this.payButtonLoading = false })
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.RecentTransactions.addEventListener('show.bs.modal', this.loadRecentTransactions);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue