btcpayserver/BTCPayServer/Views/UIApps/UpdatePointOfSale.cshtml
d11n 20a9472ee2
Sticky headers (#3416)
* Make headers sticky

Closes #3344.

* Decrease headline margin bottom on mobile

* increases gap

* adds bottom padding

* Update BTCPayServer/Views/UIApps/UpdatePointOfSale.cshtml

* add "_blank" to view action

* Fix markup and tests

* Spacing updates

* Try test fix

* Re-add sticky account header and add test logs for timeout check

* Fix timeout issues

* Apply scroll padding on pages with sticky header

Co-authored-by: dstrukt <gfxdsign@gmail.com>
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2022-02-07 18:37:45 +09:00

427 lines
23 KiB
Plaintext

@using BTCPayServer.Services.Apps
@addTagHelper *, BundlerMinifier.TagHelpers
@model UpdatePointOfSaleViewModel
@{
ViewData.SetActivePage(AppsNavPages.Update, "Update Point of Sale", Model.Id);
}
<form method="post">
<div class="sticky-header-setup"></div>
<header class="sticky-header d-sm-flex align-items-center justify-content-between">
<h2 class="mb-0">@ViewData["Title"]</h2>
<div class="d-flex gap-3 mt-3 mt-sm-0">
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings">Save</button>
<a class="btn btn-secondary" asp-action="ViewPointOfSale" asp-controller="UIAppsPublic" asp-route-appId="@Model.Id" id="ViewApp" target="app_@Model.AppId">View</a>
<a class="btn btn-secondary" asp-action="ListInvoices" asp-controller="UIInvoice" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
</div>
</header>
<script>
const { offsetHeight } = document.querySelector('.sticky-header-setup + .sticky-header');
document.documentElement.style.scrollPaddingTop = `calc(${offsetHeight}px + var(--btcpay-space-m))`;
</script>
<partial name="_StatusMessage" />
<input type="hidden" asp-for="StoreId" />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
<div class="form-group">
<label asp-for="AppName" class="form-label" data-required></label>
<input asp-for="AppName" class="form-control" required />
<span asp-validation-for="AppName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Title" class="form-label" data-required></label>
<input asp-for="Title" class="form-control" required />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Currency" class="form-label"></label>
<input asp-for="Currency" currency-selection class="form-control" placeholder="Use store's default settings" />
<span asp-validation-for="Currency" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-10 col-xxl-constrain">
<div class="form-group mb-0">
<label asp-for="Description" class="form-label"></label>
<textarea asp-for="Description" rows="10" cols="40" class="form-control richtext"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-xxl-constrain">
<partial name="TemplateEditor" model="@(nameof(Model.Template), "Products")" />
<div class="form-group mb-0">
<label asp-for="Template" class="form-label"></label>
<textarea asp-for="Template" rows="10" cols="40" class="js-product-template form-control"></textarea>
<span asp-validation-for="Template" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
<h3 class="mt-5 mb-4">Appearance</h3>
<div class="form-group">
<label asp-for="DefaultView" class="form-label" data-required></label>
<select asp-for="DefaultView" asp-items="@Html.GetEnumSelectList<PosViewType>()" class="form-select" required></select>
<span asp-validation-for="DefaultView" class="text-danger"></span>
<div class="mt-2">
<span class="text-secondary">Choose the point of sale style for your customers.</span>
</div>
</div>
<div class="form-group" id="button-price-text">
<label asp-for="ButtonText" class="form-label" data-required></label>
<input asp-for="ButtonText" class="form-control" required />
<span asp-validation-for="ButtonText" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="RequiresRefundEmail" class="form-label"></label>
<select asp-for="RequiresRefundEmail" asp-items="@Html.GetEnumSelectList<RequiresRefundEmail>()" class="form-select" required></select>
<span asp-validation-for="RequiresRefundEmail" class="text-danger"></span>
</div>
<section id="discounts" class="p-0">
<h3 class="mt-5 mb-4">Discounts</h3>
<div class="form-check mb-4">
<input asp-for="ShowDiscount" type="checkbox" class="form-check-input" />
<label asp-for="ShowDiscount" class="form-check-label"></label>
<span asp-validation-for="ShowDiscount" class="text-danger"></span>
<div class="mt-2">
<span class="text-secondary">Not recommended for customer self-checkout.</span>
</div>
</div>
</section>
<section id="custom-payments" class="p-0">
<h3 class="mt-5 mb-4">Custom Payments</h3>
<div class="form-group mb-4 d-flex align-items-center">
<input asp-for="ShowCustomAmount" type="checkbox" class="btcpay-toggle me-2" data-bs-toggle="collapse" data-bs-target="#CustomAmountSettings" aria-expanded="@Model.ShowCustomAmount" aria-controls="CustomAmountSettings"/>
<label asp-for="ShowCustomAmount" class="form-label mb-0"></label>
<span asp-validation-for="ShowCustomAmount" class="text-danger"></span>
</div>
<div class="collapse @(Model.ShowCustomAmount ? "show" : "")" id="CustomAmountSettings">
<div class="form-group mb-0 pb-3">
<label asp-for="CustomButtonText" class="form-label" data-required></label>
<input asp-for="CustomButtonText" class="form-control" required />
<span asp-validation-for="CustomButtonText" class="text-danger"></span>
</div>
</div>
</section>
<section id="tips" class="p-0">
<h3 class="mt-5 mb-4">Tips</h3>
<div class="form-group d-flex align-items-center mb-0">
<input asp-for="EnableTips" type="checkbox" class="btcpay-toggle me-2" data-bs-toggle="collapse" data-bs-target="#CustomTipsSettings" aria-expanded="@Model.EnableTips" aria-controls="CustomTipsSettings" />
<label asp-for="EnableTips" class="form-label mb-0"></label>
<span asp-validation-for="EnableTips" class="text-danger"></span>
</div>
<div class="collapse @(Model.EnableTips ? "show" : "")" id="CustomTipsSettings">
<div class="form-group pt-3">
<label asp-for="CustomTipText" class="form-label" data-required></label>
<input asp-for="CustomTipText" class="form-control" required />
<span asp-validation-for="CustomTipText" class="text-danger"></span>
</div>
<div class="form-group mb-0">
<label asp-for="CustomTipPercentages" class="form-label"></label>
<input asp-for="CustomTipPercentages" class="form-control" />
<span asp-validation-for="CustomTipPercentages" class="text-danger"></span>
</div>
</div>
</section>
</div>
</div>
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
<h3 class="mt-5 mb-2">Additional Options</h3>
<div class="form-group">
<div class="accordion" id="additional">
<div class="accordion-item">
<h2 class="accordion-header" id="additional-embed-payment-button-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-embed-payment-button" aria-expanded="false" aria-controls="additional-embed-payment-button">
Embed a payment button linking to POS item
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-embed-payment-button" class="accordion-collapse collapse" aria-labelledby="additional-embed-payment-button-header">
<div class="accordion-body">
<p>You can host point of sale buttons in an external website with the following code.</p>
@if (Model.Example1 != null)
{
<span>For anything with a custom amount</span>
<pre class="p-3">@Model.Example1</pre>
}
@if (Model.Example2 != null)
{
<span>For a specific item of your template</span>
<pre class="p-3">@Model.Example2</pre>
}
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="additional-embed-pos-iframe-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-embed-pos-iframe" aria-expanded="false" aria-controls="additional-embed-pos-iframe">
Embed Point of Sale via Iframe
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-embed-pos-iframe" class="accordion-collapse collapse" aria-labelledby="additional-embed-pos-iframe-header">
<div class="accordion-body">
You can embed this POS via an iframe.
@{
var iframe = $"<iframe src='{(Url.Action("ViewPointOfSale", "UIAppsPublic", new { appId = Model.Id }, Context.Request.Scheme))}' style='max-width: 100%; border: 0;'></iframe>";
}
<pre class="p-3">@iframe</pre>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="additional-redirect-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-redirect" aria-expanded="false" aria-controls="additional-redirect">
Redirects
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-redirect" class="accordion-collapse collapse" aria-labelledby="additional-redirect-header">
<div class="accordion-body">
<div class="form-group">
<label asp-for="RedirectUrl" class="form-label"></label>
<input asp-for="RedirectUrl" class="form-control" />
<span asp-validation-for="RedirectUrl" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="RedirectAutomatically" class="form-label"></label>
<select asp-for="RedirectAutomatically" asp-items="Model.RedirectAutomaticallySelectList" class="form-select"></select>
<span asp-validation-for="RedirectAutomatically" class="text-danger"></span>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="additional-notification-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-notification" aria-expanded="false" aria-controls="additional-notification">
Notification URL Callbacks
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-notification" class="accordion-collapse collapse" aria-labelledby="additional-notification-header">
<div class="accordion-body">
<div class="form-group">
<label asp-for="NotificationUrl" class="form-label"></label>
<input asp-for="NotificationUrl" class="form-control" />
<span asp-validation-for="NotificationUrl" class="text-danger"></span>
</div>
<p>A <code>POST</code> callback will be sent to notification with the following form will be sent to <code>notificationUrl</code> once the enough is paid and once again once there is enough confirmations to the payment:</p>
<pre class="p-3">@Model.ExampleCallback</pre>
<p><strong>Never</strong> trust anything but <code>id</code>, <strong>ignore</strong> the other fields completely, an attacker can spoof those, they are present only for backward compatibility reason:</p>
<ul>
<li>Send a <code>GET</code> request to <code>https://btcpay.example.com/invoices/{invoiceId}</code> with <code>Content-Type: application/json; Authorization: Basic YourLegacyAPIkey"</code>, Legacy API key can be created with Access Tokens in Store settings</li>
<li>Verify that the <code>orderId</code> is from your backend, that the <code>price</code> is correct and that <code>status</code> is <code>settled</code></li>
<li>You can then ship your order</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="additional-custom-css-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-custom-css" aria-expanded="false" aria-controls="additional-custom-css">
Custom CSS
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-custom-css" class="accordion-collapse collapse" aria-labelledby="additional-custom-css-header">
<div class="accordion-body">
<div class="form-group">
<label asp-for="CustomCSSLink" class="form-label"></label>
<a href="https://docs.btcpayserver.org/Development/Theme/#2-bootstrap-themes" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<input asp-for="CustomCSSLink" class="form-control" />
<span asp-validation-for="CustomCSSLink" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EmbeddedCSS" class="form-label"></label>
<textarea asp-for="EmbeddedCSS" rows="10" cols="40" class="form-control"></textarea>
<span asp-validation-for="EmbeddedCSS" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
<h3 class="mt-5 mb-4">Other Actions</h3>
<div id="danger-zone">
<a id="DeleteApp" class="btn btn-outline-danger" asp-action="DeleteApp" asp-route-appId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@Model.AppName</strong> and its settings will be permanently deleted." data-confirm-input="DELETE">Delete this app</a>
</div>
<partial name="_Confirm" model="@(new ConfirmModel("Delete app", "This app will be removed from this store.", "Delete"))" />
@section PageHeadContent {
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css" asp-append-version="true">
<bundle name="wwwroot/bundles/pos-admin-bundle.min.css" asp-append-version="true"></bundle>
}
@section PageFootContent {
<partial name="_ValidationScriptsPartial" />
<script id="template-product-item" type="text/template">
<div class="col-sm-4 col-md-3 mb-3">
<div class="card">
{image}
<div class="card-body">
<h6 class="card-title">{title}</h6>
<a href="#" class="js-product-edit btn btn-primary" data-bs-toggle="modal" data-bs-target="#product-modal">Edit</a>
<a href="#" class="js-product-remove btn btn-danger"><i class="fa fa-trash"></i></a>
</div>
</div>
</div>
</script>
<script id="template-product-content" type="text/template">
<div class="mb-3">
<input class="js-product-id" type="hidden" name="id" value="{id}">
<input class="js-product-index" type="hidden" name="index" value="{index}">
<div class="form-group row">
<div class="col-sm-6">
<label class="form-label" data-required>Title</label>
<input type="text" class="js-product-title form-control mb-2" value="{title}" autofocus required />
</div>
<div class="col-sm-3">
<label class="form-label" data-required>Price</label>
<input class="js-product-price form-control mb-2"
inputmode="numeric"
pattern="\d*"
type="number"
value="{price}"
required />
</div>
<div class="col-sm-3">
<label class="form-label">Custom price</label>
<select class="js-product-custom form-select">
{custom}
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">Image</label>
<input type="text" class="js-product-image form-control mb-2" value="{image}" />
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea rows="3" cols="40" class="js-product-description form-control mb-2">{description}</textarea>
</div>
<div class="form-group">
<label class="form-label">Inventory (leave blank to not use inventory feature)</label>
<input type="number" step="1" class="js-product-inventory form-control mb-2" value="{inventory}" />
</div>
<div class="form-group d-flex align-items-center">
<input type="checkbox" class="btcpay-toggle me-2" value="{disabled}" />
<label class="form-label mb-0">Disabled</label>
</div>
</div>
</script>
<script>
const posStyleSelector = document.getElementById('DefaultView');
posStyleSelector.addEventListener('change', function(e) {
handleStyleSelected(e.target.value);
});
function handleStyleSelected(style) {
switch (style) {
case '0': // Item list only
hideDiscountsSection();
hideButtonPriceTextSection();
showCustomPaymentAmountSection();
hideTipsSection();
break;
case '1': // Item list and cart
showDiscountsSection();
showButtonPriceTextSection();
showCustomPaymentAmountSection();
showTipsSection();
break;
case '2': // Keypad only
hideDiscountsSection();
hideButtonPriceTextSection();
hideCustomPaymentAmountSection();
showTipsSection();
break;
}
}
/** Show/hide discounts section */
const discountsSection = document.getElementById('discounts');
function hideDiscountsSection() {
hideElement(discountsSection);
}
function showDiscountsSection() {
showElement(discountsSection);
}
/***************************************/
/** Show/hide button text section */
const buttonPriceTextSection = document.getElementById('button-price-text');
function hideButtonPriceTextSection() {
hideElement(buttonPriceTextSection);
}
function showButtonPriceTextSection() {
showElement(buttonPriceTextSection);
}
/***************************************/
/** Show/hide custom payments amount seciton */
const customPaymentAmountSection = document.getElementById('custom-payments');
function hideCustomPaymentAmountSection() {
hideElement(customPaymentAmountSection);
}
function showCustomPaymentAmountSection() {
showElement(customPaymentAmountSection);
}
/***************************************/
/** Show/hide tips seciton */
const tipsSection = document.getElementById('tips');
function hideTipsSection() {
hideElement(tipsSection);
}
function showTipsSection() {
showElement(tipsSection);
}
/***************************************/
function hideElement(el) {
el.setAttribute('hidden', true);
}
function showElement(el) {
el.removeAttribute('hidden');
}
document.addEventListener("DOMContentLoaded", function() {
handleStyleSelected(posStyleSelector.value);
});
</script>
<bundle name="wwwroot/bundles/pos-admin-bundle.min.js" asp-append-version="true"></bundle>
}