Revert "Payment redesign" (#1962)

This commit is contained in:
Andrew Camilleri 2020-10-08 08:37:18 +02:00 committed by GitHub
parent 182d67881d
commit 4174fa648d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 555 additions and 566 deletions

View file

@ -806,7 +806,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear(); s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter); s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
s.AssertHappyMessage(); s.AssertHappyMessage();
Assert.Contains("Awaiting Approval", s.Driver.PageSource); Assert.Contains("AwaitingApproval", s.Driver.PageSource);
var viewPullPaymentUrl = s.Driver.Url; var viewPullPaymentUrl = s.Driver.Url;
// This one should have nothing // This one should have nothing
@ -848,7 +848,8 @@ namespace BTCPayServer.Tests
s.Driver.Navigate().GoToUrl(viewPullPaymentUrl); s.Driver.Navigate().GoToUrl(viewPullPaymentUrl);
txs = s.Driver.FindElements(By.ClassName("transaction-link")); txs = s.Driver.FindElements(By.ClassName("transaction-link"));
Assert.Equal(2, txs.Count); Assert.Equal(2, txs.Count);
Assert.Contains("In Progress", s.Driver.PageSource); Assert.Contains("InProgress", s.Driver.PageSource);
await s.Server.ExplorerNode.GenerateAsync(1); await s.Server.ExplorerNode.GenerateAsync(1);

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer; using BTCPayServer;
using BTCPayServer.Data; using BTCPayServer.Data;
@ -70,13 +69,13 @@ namespace BTCPayServer.Controllers
CurrencyData = cd, CurrencyData = cd,
LastUpdated = DateTime.Now, LastUpdated = DateTime.Now,
Payouts = payouts Payouts = payouts
.Select(entity => new ViewPullPaymentModel.PayoutLine .Select(entity => new ViewPullPaymentModel.PayoutLine()
{ {
Id = entity.Entity.Id, Id = entity.Entity.Id,
Amount = entity.Blob.Amount, Amount = entity.Blob.Amount,
AmountFormatted = _currencyNameTable.FormatCurrency(entity.Blob.Amount, blob.Currency), AmountFormatted = _currencyNameTable.FormatCurrency(entity.Blob.Amount, blob.Currency),
Currency = blob.Currency, Currency = blob.Currency,
Status = Regex.Replace(entity.Entity.State.ToString(), "(\\B[A-Z])", " $1"), Status = entity.Entity.State.ToString(),
Destination = entity.Blob.Destination.Address.ToString(), Destination = entity.Blob.Destination.Address.ToString(),
Link = GetTransactionLink(_networkProvider.GetNetwork<BTCPayNetwork>(entity.Entity.GetPaymentMethodId().CryptoCode), entity.TransactionId), Link = GetTransactionLink(_networkProvider.GetNetwork<BTCPayNetwork>(entity.Entity.GetPaymentMethodId().CryptoCode), entity.TransactionId),
TransactionId = entity.TransactionId TransactionId = entity.TransactionId

View file

@ -29,9 +29,9 @@
@if (env.OnionUrl != null) @if (env.OnionUrl != null)
{ {
<div class="text-center"> <div class="text-center">
<a href="@env.OnionUrl" target="_onion" class="btn btn-sm btn-outline-onion d-inline-flex align-items-center text-nowrap p-2" data-clipboard="@env.OnionUrl" style="min-width:117px;"> <a href="@env.OnionUrl" target="_onion" class="btn btn-sm btn-outline-onion d-inline-flex align-items-center text-nowrap p-2" data-clipboard="@env.OnionUrl">
<img src="~/img/icons/onion-purple.svg" height="20" class="mr-2" asp-append-version="true" /> <img src="~/img/icons/onion-purple.svg" height="20" class="mr-2" asp-append-version="true" />
<span data-clipboard-confirm="Copied URL ✔">Copy Tor URL</span> Copy Tor URL
</a> </a>
</div> </div>
} }

View file

@ -33,9 +33,9 @@
@if (env.OnionUrl != null) @if (env.OnionUrl != null)
{ {
<div class="text-center"> <div class="text-center">
<a href="@env.OnionUrl" target="_onion" class="btn btn-sm btn-outline-onion d-inline-flex align-items-center text-nowrap p-2" data-clipboard="@env.OnionUrl" style="min-width:117px;"> <a href="@env.OnionUrl" target="_onion" class="btn btn-sm btn-outline-onion d-inline-flex align-items-center text-nowrap p-2" data-clipboard="@env.OnionUrl">
<img src="~/img/icons/onion-purple.svg" height="20" class="mr-2" asp-append-version="true" /> <img src="~/img/icons/onion-purple.svg" height="20" class="mr-2" asp-append-version="true" />
<span data-clipboard-confirm="Copied URL ✔">Copy Tor URL</span> Copy Tor URL
</a> </a>
</div> </div>
} }

View file

@ -33,8 +33,9 @@
{ {
<style type="text/css"> <style type="text/css">
body { body {
background: rgba(25, 25, 25, 0.9); background: rgba(55, 58, 60, 0.4);
} }
.close-icon { .close-icon {
display: flex; display: flex;
} }
@ -43,25 +44,25 @@
</head> </head>
<body> <body>
<noscript> <noscript>
<div style="padding:2em;text-align:center;"> <center style="padding: 2em">
<h2>Javascript is currently disabled in your browser.</h2> <h2>Javascript is currently disabled in your browser.</h2>
<h5>Please enable Javascript and refresh this page for the best experience.</h5> <h5>Please enable Javascript and refresh this page for the best experience.</h5>
<p>Alternatively, click below to continue to our HTML-only invoice.</p> <p>Alternatively, click below to continue to our HTML-only invoice.</p>
<a href="/invoice-noscript?id=@Model.InvoiceId" style="text-decoration:underline;color:blue"> <a href="/invoice-noscript?id=@Model.InvoiceId" style="text-decoration: underline; color: blue">
Continue to javascript-disabled invoice &gt; Continue to javascript-disabled invoice &gt;
</a> </a>
</div> </center>
</noscript> </noscript>
<!--[if lte IE 8]> <!--[if lte IE 8]>
<div style="padding:2em;text-align:center;"> <center style="padding: 2em">
<form action="/invoice-noscript" method="GET"> <form action="/invoice-noscript" method="GET">
<button style="text-decoration: underline; color: blue">Continue to legacy browser compatible invoice page <button style="text-decoration: underline; color: blue">Continue to legacy browser compatible invoice page
</button> </button>
</form> </form>
</div> </center>
<![endif]--> <![endif]-->
<invoice> <invoice>

View file

@ -0,0 +1,175 @@
@model BTCPayServer.Models.PaymentRequestViewModels.ViewPaymentRequestViewModel
<div class="container">
<div class="row w-100 p-0 m-0" style="height: 100vh">
<div class="mx-auto my-auto w-100">
<div class="card">
<h1 class="card-header px-3">
@Model.Title
<span class="text-muted float-right text-center">@Model.Status</span>
</h1>
<div class="card-body px-0 pt-0 pb-0">
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
<table class="table table-light mb-0">
<tbody>
<tr>
<td class="px-3 h2 text-muted">Request amount:</td>
<td class="px-3 h2 text-nowrap text-right">@Model.AmountFormatted</td>
</tr>
<tr>
<td class="px-3 h2 text-muted">Paid so far:</td>
<td class="px-3 h2 text-nowrap text-right">@Model.AmountCollectedFormatted</td>
</tr>
<tr>
<td class="px-3 h2 text-muted">Amount due:</td>
<td class="px-3 h2 text-nowrap text-right">@Model.AmountDueFormatted</td>
</tr>
</tbody>
</table>
@if (Model.Description != null && Model.Description != "" && Model.Description != "<br>")
{
<div class="w-100 px-3 pt-4 pb-3">@Safe.Raw(Model.Description)</div>
}
</div>
<div class="col-sm-12 col-md-12 col-lg-6 pt-2">
<div class="table-responsive">
<table class="table border-top-0">
<thead>
<tr>
<th class="border-top-0" scope="col">Invoice #</th>
<th class="border-top-0">Price</th>
<th class="border-top-0">Expiry</th>
<th class="border-top-0">Status</th>
</tr>
</thead>
<tbody>
@if (Model.Invoices == null && !Model.Invoices.Any())
{
<tr>
<td colspan="4" class="text-center">No payments made yet</td>
</tr>
}
else
{
foreach (var invoice in Model.Invoices)
{
<tr class="bg-light">
<td scope="row">@invoice.Id</td>
<td>@invoice.Amount @invoice.Currency</td>
<td>@invoice.ExpiryDate.ToString("g")</td>
<td>@invoice.Status</td>
</tr>
if (invoice.Payments != null && invoice.Payments.Any())
{
<tr class="bg-light">
<td colspan="4" class=" px-2 py-1 border-top-0">
<div class="table-responsive">
<table class="table table-bordered">
<tr>
<th class="p-1" style="max-width: 300px">Tx Id</th>
<th class="p-1">Payment Method</th>
<th class="p-1">Amount</th>
<th class="p-1">Link</th>
</tr>
@foreach (var payment in invoice.Payments)
{
<tr class="d-flex">
<td class="p-1 m-0 d-print-none d-block" style="max-width: 300px">
<div style="width: 100%; overflow-x: auto; overflow-wrap: initial;">@payment.Id</div>
</td>
<td class="p-1 m-0 d-none d-print-table-cell" style="max-width: 150px;">
@payment.Id
</td>
<td class="p-1">@payment.PaymentMethod</td>
<td class="p-1">@payment.Amount</td>
<td class="p-1 d-print-none">
@if (!string.IsNullOrEmpty(payment.Link))
{
<a :href="@payment.Link" target="_blank">Link</a>
}
</td>
<td class="p-1 d-none d-print-table-cell" style="max-width: 150px;">
@payment.Link
</td>
</tr>
}
</table>
</div>
</td>
</tr>
}
}
}
@if (Model.IsPending && !Model.Archived)
{
<tr>
<td colspan="4" class="text-center">
@if (Model.AllowCustomPaymentAmounts && !Model.AnyPendingInvoice)
{
<form method="get" asp-action="PayPaymentRequest">
<div class="input-group m-auto" style="max-width: 250px">
<input
class="form-control"
type="number"
name="amount"
value="@Model.AmountDue"
max="@Model.AmountDue"
step="any"
placeholder="Amount"
required>
<div class="input-group-append">
<span class='input-group-text'>@Model.Currency.ToUpper()</span>
<button
class="btn btn-primary"
type="submit">
Pay now
</button>
</div>
</div>
</form>
}
else
{
<a class="btn btn-primary btn-lg d-print-none mt-1" asp-action="PayPaymentRequest">
Pay now
</a>
if (Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)
{
<form method="get" asp-action="CancelUnpaidPendingInvoice">
<button class="btn btn-secondary btn-lg mt-1" type="submit">
Cancel current invoice</button>
</form>
}
}
</td>
</tr>
}else if (Model.Archived)
{
<tr>
<td colspan="4" class="text-center">
<button type="button" class="btn btn-secondary" disabled>Archived</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card-footer text-muted d-flex justify-content-between">
<div >Updated @Model.LastUpdated.ToString("g")</div>
<div >
<span class="text-muted">Powered by </span><a href="https://btcpayserver.org" target="_blank">BTCPay Server</a>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,7 +1,6 @@
@model BTCPayServer.Models.PaymentRequestViewModels.ViewPaymentRequestViewModel @model BTCPayServer.Models.PaymentRequestViewModels.ViewPaymentRequestViewModel
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@inject BTCPayServer.Services.BTCPayServerEnvironment env
@inject BTCPayServer.HostedServices.CssThemeManager themeManager @inject BTCPayServer.HostedServices.CssThemeManager themeManager
@{ @{
ViewData["Title"] = Model.Title; ViewData["Title"] = Model.Title;
@ -9,313 +8,221 @@
} }
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" @(env.IsDeveloping ? " data-devenv" : "")> <html class="h-100">
<head> <head>
<title>@Model.Title</title> <title>@Model.Title</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="robots" content="noindex,nofollow"> <meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<link href="@Context.Request.GetRelativePathOrAbsolute(themeManager.BootstrapUri)" rel="stylesheet" /> <link href="@Context.Request.GetRelativePathOrAbsolute(themeManager.BootstrapUri)" rel="stylesheet" />
<link href="@Context.Request.GetRelativePathOrAbsolute(themeManager.ThemeUri)" rel="stylesheet" /> <link href="@Context.Request.GetRelativePathOrAbsolute(themeManager.ThemeUri)" rel="stylesheet" />
<bundle name="wwwroot/bundles/payment-request-bundle.min.css" asp-append-version="true"></bundle>
@if (Model.CustomCSSLink != null) @if (Model.CustomCSSLink != null)
{ {
<link href="@Model.CustomCSSLink" rel="stylesheet" /> <link href="@Model.CustomCSSLink" rel="stylesheet" />
} }
<script type="text/javascript"> @if (!Context.Request.Query.ContainsKey("simple"))
var srvModel = @Safe.Json(Model); {
</script> <script type="text/javascript">
<bundle name="wwwroot/bundles/payment-request-bundle.min.js" asp-append-version="true"></bundle> var srvModel = @Safe.Json(Model);
@*We need to make sure btcpay.js is not bundled, else it will not work if there is a RootPath*@ </script>
<script src="~/modal/btcpay.js" asp-append-version="true"></script> <bundle name="wwwroot/bundles/payment-request-bundle-1.min.js" asp-append-version="true"></bundle>
<bundle name="wwwroot/bundles/payment-request-bundle-2.min.js" asp-append-version="true"></bundle>
@*We need to make sure btcpay.js is not bundled, else it will not work if there is a RootPath*@
<script src="~/modal/btcpay.js" asp-append-version="true"></script>
}
<bundle name="wwwroot/bundles/payment-request-bundle.min.css" asp-append-version="true"></bundle>
@Safe.Raw(Model.EmbeddedCSS) @Safe.Raw(Model.EmbeddedCSS)
<noscript>
<style>
.hide-when-js, [v-cloak] { display: block !important; }
.only-for-js { display: none !important; }
</style>
</noscript>
</head> </head>
<body class="h-100"> <body>
<div id="app" class="h-100 d-flex flex-column">
<nav id="mainNav" class="navbar sticky-top py-3 py-lg-4 d-print-block"> <partial name="_StatusMessage" />
<div class="container"> @if (Context.Request.Query.ContainsKey("simple"))
<div class="row align-items-center" style="width:calc(100% + 30px)"> {
<div class="col-12 col-md-8 col-lg-9"> @await Html.PartialAsync("MinimalPaymentRequest", Model)
<div class="row"> }
<div class="col col-12 col-lg-8"> else
<h1 class="h3" v-text="srvModel.title">@Model.Title</h1> {
</div> <noscript>
<div class="col col-12 col-sm-6 col-lg-8 d-flex align-items-center"> @await Html.PartialAsync("MinimalPaymentRequest", Model)
<span class="text-muted text-nowrap">Last Updated</span> </noscript>
&nbsp;
<span class="text-nowrap" v-text="lastUpdated" v-cloak>@Model.LastUpdated.ToString("g")</span> <div class="container" id="app" v-cloak>
<button type="button" class="btn btn-link d-none d-lg-inline-block d-print-none border-0 p-0 ml-4 only-for-js" v-on:click="window.print" v-cloak> <div class="row w-100 p-0 m-0" style="height: 100vh">
Print <div class="mx-auto my-auto w-100">
</button> <div class="card">
<button type="button" class="btn btn-link d-none d-lg-inline-block d-print-none border-0 p-0 ml-4 only-for-js" v-on:click="copyLink" v-cloak> <h1 class="card-header px-3">
Copy Link {{srvModel.title}}
</button>
</div> <span class="text-muted float-right text-center">
<div class="col col-12 col-sm-6 text-sm-right col-lg-4 mt-lg-n4 pt-lg-1 d-print-none"> <template v-if="settled">Settled</template>
@if (Model.IsPending && !Model.Archived && Model.ExpiryDate.HasValue)
{
<noscript>@Model.Status</noscript>
}
<template v-if="srvModel.isPending && !srvModel.archived && endDiff">
<span class="text-muted">Expires in</span> {{endDiff}}
</template>
</div>
</div>
</div>
<div class="col-12 pt-3 pb-2 col-md-4 py-md-0 col-lg-3 d-print-none">
<noscript>
@if (Model.IsPending && !Model.Archived)
{
@if (Model.AllowCustomPaymentAmounts && !Model.AnyPendingInvoice)
{
<form method="get" asp-action="PayPaymentRequest" asp-route-id="@Model.Id">
<div class="row">
<div class="col col-12 col-sm-6 col-md-12">
<div class="input-group">
<input type="number" class="form-control text-right hide-number-spin" name="amount" value="@Model.AmountDue" @if (!Model.AllowCustomPaymentAmounts) { @("readonly") } max="@Model.AmountDue" step="any" placeholder="Amount" required>
<div class="input-group-append">
<span class="input-group-text">@Model.Currency.ToUpper()</span>
</div>
</div>
</div>
<div class="col mt-2 col-12 col-sm-6 mt-sm-0 col-md-12 mt-md-2">
<button class="btn btn-primary w-100 text-nowrap" type="submit">Pay Invoice</button>
</div>
</div>
</form>
}
else
{
<a class="btn btn-primary d-inline-block w-100 text-nowrap @if (!(Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)) { @("btn-lg") }" asp-action="PayPaymentRequest" asp-route-id="@Model.Id">
Pay Invoice
</a>
if (Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)
{
<form method="get" asp-action="CancelUnpaidPendingInvoice" asp-route-id="@Model.Id" class="mt-2">
<button class="btn btn-outline-secondary w-100 text-nowrap" type="submit">Cancel Invoice</button>
</form>
}
}
}
else
{
<div class="h2 text-md-right">
<span class="badge @if (Model.Status == "Settled") { @("badge-primary") } else if (Model.Status == "Expired") { @("badge-danger") } else { @("badge-info") }">
@Model.Status
@if (Model.Archived)
{
<span>(archived)</span>
}
</span>
</div>
}
</noscript>
<template v-if="srvModel.isPending && !srvModel.archived" class="d-print-none">
<template v-if="srvModel.allowCustomPaymentAmounts && !srvModel.anyPendingInvoice">
<form v-on:submit="submitCustomAmountForm">
<div class="row">
<div class="col col-12 col-sm-6 col-md-12">
<div class="input-group">
<input type="number" class="form-control text-right hide-number-spin" v-model="customAmount" :readonly="!srvModel.allowCustomPaymentAmounts" :max="srvModel.amountDue" placeholder="Amount" required>
<div class="input-group-append">
<span class="input-group-text">{{currency}}</span>
</div>
</div>
</div>
<div class="col mt-2 col-12 col-sm-6 mt-sm-0 col-md-12 mt-md-2">
<button class="btn btn-primary w-100 text-nowrap" v-bind:class="{ 'btn-disabled': loading}" :disabled="loading" type="submit">
<div v-if="loading" class="spinner-grow spinner-grow-sm" role="status">
<span class="sr-only">Loading...</span>
</div>
Pay Invoice
</button>
</div>
</div>
</form>
</template>
<template v-else> <template v-else>
<button class="btn btn-primary w-100 d-flex align-items-center justify-content-center text-nowrap" :class="{ 'btn-lg': !(srvModel.anyPendingInvoice && !srvModel.pendingInvoiceHasPayments)}" v-on:click="pay(null)" :disabled="loading"> <template v-if="ended">Request Expired</template>
<div v-if="loading" class="spinner-grow spinner-grow-sm mr-2" role="status"> <template v-else-if="endDiff">Expires in {{endDiff}}</template>
<span class="sr-only">Loading...</span> <template v-else>{{srvModel.status}}</template>
</div>
<span>Pay Invoice</span>
</button>
<button class="btn btn-outline-secondary mt-2 w-100 d-flex align-items-center justify-content-center text-nowrap" v-if="srvModel.anyPendingInvoice && !srvModel.pendingInvoiceHasPayments" v-on:click="cancelPayment()" :disabled="loading">
<span v-if="loading" class="spinner-grow spinner-grow-sm mr-2" role="status">
<span class="sr-only">Loading...</span>
</span>
<span>Cancel Invoice</span>
</button>
</template> </template>
</template> </span>
<template v-else> </h1>
<div class="h2 text-md-right"> <div class="card-body px-0 pt-0 pb-0">
<span class="badge" :class="{ 'badge-primary': srvModel.status === 'Settled', 'badge-danger': srvModel.status === 'Expired', 'badge-info': (srvModel.status !== 'Settled' && srvModel.status !== 'Expired') }"> <div class="row">
{{srvModel.status}} <div class="col-sm-12 col-md-12 col-lg-6">
<span v-if="srvModel.archived">(archived)</span> <table class="table table-light mb-0">
</span> <tbody>
<tr>
<td class="px-3 h2 text-muted">Request amount:</td>
<td class="px-3 h2 text-nowrap text-right">{{srvModel.amountFormatted}}</td>
</tr>
<tr>
<td class="px-3 h2 text-muted">Paid so far:</td>
<td class="px-3 h2 text-nowrap text-right">{{srvModel.amountCollectedFormatted}}</td>
</tr>
<tr>
<td class="px-3 h2 text-muted">Amount due:</td>
<td class="px-3 h2 text-nowrap text-right">{{srvModel.amountDueFormatted}}</td>
</tr>
</tbody>
</table>
<div
v-if="srvModel.description && srvModel.description !== '' && srvModel.description !== '<br>'"
v-html="srvModel.description"
class="w-100 px-3 pt-4 pb-3"
></div>
</div> </div>
</template> <div class="col-sm-12 col-md-12 col-lg-6">
</div> <div class="table-responsive">
</div> <table class="table table-light border-top-0 ">
</div>
</nav>
<main class="flex-grow-1 py-4">
<div class="container">
@await Html.PartialAsync("_StatusMessage", new ViewDataDictionary(ViewData){ { "Margin", "mb-4" } })
<div class="row">
<div class="col col-12 col-lg-6 mb-4">
<div class="jumbotron h-100 m-0 p-sm-5">
<h2 class="h4 mb-3">Invoice Summary</h2>
<div v-html="srvModel.description">
@if (!string.IsNullOrEmpty(Model.Description) && Model.Description != "<br>")
{
@Safe.Raw(Model.Description)
}
</div>
</div>
</div>
<div class="col col-12 col-lg-6 mb-4">
<div class="jumbotron h-100 m-0 p-sm-5">
<h2 class="h4 mb-3">Payment Details</h2>
<dl class="mb-0 mt-md-4">
<div class="d-flex flex-column mb-4">
<dt class="h4 font-weight-normal text-nowrap text-primary order-2 order-sm-1 mb-0" v-text="srvModel.amountDueFormatted">@Model.AmountDueFormatted</dt>
<dd class="text-muted order-1 order-sm-2 mb-1">Amount due</dd>
</div>
<div class="progress bg-light d-none d-sm-flex mb-sm-4 d-print-none" style="height:5px">
<div class="progress-bar bg-primary" role="progressbar" style="width:@((Model.AmountCollected/Model.Amount)*100)%" v-bind:style="{ width: (srvModel.amountCollected/srvModel.amount*100) + '%' }"></div>
</div>
<div class="d-flex flex-column mb-4 d-sm-inline-flex mb-sm-0">
<dt class="h4 font-weight-normal text-nowrap order-2 order-sm-1 mb-0" v-text="srvModel.amountCollectedFormatted">@Model.AmountCollectedFormatted</dt>
<dd class="text-muted order-1 order-sm-2 mb-1">Amount paid</dd>
</div>
<div class="d-flex flex-column mb-0 d-sm-inline-flex float-sm-right">
<dt class="h4 font-weight-normal text-nowrap order-2 order-sm-1 mb-0" v-text="srvModel.amountFormatted">@Model.AmountFormatted</dt>
<dd class="text-muted text-sm-right order-1 order-sm-2 mb-1">Total requested</dd>
</div>
</dl>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="jumbotron h-100 m-0 p-sm-5">
<h2 class="h4 mb-3">Payment History</h2>
<div class="table-responsive">
<noscript>
@if (Model.Invoices == null || !Model.Invoices.Any())
{
<p class="text-muted">No payments made yet.</p>
}
else
{
<table class="table my-0">
<thead>
<tr class="table-borderless">
<th class="font-weight-normal text-secondary" scope="col">Invoice Id</th>
<th class="font-weight-normal text-secondary">Price</th>
<th class="font-weight-normal text-secondary">Expiry</th>
<th class="font-weight-normal text-secondary text-right">Status</th>
</tr>
</thead>
<tbody>
@foreach (var invoice in Model.Invoices)
{
<tr>
<td>@invoice.Id</td>
<td>@invoice.Amount @invoice.Currency</td>
<td>@invoice.ExpiryDate.ToString("g")</td>
<td class="text-right">@invoice.Status</td>
</tr>
if (invoice.Payments != null && invoice.Payments.Any())
{
<tr class="table-borderless table-light">
<th colspan="2" class="pl-3 font-weight-normal text-secondary">TX Id</th>
<th class="font-weight-normal text-secondary">Payment Method</th>
<th class="font-weight-normal text-secondary text-right">Amount</th>
</tr>
@foreach (var payment in invoice.Payments)
{
<tr class="table-borderless table-light">
<td colspan="2" class="pl-3 text-break">
@if (!string.IsNullOrEmpty(payment.Link))
{
<a href="@payment.Link" target="_blank">@payment.Id</a>
}
else
{
<span>@payment.Id</span>
}
</td>
<td>@payment.PaymentMethod</td>
<td class="text-right">@payment.Amount</td>
</tr>
}
}
}
</tbody>
</table>
}
</noscript>
<template v-if="!srvModel.invoices || srvModel.invoices.length == 0">
<p class="text-muted">No payments made yet.</p>
</template>
<template v-else v-for="invoice of srvModel.invoices" :key="invoice.id">
<table class="table my-0">
<thead> <thead>
<tr class="table-borderless"> <tr>
<th class="font-weight-normal text-secondary" scope="col">Invoice Id</th> <th class="border-top-0" scope="col">Invoice #</th>
<th class="font-weight-normal text-secondary">Price</th> <th class="border-top-0">Price</th>
<th class="font-weight-normal text-secondary">Expiry</th> <th class="border-top-0">Expiry</th>
<th class="font-weight-normal text-secondary text-right">Status</th> <th class="border-top-0">Status</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-if="!srvModel.invoices || srvModel.invoices.length == 0">
<td colspan="4" class="text-center">No payments made yet</td>
</tr>
<template v-else v-for="invoice of srvModel.invoices" :key="invoice.id">
<tr> <tr>
<td>{{invoice.id}}</td> <td scope="row">{{invoice.id}}</td>
<td>{{invoice.amountFormatted}}</td> <td>{{invoice.amountFormatted}}</td>
<td>{{moment(invoice.expiryDate).format('L HH:mm')}}</td> <td>{{moment(invoice.expiryDate).format('L HH:mm')}}</td>
<td class="text-right">{{invoice.status}}</td> <td>{{invoice.status}}</td>
</tr> </tr>
<template v-if="invoice.payments && invoice.payments.length > 0"> <tr v-if="invoice.payments && invoice.payments.length > 0">
<tr class="table-borderless table-light"> <td colspan="4" class=" px-2 py-1 border-top-0">
<th colspan="2" class="pl-3 font-weight-normal text-secondary">TX Id</th>
<th class="font-weight-normal text-secondary">Payment Method</th> <div class="table-responsive">
<th class="font-weight-normal text-secondary text-right">Amount</th> <table class="table table-bordered">
</tr> <tr>
<tr v-for="payment of invoice.payments" class="table-borderless table-light"> <th class="p-1" style="max-width: 300px">Tx Id</th>
<td colspan="2" class="pl-3 text-break"> <th class="p-1">Payment Method</th>
<a v-if="payment.link" :href="payment.link" target="_blank">{{payment.id}}</a> <th class="p-1">Amount</th>
<span v-else>{{payment.id}}</span> <th class="p-1">Link</th>
</td> </tr>
<td>{{formatPaymentMethod(payment.paymentMethod)}}</td> <tr v-for="payment of invoice.payments">
<td class="text-right">{{payment.amount.noExponents()}}</td> <td class="p-1 m-0 d-print-none d-block" style="max-width: 300px">
</tr> <div style="width: 100%; overflow-x: auto; overflow-wrap: initial;">{{payment.id}}</div>
</template> </td>
<td class="p-1 m-0 d-none d-print-table-cell" style="max-width: 150px;">
{{payment.id}}
</td>
<td class="p-1">{{formatPaymentMethod(payment.paymentMethod)}}</td>
<td class="p-1">{{payment.amount.noExponents()}}</td>
<td class="p-1 d-print-none">
<a v-if="payment.link" :href="payment.link" target="_blank">Link</a>
</td>
<td class="p-1 d-none d-print-table-cell" style="max-width: 150px;">
{{payment.link}}
</td>
</tr>
</table>
</div>
</td>
</tr>
</template>
<tr v-if="srvModel.archived">
<td colspan="4" class="text-center">
<button type="button" class="btn btn-secondary" disabled>Archived</button>
</td>
</tr>
<tr v-else-if="!ended && (srvModel.amountDue) > 0" class="d-print-none">
<td colspan="4" class="text-center">
<template v-if="srvModel.allowCustomPaymentAmounts && !srvModel.anyPendingInvoice">
<form v-on:submit="submitCustomAmountForm">
<div class="input-group m-auto" style="max-width: 250px">
<input
:readonly="!srvModel.allowCustomPaymentAmounts"
class="form-control"
type="number"
v-model="customAmount"
:max="srvModel.amountDue"
step="any"
placeholder="Amount"
required>
<div class="input-group-append">
<span class='input-group-text'>{{currency}}</span>
<button
class="btn btn-primary"
v-bind:class="{ 'btn-disabled': loading}"
:disabled="loading"
type="submit">
<div v-if="loading" class="spinner-grow spinner-grow-sm" role="status">
<span class="sr-only">Loading...</span>
</div>
Pay now
</button>
</div>
</div>
</form>
</template>
<template v-else>
<button class="btn btn-primary btn-lg mt-1" v-on:click="pay(null)"
:disabled="loading">
<div v-if="loading" class="spinner-grow spinner-grow-sm" role="status">
<span class="sr-only">Loading...</span>
</div>
Pay now
</button>
<button class="btn btn-secondary btn-lg mt-1"
v-if="srvModel.anyPendingInvoice && !srvModel.pendingInvoiceHasPayments"
v-on:click="cancelPayment()"
:disabled="loading">
<div v-if="loading" class="spinner-grow spinner-grow-sm" role="status">
<span class="sr-only">Loading...</span>
</div>
Cancel current invoice</button>
</template>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</template> </div>
</div> </div>
</div> </div>
</div>
<div class="card-footer text-muted d-flex justify-content-between">
<div>
<span v-on:click="print" class="btn-link d-print-none" style="cursor: pointer"> <span class="fa fa-print"></span> Print</span>
<span>Updated {{lastUpdated}}</span>
</div>
<div>
<span class="text-muted">Powered by </span><a href="https://btcpayserver.org" target="_blank">BTCPay Server</a>
</div>
</div> </div>
</div> </div>
</div> </div>
</main> </div>
<footer class="pt-2 pb-4 d-print-none">
<div class="container text-center">
Powered by <a href="https://btcpayserver.org" target="_blank">BTCPay Server</a>
</div>
</footer>
</div> </div>
}
</body> </body>
</html> </html>

View file

@ -2,14 +2,14 @@
@model BTCPayServer.Models.ViewPullPaymentModel @model BTCPayServer.Models.ViewPullPaymentModel
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@inject BTCPayServer.Services.BTCPayServerEnvironment env
@inject BTCPayServer.HostedServices.CssThemeManager themeManager @inject BTCPayServer.HostedServices.CssThemeManager themeManager
@{ @{
ViewData["Title"] = Model.Title; ViewData["Title"] = Model.Title;
Layout = null; Layout = null;
} }
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" @(env.IsDeveloping ? " data-devenv" : "")> <html class="h-100">
<head> <head>
<title>@ViewData["Title"]</title> <title>@ViewData["Title"]</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
@ -18,189 +18,155 @@
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<link href="@Context.Request.GetRelativePathOrAbsolute(themeManager.BootstrapUri)" rel="stylesheet" /> <link href="@Context.Request.GetRelativePathOrAbsolute(themeManager.BootstrapUri)" rel="stylesheet" />
<link href="@Context.Request.GetRelativePathOrAbsolute(themeManager.ThemeUri)" rel="stylesheet" /> <link href="@Context.Request.GetRelativePathOrAbsolute(themeManager.ThemeUri)" rel="stylesheet" />
<bundle name="wwwroot/bundles/payment-request-bundle.min.css" asp-append-version="true"></bundle>
@if (Model.CustomCSSLink != null) @if (Model.CustomCSSLink != null)
{ {
<link href="@Model.CustomCSSLink" rel="stylesheet" /> <link href="@Model.CustomCSSLink" rel="stylesheet" />
} }
<bundle name="wwwroot/bundles/payment-request-bundle.min.css" asp-append-version="true"></bundle>
@Safe.Raw(Model.EmbeddedCSS) @Safe.Raw(Model.EmbeddedCSS)
<noscript>
<style>
.hide-when-js, [v-cloak] { display: block !important; }
.only-for-js { display: none !important; }
</style>
</noscript>
</head> </head>
<body class="h-100"> <body>
<div class="h-100 d-flex flex-column"> <div class="container" style="max-width:960px;">
@if (Model.IsPending)
{ <div class="row w-100 p-0 m-0" style="height: 100vh">
<nav id="mainNav" class="navbar sticky-top py-3 py-lg-4 d-print-block"> <div class="mx-auto my-auto w-100">
<div class="container"> @if (TempData.HasStatusMessage())
<form asp-action="ClaimPullPayment" asp-route-pullPaymentId="@Model.Id" class="w-100"> {
<div class="row align-items-center" style="width:calc(100% + 30px)"> <partial name="_StatusMessage" />
<div class="col-12 mb-3 col-lg-6 mb-lg-0"> }
<input class="form-control form-control-lg text-monospace w-100" asp-for="Destination" placeholder="Enter destination address to claim funds …" required style="font-size:.9rem;height:42px;"> @if (!this.ViewContext.ModelState.IsValid)
</div> {
<div class="col-12 mb-3 col-sm-6 mb-sm-0 col-lg-3"> @Html.ValidationSummary(string.Empty, new { @class = "alert alert-danger pb-0 text-center" })
<div class="input-group"> }
<input type="number" class="form-control form-control-lg text-right hide-number-spin" asp-for="ClaimedAmount" max="@Model.AmountDue" min="@Model.MinimumClaim" step="any" placeholder="Amount" required>
<div class="input-group-append"> <div class="card">
<span class="input-group-text px-3">@Model.Currency.ToUpper()</span> @if (!Model.Title.IsNullOrWhiteSpace())
{
<h1 class="card-header px-3">
@Model.Title
<span class="text-muted float-right text-center">@Model.Status</span>
</h1>
}
@if (Model.IsPending)
{
<div class="card-body" style="padding-left:.75rem;padding-right:.75rem;">
<form asp-action="ClaimPullPayment" asp-route-pullPaymentId="@Model.Id">
<div class="row">
<div class="col-12 mb-2 col-lg-6 mb-lg-0">
<input class="form-control text-monospace w-100"
asp-for="Destination"
placeholder="Destination address"
required
style="font-size:.9rem;height:38px;">
</div>
<div class="col-12 mb-2 col-sm-6 mb-sm-0 col-lg-3">
<div class="input-group">
<input class="form-control"
asp-for="ClaimedAmount"
type="number"
max="@Model.AmountDue"
min="@Model.MinimumClaim"
step="any"
placeholder="Amount"
required>
<div class="input-group-append">
<span class='input-group-text'>@Model.Currency.ToUpper()</span>
</div>
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-lg-3">
<button class="form-control btn btn-primary" type="submit">Claim now</button>
</div>
</div> </div>
<div class="col-12 col-sm-6 col-lg-3"> </form>
<button class="btn btn-lg btn-primary w-100 text-nowrap" type="submit">Claim Funds</button> </div>
</div>
</div>
</form>
</div>
</nav>
}
<main class="flex-grow-1 py-4">
<div class="container">
@await Html.PartialAsync("_StatusMessage", new ViewDataDictionary(ViewData){ { "Margin", "mb-4" } })
@if (!ViewContext.ModelState.IsValid)
{
@Html.ValidationSummary(string.Empty, new { @class = "alert alert-danger mb-4 pb-0 text-center" })
} }
<div class="row"> <div class="card-body px-0 pt-0 pb-0">
<div class="col col-12 col-lg-6 mb-4"> <div class="row">
<div class="jumbotron h-100 m-0 p-sm-5"> <div class="col-sm-12 col-md-12 col-lg-6">
@if (!Model.Title.IsNullOrWhiteSpace()) <h5 style="margin:1rem .75rem;">Pull payment details</h5>
{ <table class="table mb-lg-0 border-top-0">
<h2 class="h4 mb-3">@Model.Title</h2> <tr class="bg-light">
} <td class="font-weight-bold">Claim limit:</td>
<div class="d-flex align-items-center"> <td class="text-right">@Model.AmountFormatted</td>
<span class="text-muted text-nowrap">Last Updated</span> </tr>
&nbsp; <tr class="bg-light">
<span class="text-nowrap">@Model.LastUpdated.ToString("g")</span> <td class="font-weight-bold">Already claimed:</td>
<button type="button" class="btn btn-link d-none d-lg-inline-block d-print-none border-0 p-0 ml-4 only-for-js" id="copyLink"> <td class="text-right">@Model.AmountCollectedFormatted</td>
Copy Link </tr>
</button> <tr class="bg-light">
</div> <td class="font-weight-bold">Available claim:</td>
@if (!string.IsNullOrEmpty(Model.ResetIn)) <td class="text-right">@Model.AmountDueFormatted</td>
{ </tr>
<p> @if (Model.ResetIn != String.Empty)
<span class="text-muted text-nowrap">Reset in</span>
&nbsp;
<span class="text-nowrap">@Model.ResetIn</span>
</p>
}
@if (!string.IsNullOrEmpty(Model.Description) && Model.Description != "<br>")
{
<div>@Safe.Raw(Model.Description)</div>
}
</div>
</div>
<div class="col col-12 col-lg-6 mb-4">
<div class="jumbotron h-100 m-0 p-sm-5">
<h2 class="h4 mb-3">Payment Details</h2>
<dl class="mb-0 mt-md-4">
<div class="d-flex flex-column mb-4">
<dt class="h4 font-weight-normal text-nowrap text-primary order-2 order-sm-1 mb-0">@Model.AmountDueFormatted</dt>
<dd class="text-muted order-1 order-sm-2 mb-1">Available claim</dd>
</div>
<div class="progress bg-light d-none d-sm-flex mb-sm-4 d-print-none" style="height:5px">
<div class="progress-bar bg-primary" role="progressbar" style="width:@((Model.AmountCollected / Model.Amount) * 100)%"></div>
</div>
<div class="d-flex flex-column mb-4 d-sm-inline-flex mb-sm-0">
<dt class="h4 font-weight-normal text-nowrap order-2 order-sm-1 mb-0">@Model.AmountCollectedFormatted</dt>
<dd class="text-muted order-1 order-sm-2 mb-1">Already claimed</dd>
</div>
<div class="d-flex flex-column mb-0 d-sm-inline-flex float-sm-right">
<dt class="h4 font-weight-normal text-nowrap order-2 order-sm-1 mb-0">@Model.AmountFormatted</dt>
<dd class="text-muted text-sm-right order-1 order-sm-2 mb-1">Claim limit</dd>
</div>
</dl>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="jumbotron h-100 m-0 p-sm-5">
<h2 class="h4 mb-3">Awaiting Claims</h2>
<div class="table-responsive">
@if (Model.Payouts.Any())
{ {
<table class="table my-0"> <tr class="bg-light">
<thead> <td class="font-weight-bold">Reset in:</td>
<tr class="table-borderless"> <td class="text-right">@Model.ResetIn</td>
<th class="font-weight-normal text-secondary" scope="col">Destination</th> </tr>
<th class="font-weight-normal text-secondary text-right text-nowrap">Amount requested</th> }
<th class="font-weight-normal text-secondary text-right">Status</th> </table>
@if (Model.Description != null && Model.Description != "" && Model.Description != "<br>")
{
<div class="w-100 px-3 pt-4 pb-3">@Safe.Raw(Model.Description)</div>
}
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
<h5 style="margin:1rem .75rem;">Awaiting claims</h5>
@if (Model.Payouts.Any())
{
foreach (var invoice in Model.Payouts)
{
<table class="table border-top-0 mt-3 mb-0">
<tr class="bg-light">
<td class="font-weight-bold">Status</td>
<td class="text-right">@invoice.Status</td>
</tr> </tr>
</thead> <tr class="bg-light">
<tbody> <td class="font-weight-bold">Amount&nbsp;claimed</td>
@foreach (var invoice in Model.Payouts) <td class="text-right">@invoice.AmountFormatted</td>
</tr>
<tr class="bg-light">
<td class="font-weight-bold">Destination</td>
<td class="text-right text-break">
<code>@invoice.Destination</code>
</td>
</tr>
@if (!String.IsNullOrEmpty(invoice.Link))
{ {
<tr> <tr class="bg-light">
<td class="text-break"> <td class="font-weight-bold">Transaction</td>
@invoice.Destination <td class="text-right text-truncate" style="max-width: 100px;"><a class="transaction-link" href="@invoice.Link">@invoice.TransactionId</a></td>
</td>
<td class="text-right">@invoice.AmountFormatted</td>
<td class="text-right text-nowrap">
@{
string textClass;
switch (invoice.Status)
{
case "Completed":
case "In Progress":
textClass = "text-success";
break;
case "Cancelled":
textClass = "text-danger";
break;
default:
textClass = "text-warning";
break;
}
}
@if (!string.IsNullOrEmpty(invoice.Link))
{
<a class="transaction-link @textClass" href="@invoice.Link">@invoice.Status</a>
}
else
{
<span class="@textClass">@invoice.Status</span>
}
</td>
</tr> </tr>
} }
</tbody>
</table> </table>
} }
else }
{ else
<p class="text-muted">No claim made yet.</p> {
} <p class="text-secondary">No claim made yet</p>
</div> }
</div>
</div>
</div>
<div class="card-footer text-muted">
<div class="row">
<div class="col-12 col-sm-6">
Updated @Model.LastUpdated.ToString("g")
</div>
<div class="col-12 col-sm-6 text-sm-right">
Powered by <a href="https://btcpayserver.org" target="_blank">BTCPay Server</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</main> </div>
<footer class="pt-2 pb-4 d-print-none">
<div class="container text-center">
Powered by <a href="https://btcpayserver.org" target="_blank">BTCPay Server</a>
</div>
</footer>
</div> </div>
<script> </div>
document.getElementById("copyLink").addEventListener("click", function (e) { <style>
if (navigator.clipboard) { tbody + tbody { padding-top: 1.5rem; }
e.preventDefault(); </style>
var button = e.currentTarget;
if (!button.dataset.initialText) button.dataset.initialText = button.innerText;
navigator.clipboard.writeText(window.location).then(function () {
button.innerText = 'Copied ✔';
setTimeout(function() { button.innerText = button.dataset.initialText; }, 2500);
});
button.blur();
}
});
</script>
</body> </body>
</html> </html>

View file

@ -21,10 +21,3 @@
<bundle name="wwwroot/bundles/main-bundle.min.css" asp-append-version="true" /> <bundle name="wwwroot/bundles/main-bundle.min.css" asp-append-version="true" />
@* JS *@ @* JS *@
<bundle name="wwwroot/bundles/main-bundle.min.js" asp-append-version="true" /> <bundle name="wwwroot/bundles/main-bundle.min.js" asp-append-version="true" />
@* Non-JS *@
<noscript>
<style>
.hide-when-js, [v-cloak] { display: block !important; }
.only-for-js { display: none !important; }
</style>
</noscript>

View file

@ -5,13 +5,30 @@
@inject BTCPayServer.HostedServices.CssThemeManager themeManager @inject BTCPayServer.HostedServices.CssThemeManager themeManager
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"@(env.IsDeveloping ? " data-devenv" : "")> <html lang="en">
<head> <head>
<partial name="Header" /> <partial name="Header" />
@RenderSection("HeadScripts", required: false) @RenderSection("HeadScripts", required: false)
@RenderSection("HeaderContent", false) @RenderSection("HeaderContent", false)
<noscript>
<style>
.hide-when-js {
display: block !important;
}
.only-for-js {
display: none !important;
}
[v-cloak]::before {
content: "" !important;
}
</style>
</noscript>
</head> </head>
<body id="page-top"> <body id="page-top">

View file

@ -4,10 +4,10 @@
@if (parsedModel != null) @if (parsedModel != null)
{ {
<div class="alert alert-@parsedModel.SeverityCSS @(parsedModel.AllowDismiss? "alert-dismissible":"" ) @(ViewData["Margin"] ?? "mb-5") text-break" role="alert"> <div class="alert alert-@parsedModel.SeverityCSS @(parsedModel.AllowDismiss? "alert-dismissible":"" ) mb-5 text-break" role="alert">
@if (parsedModel.AllowDismiss) @if (parsedModel.AllowDismiss)
{ {
<button type="button" class="close only-for-js" data-dismiss="alert" aria-label="Close"> <button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
} }

View file

@ -166,7 +166,7 @@
] ]
}, },
{ {
"outputFileName": "wwwroot/bundles/payment-request-bundle.min.js", "outputFileName": "wwwroot/bundles/payment-request-bundle-1.min.js",
"inputFiles": [ "inputFiles": [
"wwwroot/vendor/vuejs/vue.min.js", "wwwroot/vendor/vuejs/vue.min.js",
"wwwroot/vendor/babel-polyfill/polyfill.min.js", "wwwroot/vendor/babel-polyfill/polyfill.min.js",
@ -174,8 +174,13 @@
"wwwroot/vendor/bootstrap-vue/bootstrap-vue.js", "wwwroot/vendor/bootstrap-vue/bootstrap-vue.js",
"wwwroot/vendor/signalr/signalr.js", "wwwroot/vendor/signalr/signalr.js",
"wwwroot/vendor/animejs/anime.min.js", "wwwroot/vendor/animejs/anime.min.js",
"wwwroot/vendor/moment/moment.min.js",
"wwwroot/payment-request/**/*.js" "wwwroot/payment-request/**/*.js"
]
},
{
"outputFileName": "wwwroot/bundles/payment-request-bundle-2.min.js",
"inputFiles": [
"wwwroot/vendor/moment/moment.min.js"
], ],
"minify": { "minify": {
"enabled": false "enabled": false
@ -186,7 +191,7 @@
"inputFiles": [ "inputFiles": [
"wwwroot/vendor/font-awesome/css/font-awesome.min.css", "wwwroot/vendor/font-awesome/css/font-awesome.min.css",
"wwwroot/vendor/bootstrap-vue/bootstrap-vue.css", "wwwroot/vendor/bootstrap-vue/bootstrap-vue.css",
"wwwroot/main/site.css" "wwwroot/payment-request/**/*.css"
] ]
}, },
{ {

View file

@ -176,14 +176,8 @@ pre {
background-color: lightgray; background-color: lightgray;
} }
/* Fix for small table showing unnecessary scrollbars */ [v-cloak] > * { display:none }
.table-responsive .table-sm { [v-cloak]::before { content: "loading…" }
width: 100% !important;
}
[v-cloak] { display:none }
[v-cloak-loading] > * { display:none }
[v-cloak-loading]::before { content: "loading…" }
.cursor-pointer{ .cursor-pointer{
cursor: pointer; cursor: pointer;
@ -248,54 +242,3 @@ pre {
display: inline-block; display: inline-block;
margin-top: .5rem; margin-top: .5rem;
} }
/* Chrome, Safari, Edge, Opera */
input[type=number].hide-number-spin::-webkit-outer-spin-button,
input[type=number].hide-number-spin::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type=number].hide-number-spin {
-moz-appearance: textfield;
}
/* Bootstrap Responsive Helper */
html[data-devenv]:before {
display: inline-block;
position: fixed;
z-index: 1000;
right: 0;
bottom: 0;
background: var(--btcpay-bg-dark);
color: var(--btcpay-color-dark-text);
opacity: .7;
padding: 4px 5px 3px 7px;
font-size: 10px;
border-top-left-radius: 4px;
}
@media (max-width: 575px) { html[data-devenv]:before { content: 'XS'; } }
@media (min-width: 576px) and (max-width: 767px) { html[data-devenv]:before { content: 'SM'; } }
@media (min-width: 768px) and (max-width: 991px) { html[data-devenv]:before { content: 'MD'; } }
@media (min-width: 992px) and (max-width: 1199px) { html[data-devenv]:before { content: 'LG'; } }
@media (min-width: 1200px) { html[data-devenv]:before { content: 'XL'; } }
@media print { html[data-devenv]:before { content: none; } }
/* Print */
@media print {
.jumbotron {
padding: 1rem 0 !important;
}
}
/* Richtext editor */
.note-editor .table.table-sm {
border-collapse: collapse !important;
}
.note-editor .table.table-sm th,
.note-editor .table.table-sm td {
border: 1px dotted var(--btcpay-color-neutral-400);
}

View file

@ -85,17 +85,7 @@ $(function () {
e.preventDefault(); e.preventDefault();
var item = e.currentTarget; var item = e.currentTarget;
var text = item.getAttribute('data-clipboard'); var text = item.getAttribute('data-clipboard');
var confirm = item.querySelector('[data-clipboard-confirm]') || item; navigator.clipboard.writeText(text);
var message = confirm.getAttribute('data-clipboard-confirm') || 'Copied ✔';
if (!confirm.dataset.clipboardInitialText) {
confirm.dataset.clipboardInitialText = confirm.innerText;
console.log(confirm.clientWidth)
confirm.style.minWidth = confirm.clientWidth + 'px';
}
navigator.clipboard.writeText(text).then(function () {
confirm.innerText = message;
setTimeout(function(){ confirm.innerText = confirm.dataset.clipboardInitialText; }, 2500);
});
item.blur(); item.blur();
} }
}); });

View file

@ -1,11 +1,6 @@
$(document).ready(function() { $(document).ready(function() {
$(".richtext").summernote({ $(".richtext").summernote({
minHeight: 300, minHeight: 300
tableClassName: 'table table-sm',
insertTableMaxSize: {
col: 5,
row: 10
}
}); });
}); });

View file

@ -18,6 +18,7 @@ function addLoadEvent(func) {
addLoadEvent(function (ev) { addLoadEvent(function (ev) {
Vue.use(Toasted); Vue.use(Toasted);
app = new Vue({ app = new Vue({
el: '#app', el: '#app',
data: function () { data: function () {
@ -28,6 +29,7 @@ addLoadEvent(function (ev) {
ended: false, ended: false,
endDiff: "", endDiff: "",
active: true, active: true,
lastUpdated: "",
loading: false, loading: false,
timeoutState: "", timeoutState: "",
customAmount: null customAmount: null
@ -39,12 +41,6 @@ addLoadEvent(function (ev) {
}, },
settled: function () { settled: function () {
return this.srvModel.amountDue <= 0; return this.srvModel.amountDue <= 0;
},
lastUpdated: function () {
return this.srvModel.lastUpdated && moment(this.srvModel.lastUpdated).calendar();
},
active: function () {
return !this.ended;
} }
}, },
methods: { methods: {
@ -53,6 +49,7 @@ addLoadEvent(function (ev) {
var endDateM = moment(this.srvModel.expiryDate); var endDateM = moment(this.srvModel.expiryDate);
this.endDate = endDateM.format('MMMM Do YYYY'); this.endDate = endDateM.format('MMMM Do YYYY');
this.ended = endDateM.isBefore(moment()); this.ended = endDateM.isBefore(moment());
} else { } else {
this.ended = false; this.ended = false;
this.endDate = null; this.endDate = null;
@ -67,6 +64,8 @@ addLoadEvent(function (ev) {
this.endDiff = mDiffD > 0 ? mDiffD + " days" : mDiffH > 0 ? mDiffH + " hours" : mDiffM > 0 ? mDiffM + " minutes" : mDiffS > 0 ? mDiffS + " seconds" : ""; this.endDiff = mDiffD > 0 ? mDiffD + " days" : mDiffH > 0 ? mDiffH + " hours" : mDiffM > 0 ? mDiffM + " minutes" : mDiffS > 0 ? mDiffS + " seconds" : "";
} }
this.lastUpdated = moment(this.srvModel.lastUpdated).calendar();
this.active = !this.ended;
setTimeout(this.updateComputed, 1000); setTimeout(this.updateComputed, 1000);
}, },
setLoading: function (val) { setLoading: function (val) {
@ -84,18 +83,6 @@ addLoadEvent(function (ev) {
eventAggregator.$emit("pay", amount); eventAggregator.$emit("pay", amount);
}, },
copyLink: function (e) {
if (navigator.clipboard) {
e.preventDefault();
var button = e.currentTarget;
if (!button.dataset.initialText) button.dataset.initialText = button.innerText;
navigator.clipboard.writeText(window.location).then(function () {
button.innerText = 'Copied ✔';
setTimeout(function() { button.innerText = button.dataset.initialText; }, 2500);
});
button.blur();
}
},
cancelPayment: function (amount) { cancelPayment: function (amount) {
this.setLoading(true); this.setLoading(true);
var self = this; var self = this;
@ -113,6 +100,9 @@ addLoadEvent(function (ev) {
return str; return str;
}, },
print:function(){
window.print();
},
submitCustomAmountForm : function(e){ submitCustomAmountForm : function(e){
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
@ -125,10 +115,10 @@ addLoadEvent(function (ev) {
} }
}, },
mounted: function () { mounted: function () {
this.customAmount = (this.srvModel.amountDue || 0).noExponents(); this.customAmount = (this.srvModel.amountDue || 0).noExponents();
hubListener.connect(); hubListener.connect();
var self = this; var self = this;
eventAggregator.$on("invoice-created", function (invoiceId) { eventAggregator.$on("invoice-created", function (invoiceId) {
self.setLoading(false); self.setLoading(false);
btcpay.showInvoice(invoiceId); btcpay.showInvoice(invoiceId);

View file

@ -0,0 +1,7 @@
[v-cloak] > * {
display: none
}
[v-cloak]::before {
content: "loading…"
}