btcpayserver/BTCPayServer/Views/Shared/PointOfSale/Public/VueLight.cshtml

157 lines
10 KiB
Text
Raw Normal View History

@using BTCPayServer.Plugins.PointOfSale.Models
@using BTCPayServer.Services
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using Newtonsoft.Json.Linq
@using BTCPayServer.Client
@inject DisplayFormatter DisplayFormatter
2022-07-18 20:51:53 +02:00
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
@functions {
private string GetItemPriceFormatted(ViewPointOfSaleViewModel.Item item)
{
if (item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup) return "any amount";
if (item.Price == 0) return "free";
var formatted = DisplayFormatter.Currency(item.Price ?? 0, Model.CurrencyCode, DisplayFormatter.CurrencyFormat.Symbol);
return item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum ? $"{formatted} minimum" : formatted;
}
}
<form id="app" method="post" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" v-on:submit="handleFormSubmit" class="d-flex flex-column gap-4 my-auto" v-cloak>
<input type="hidden" name="amount" :value="totalNumeric">
<input type="hidden" name="tip" :value="tipNumeric">
<input type="hidden" name="discount" :value="discountPercentNumeric">
<input type="hidden" name="posdata" :value="posdata">
<div ref="display" class="d-flex flex-column align-items-center px-4 mb-auto">
<div class="fw-semibold text-muted" id="Currency">{{currencyCode}}</div>
<div class="fw-bold lh-sm" ref="amount" v-bind:style="{ fontSize: `${fontSize}px` }" id="Amount">{{ formatCurrency(total, false) }}</div>
<div class="text-muted text-center mt-2" id="Calculation">{{ calculation }}</div>
</div>
<div id="ModeTabs" class="tab-content mb-n2" v-if="showDiscount || enableTips">
<div id="Mode-Discount" class="tab-pane fade px-2" :class="{ show: mode === 'discount', active: mode === 'discount' }" role="tabpanel" aria-labelledby="ModeTablist-Discount" v-if="showDiscount">
<div class="h4 fw-semibold text-muted text-center" id="Discount">
<span class="h3 text-body me-1">{{discountPercent || 0}}%</span> discount
</div>
</div>
<div id="Mode-Tip" class="tab-pane fade px-2" :class="{ show: mode === 'tip', active: mode === 'tip' }" role="tabpanel" aria-labelledby="ModeTablist-Tip" v-if="enableTips">
<div class="btcpay-pills d-flex flex-wrap align-items-center justify-content-center gap-2">
<template v-if="customTipPercentages">
<button
id="Tip-Custom"
type="button"
class="btcpay-pill"
:class="{ active: !tipPercent }"
v-on:click.prevent="tipPercent = null">
<template v-if="tip && tip > 0">{{formatCurrency(tip, true)}}</template>
<template v-else>Custom</template>
</button>
<button
v-for="percentage in customTipPercentages"
type="button"
class="btcpay-pill"
:class="{ active: tipPercent == percentage }"
:id="`Tip-${percentage}`"
v-on:click.prevent="tipPercentage(percentage)">
{{ percentage }}%
</button>
</template>
<div v-else class="h5 fw-semibold text-muted text-center">
Amount<template v-if="tip">: {{formatCurrency(tip, true)}}</template>
</div>
</div>
</div>
</div>
<div id="ModeTablist" class="nav btcpay-pills align-items-center justify-content-center mb-n2 pb-1" role="tablist" v-if="modes.length > 1">
<template v-for="m in modes" :key="m.value">
<input :id="`ModeTablist-${m.type}`" name="mode" :value="m.type" type="radio" role="tab" data-bs-toggle="pill" :data-bs-target="`#Mode-${m.type}`" :disabled="m.type != 'amounts' && amountNumeric == 0" :aria-controls="`Mode-${m.type}`" :aria-selected="mode === m.type" :checked="mode === m.type" v-on:click="mode = m.type">
<label :for="`ModeTablist-${m.type}`">{{ m.title }}</label>
</template>
</div>
<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 || totalNumeric <= 0" id="pay-button">
<div v-if="payButtonLoading" class="spinner-border spinner-border-sm" id="pay-button-spinner" role="status">
2023-11-20 11:18:19 +01:00
<span class="visually-hidden">Loading...</span>
</div>
<template v-else>Charge</template>
</button>
<partial name="PointOfSale/Public/RecentTransactions" model="Model"/>
<button type="button" class="btn btn-link p-1" data-bs-toggle="modal" data-bs-target="#RecentTransactions" id="RecentTransactionsToggle" permission="@Policies.CanViewInvoices">
<vc:icon symbol="nav-invoice"/>
</button>
<button type="button" class="btn btn-link p-1" data-bs-toggle="offcanvas" data-bs-target="#ItemsListOffcanvas" id="ItemsListToggle" aria-controls="ItemsList" v-if="showItems">
<vc:icon symbol="nav-mobile-menu"/>
</button>
<div class="offcanvas offcanvas-end" data-bs-backdrop="static" tabindex="-1" id="ItemsListOffcanvas" aria-labelledby="ItemsListToggle" v-if="showItems">
<div class="offcanvas-header flex-wrap p-3">
<h5 class="offcanvas-title" id="offcanvasExampleLabel">Products</h5>
<button type="button" class="btn btn-sm rounded-pill" :class="{ 'btn-primary': cart.length > 0, 'btn-outline-secondary': cart.length === 0}" data-bs-dismiss="offcanvas" v-text="cart.length > 0 ? 'Apply' : 'Close'"></button>
@if (Model.ShowSearch)
{
<div class="w-100 mt-3">
<input id="SearchTerm" class="form-control rounded-pill" placeholder="Search…" v-model="searchTerm" v-if="showSearch">
</div>
}
@if (Model.ShowCategories)
{
<div id="Categories" ref="categories" v-if="showCategories && allCategories" class="w-100 mt-3 btcpay-pills d-flex flex-wrap align-items-center justify-content-center gap-2" :class="{ 'scrollable': categoriesScrollable }">
<nav class="btcpay-pills d-flex align-items-center gap-3" ref="categoriesNav">
<template v-for="cat in allCategories">
<input :id="`Category-${cat.value}`" type="radio" name="category" autocomplete="off" v-model="displayCategory" :value="cat.value">
<label :for="`Category-${cat.value}`" class="btcpay-pill text-nowrap">{{ cat.text }}</label>
</template>
</nav>
</div>
}
</div>
<div class="offcanvas-body">
<div ref="posItems" id="PosItems">
@for (var index = 0; index < Model.Items.Length; index++)
{
var item = Model.Items[index];
var formatted = GetItemPriceFormatted(item);
var inStock = item.Inventory is null or > 0;
var displayed = item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed && inStock ? "true" : "false";
var categories = new JArray(item.Categories ?? new object[] { });
<div class="posItem p-3" :class="{ 'posItem--inStock': inStock(@index), 'posItem--displayed': @displayed }" data-index="@index" data-search="@Safe.RawEncode(item.Title + " " + item.Description)" data-categories='@Safe.Json(categories)' v-show="@displayed">
<div class="d-flex align-items-start w-100 gap-3">
@if (!string.IsNullOrWhiteSpace(item.Image))
{
<div class="img d-none d-sm-block">
2024-03-22 15:16:59 +01:00
<img src="@item.Image" alt="@item.Title" asp-append-version="true" />
</div>
}
<div class="d-flex flex-column gap-2">
<h5 class="card-title m-0">@Safe.Raw(item.Title)</h5>
<div class="d-flex gap-2 align-items-center">
@if (item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup || item.Price == 0)
{
<span class="fw-semibold badge text-bg-info">@Safe.Raw(char.ToUpper(formatted[0]) + formatted[1..])</span>
}
else
{
<span class="fw-semibold">@Safe.Raw(formatted)</span>
}
@if (item.Inventory.HasValue)
{
<span class="badge text-bg-warning inventory" v-text="inventoryText(@index)">
@(item.Inventory > 0 ? $"{item.Inventory} left" : "Sold out")
</span>
}
</div>
</div>
<div class="d-flex align-items-center gap-2 ms-auto quantities">
<button type="button" v-on:click="updateQuantity(`@Safe.Raw(item.Id)`, -1, true)" class="btn btn-minus" :disabled="getQuantity(`@Safe.Raw(item.Id)`) <= 0">
<span><vc:icon symbol="minus" /></span>
</button>
<div class="quantity text-center fs-5" style="width:2rem">{{ getQuantity(`@Safe.Raw(item.Id)`) }}</div>
<button type="button" v-on:click="updateQuantity(`@Safe.Raw(item.Id)`, +1, true)" class="btn btn-plus" :disabled="!inStock(@index)">
<span><vc:icon symbol="plus" /></span>
</button>
</div>
</div>
</div>
}
</div>
</div>
</div>
</form>