mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Crowdfund : Add Buyer information / Additional information(forms) like POS (#5659)
* Crowfund : Add Buyer information / Additional information(forms) like POS * PR 5659 - changes * Cleanups * fix perk * Crowdfund form tests * Add Selenium test for Crowfund * Selenium update * update Selenium * selenium update * update selenium * Test fixes and view improvements * Cleanups * do not use hacky form element for form detection --------- Co-authored-by: nisaba <infos@nisaba.solutions> Co-authored-by: Dennis Reimann <mail@dennisreimann.de> Co-authored-by: Kukks <evilkukka@gmail.com>
This commit is contained in:
parent
4943c84655
commit
04037b3d2d
13 changed files with 944 additions and 619 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -300,3 +300,4 @@ Plugins/packed
|
|||
BTCPayServer/wwwroot/swagger/v1/openapi.json
|
||||
BTCPayServer/appsettings.dev.json
|
||||
BTCPayServer.Tests/monero_wallet
|
||||
/BTCPayServer.Tests/NewBlocks.bat
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Forms;
|
||||
using BTCPayServer.Forms.Models;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Plugins.Crowdfund;
|
||||
using BTCPayServer.Plugins.Crowdfund.Controllers;
|
||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
|
@ -303,5 +307,114 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(0.7m, model.Info.CurrentPendingAmount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CrowdfundWithFormNoPerk()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
|
||||
var frmService = tester.PayTester.GetService<FormDataService>();
|
||||
var appService = tester.PayTester.GetService<AppService>();
|
||||
var crowdfund = user.GetController<UICrowdfundController>();
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var appData = new AppData { StoreDataId = user.StoreId, Name = "test", AppType = CrowdfundAppType.AppType };
|
||||
await appService.UpdateOrCreateApp(appData);
|
||||
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
var app = appList.Apps[0];
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
crowdfund.HttpContext.SetAppData(appData);
|
||||
|
||||
var form = new Form
|
||||
{
|
||||
Fields =
|
||||
[
|
||||
Field.Create("Enter your email", "item1", "test@toto.com", true, null, "email"),
|
||||
Field.Create("Name", "item2", 2.ToString(), true, null),
|
||||
Field.Create("Item3", "invoice_item3", 3.ToString(), true, null)
|
||||
]
|
||||
};
|
||||
var frmData = new FormData
|
||||
{
|
||||
StoreId = user.StoreId,
|
||||
Name = "frmTest",
|
||||
Config = form.ToString()
|
||||
};
|
||||
await frmService.AddOrUpdateForm(frmData);
|
||||
|
||||
var lstForms = await frmService.GetForms(user.StoreId);
|
||||
Assert.NotEmpty(lstForms);
|
||||
|
||||
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
|
||||
crowdfundViewModel.FormId = lstForms[0].Id;
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.Enabled = true;
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
|
||||
var vm2 = await crowdfund.CrowdfundForm(app.Id, (decimal?)0.01).AssertViewModelAsync<FormViewModel>();
|
||||
var res = await crowdfund.CrowdfundFormSubmit(app.Id, (decimal)0.01, "", vm2);
|
||||
Assert.IsNotType<NotFoundObjectResult>(res);
|
||||
Assert.IsNotType<BadRequest>(res);
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CrowdfundWithFormAndPerk()
|
||||
{
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
|
||||
var frmService = tester.PayTester.GetService<FormDataService>();
|
||||
var appService = tester.PayTester.GetService<AppService>();
|
||||
var crowdfund = user.GetController<UICrowdfundController>();
|
||||
var apps = user.GetController<UIAppsController>();
|
||||
var appData = new AppData { StoreDataId = user.StoreId, Name = "test", AppType = CrowdfundAppType.AppType };
|
||||
await appService.UpdateOrCreateApp(appData);
|
||||
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
var app = appList.Apps[0];
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
crowdfund.HttpContext.SetAppData(appData);
|
||||
|
||||
var form = new Form
|
||||
{
|
||||
Fields =
|
||||
[
|
||||
Field.Create("Enter your email", "item1", "test@toto.com", true, null, "email"),
|
||||
Field.Create("Name", "item2", 2.ToString(), true, null),
|
||||
Field.Create("Item3", "invoice_item3", 3.ToString(), true, null)
|
||||
]
|
||||
};
|
||||
var frmData = new FormData
|
||||
{
|
||||
StoreId = user.StoreId,
|
||||
Name = "frmTest",
|
||||
Config = form.ToString()
|
||||
};
|
||||
await frmService.AddOrUpdateForm(frmData);
|
||||
|
||||
var lstForms = await frmService.GetForms(user.StoreId);
|
||||
Assert.NotEmpty(lstForms);
|
||||
|
||||
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
|
||||
crowdfundViewModel.FormId = lstForms[0].Id;
|
||||
crowdfundViewModel.TargetCurrency = "BTC";
|
||||
crowdfundViewModel.Enabled = true;
|
||||
crowdfundViewModel.PerksTemplate = "[{\"id\": \"xxx\",\"title\": \"Perk 1\",\"priceType\": \"Fixed\",\"price\": \"0.001\",\"image\": \"\",\"description\": \"\",\"categories\": [],\"disabled\": false}]";
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
|
||||
var vm2 = await crowdfund.CrowdfundForm(app.Id, (decimal?)0.01, "xxx").AssertViewModelAsync<FormViewModel>();
|
||||
var res = await crowdfund.CrowdfundFormSubmit(app.Id, (decimal)0.01, "xxx", vm2);
|
||||
Assert.IsNotType<NotFoundObjectResult>(res);
|
||||
Assert.IsNotType<BadRequest>(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("button[type='submit']")).Click();
|
||||
|
||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||
|
@ -108,7 +108,7 @@ namespace BTCPayServer.Tests
|
|||
var invoiceId = s.Driver.Url[(s.Driver.Url.LastIndexOf("/", StringComparison.Ordinal) + 1)..];
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
|
||||
s.GoToInvoice(invoiceId);
|
||||
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
||||
|
||||
|
@ -123,10 +123,10 @@ namespace BTCPayServer.Tests
|
|||
|
||||
s.Driver.FindElement(By.XPath("//a[starts-with(@id, 'Edit-')]")).Click();
|
||||
var editUrl = s.Driver.Url;
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewPaymentRequest")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("[data-test='form-button']")).Click();
|
||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||
|
||||
|
@ -135,7 +135,7 @@ namespace BTCPayServer.Tests
|
|||
invoiceId = s.Driver.Url.Split('/').Last();
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
|
||||
s.Driver.Navigate().GoToUrl(editUrl);
|
||||
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
||||
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
||||
|
@ -147,13 +147,13 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.FindElement(By.Id("CreateForm")).Click();
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("Custom Form 1");
|
||||
s.Driver.FindElement(By.Id("ApplyEmailTemplate")).Click();
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Id("CodeTabButton")).Click();
|
||||
s.Driver.WaitForElement(By.Id("CodeTabPane"));
|
||||
|
||||
|
||||
var config = s.Driver.FindElement(By.Name("FormConfig")).GetAttribute("value");
|
||||
Assert.Contains("buyerEmail", config);
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Name("FormConfig")).Clear();
|
||||
s.Driver.FindElement(By.Name("FormConfig"))
|
||||
.SendKeys(config.Replace("Enter your email", "CustomFormInputTest"));
|
||||
|
@ -179,10 +179,10 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.FindElement(By.Id("CreateForm")).Click();
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("Custom Form 2");
|
||||
s.Driver.FindElement(By.Id("ApplyEmailTemplate")).Click();
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Id("CodeTabButton")).Click();
|
||||
s.Driver.WaitForElement(By.Id("CodeTabPane"));
|
||||
|
||||
|
||||
s.Driver.SetCheckbox(By.Name("Public"), true);
|
||||
|
||||
s.Driver.FindElement(By.Name("FormConfig")).Clear();
|
||||
|
@ -1292,34 +1292,33 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.ExecuteJavaScript("document.getElementById('EndDate').value = ''");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
var appId = s.Driver.Url.Split('/')[4];
|
||||
var editUrl = s.Driver.Url;
|
||||
var appId = editUrl.Split('/')[4];
|
||||
|
||||
// CHeck public page
|
||||
// Check public page
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
Assert.Equal(2, windows.Count);
|
||||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
var cfUrl = s.Driver.Url;
|
||||
|
||||
Assert.Equal("Currently active!",
|
||||
s.Driver.FindElement(By.CssSelector("[data-test='time-state']")).Text);
|
||||
Assert.Equal("Currently active!", s.Driver.FindElement(By.CssSelector("[data-test='time-state']")).Text);
|
||||
|
||||
// Contribute
|
||||
s.Driver.FindElement(By.Id("crowdfund-body-header-cta")).Click();
|
||||
s.Driver.WaitUntilAvailable(By.Name("btcpay"));
|
||||
|
||||
var frameElement = s.Driver.FindElement(By.Name("btcpay"));
|
||||
Assert.True(frameElement.Displayed);
|
||||
var iframe = s.Driver.SwitchTo().Frame(frameElement);
|
||||
iframe.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
|
||||
IWebElement closebutton = null;
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
closebutton = iframe.FindElement(By.Id("close"));
|
||||
Assert.True(closebutton.Displayed);
|
||||
s.Driver.WaitUntilAvailable(By.Name("btcpay"));
|
||||
|
||||
var frameElement = s.Driver.FindElement(By.Name("btcpay"));
|
||||
Assert.True(frameElement.Displayed);
|
||||
var iframe = s.Driver.SwitchTo().Frame(frameElement);
|
||||
iframe.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
|
||||
var closeButton = iframe.FindElement(By.Id("close"));
|
||||
Assert.True(closeButton.Displayed);
|
||||
closeButton.Click();
|
||||
});
|
||||
closebutton.Click();
|
||||
s.Driver.AssertElementNotFound(By.Name("btcpay"));
|
||||
|
||||
// Back to admin view
|
||||
|
@ -1343,6 +1342,56 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.FindElement(By.Id($"App-{appId}")).Click();
|
||||
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
|
||||
Assert.Contains("The app has been unarchived and will appear in the apps list by default again.", s.FindAlertMessage().Text);
|
||||
|
||||
// Crowdfund with form
|
||||
s.GoToUrl(editUrl);
|
||||
new SelectElement(s.Driver.FindElement(By.Id("FormId"))).SelectByValue("Email");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
s.Driver.FindElement(By.Id("crowdfund-body-header-cta")).Click();
|
||||
|
||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("test-without-perk@crowdfund.com");
|
||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||
|
||||
s.PayInvoice(true, 10);
|
||||
var invoiceId = s.Driver.Url[(s.Driver.Url.LastIndexOf("/", StringComparison.Ordinal) + 1)..];
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
s.GoToInvoice(invoiceId);
|
||||
Assert.Contains("test-without-perk@crowdfund.com", s.Driver.PageSource);
|
||||
|
||||
// Crowdfund with perk
|
||||
s.GoToUrl(editUrl);
|
||||
s.Driver.ScrollTo(By.Id("btAddItem"));
|
||||
s.Driver.FindElement(By.Id("btAddItem")).Click();
|
||||
s.Driver.FindElement(By.Id("EditorTitle")).SendKeys("Perk 1");
|
||||
s.Driver.FindElement(By.Id("EditorId")).SendKeys("Perk-1");
|
||||
s.Driver.FindElement(By.Id("EditorAmount")).SendKeys("20");
|
||||
s.Driver.FindElement(By.Id("ApplyItemChanges")).Click();
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
s.Driver.WaitForElement(By.Id("Perk-1")).Click();
|
||||
s.Driver.WaitForElement(By.CssSelector("#Perk-1 button[type=\"submit\"]")).Submit();
|
||||
|
||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("test-with-perk@crowdfund.com");
|
||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||
|
||||
s.PayInvoice(true, 20);
|
||||
invoiceId = s.Driver.Url[(s.Driver.Url.LastIndexOf("/", StringComparison.Ordinal) + 1)..];
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
s.GoToInvoice(invoiceId);
|
||||
Assert.Contains("test-with-perk@crowdfund.com", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
|
|
@ -5,11 +5,15 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Forms;
|
||||
using BTCPayServer.Forms.Models;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
|
@ -20,7 +24,10 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NicolasDorier.RateLimits;
|
||||
using CrowdfundResetEvery = BTCPayServer.Services.Apps.CrowdfundResetEvery;
|
||||
|
||||
|
@ -37,6 +44,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
|||
StoreRepository storeRepository,
|
||||
UIInvoiceController invoiceController,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
FormDataService formDataService,
|
||||
CrowdfundAppType app)
|
||||
{
|
||||
_currencies = currencies;
|
||||
|
@ -46,6 +54,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
|||
_storeRepository = storeRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
_invoiceController = invoiceController;
|
||||
FormDataService = formDataService;
|
||||
}
|
||||
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
|
@ -55,6 +64,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
|||
private readonly UIInvoiceController _invoiceController;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly CrowdfundAppType _app;
|
||||
public FormDataService FormDataService { get; }
|
||||
|
||||
[HttpGet("/")]
|
||||
[HttpGet("/apps/{appId}/crowdfund")]
|
||||
|
@ -95,7 +105,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
|||
[EnableCors(CorsPolicies.All)]
|
||||
[DomainMappingConstraint(CrowdfundAppType.AppType)]
|
||||
[RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)]
|
||||
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, string formResponse = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, CrowdfundAppType.AppType, true);
|
||||
|
||||
|
@ -121,6 +131,24 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
|||
return NotFound("Crowdfund is not currently active");
|
||||
}
|
||||
|
||||
JObject formResponseJObject = null;
|
||||
|
||||
if (settings.FormId is not null)
|
||||
{
|
||||
var formData = await FormDataService.GetForm(settings.FormId);
|
||||
if (formData is not null)
|
||||
{
|
||||
formResponseJObject = TryParseJObject(formResponse) ?? new JObject();
|
||||
var form = Form.Parse(formData.Config);
|
||||
FormDataService.SetValues(form, formResponseJObject);
|
||||
if (!FormDataService.Validate(form, ModelState))
|
||||
{
|
||||
// someone tried to bypass validation
|
||||
return RedirectToAction(nameof(ViewCrowdfund), new { appId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var store = await _appService.GetStore(app);
|
||||
var title = settings.Title;
|
||||
decimal? price = request.Amount;
|
||||
|
@ -203,6 +231,11 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
|||
entity.FullNotifications = true;
|
||||
entity.ExtendedNotifications = true;
|
||||
entity.Metadata.OrderUrl = appUrl;
|
||||
if (formResponseJObject is null)
|
||||
return;
|
||||
var meta = entity.Metadata.ToJObject();
|
||||
meta.Merge(formResponseJObject);
|
||||
entity.Metadata = InvoiceMetadata.FromJObject(meta);
|
||||
});
|
||||
|
||||
if (request.RedirectToCheckout)
|
||||
|
@ -219,6 +252,108 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
private JObject TryParseJObject(string posData)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JObject.Parse(posData);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[HttpGet("/apps/{appId}/crowdfund/form")]
|
||||
[IgnoreAntiforgeryToken]
|
||||
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
|
||||
public async Task<IActionResult> CrowdfundForm(string appId, decimal? amount=0, string choiceKey="")
|
||||
{
|
||||
var app = await _appService.GetApp(appId, CrowdfundAppType.AppType);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
var settings = app.GetSettings<CrowdfundSettings>();
|
||||
var formData = await FormDataService.GetForm(settings.FormId);
|
||||
if (formData is null)
|
||||
{
|
||||
return RedirectToAction(nameof(ViewCrowdfund), new { appId });
|
||||
}
|
||||
|
||||
var prefix = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)) + "_";
|
||||
var formParameters = new MultiValueDictionary<string, string>();
|
||||
var controller = nameof(UICrowdfundController).TrimEnd("Controller", StringComparison.InvariantCulture);
|
||||
var store = await _appService.GetStore(app);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var form = Form.Parse(formData.Config);
|
||||
form.ApplyValuesFromForm(Request.Query);
|
||||
var vm = new FormViewModel
|
||||
{
|
||||
StoreName = store.StoreName,
|
||||
StoreBranding = new StoreBrandingViewModel(storeBlob),
|
||||
FormName = formData.Name,
|
||||
Form = form,
|
||||
AspController = controller,
|
||||
AspAction = nameof(CrowdfundFormSubmit),
|
||||
RouteParameters = new Dictionary<string, string> { { "appId", appId }, { "amount", amount.ToString() }, { "choiceKey", choiceKey } },
|
||||
FormParameters = formParameters,
|
||||
FormParameterPrefix = prefix
|
||||
};
|
||||
|
||||
return View("Views/UIForms/View", vm);
|
||||
}
|
||||
|
||||
[HttpPost("/apps/{appId}/crowdfund/form/submit")]
|
||||
[IgnoreAntiforgeryToken]
|
||||
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)]
|
||||
public async Task<IActionResult> CrowdfundFormSubmit(string appId, decimal amount, string choiceKey, FormViewModel viewModel)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, CrowdfundAppType.AppType);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
var settings = app.GetSettings<CrowdfundSettings>();
|
||||
var formData = await FormDataService.GetForm(settings.FormId);
|
||||
if (formData is null)
|
||||
{
|
||||
return RedirectToAction(nameof(ViewCrowdfund));
|
||||
}
|
||||
var form = Form.Parse(formData.Config);
|
||||
var formFieldNames = form.GetAllFields().Select(tuple => tuple.FullName).Distinct().ToArray();
|
||||
|
||||
// For unit testing
|
||||
if (Request.Headers.Count == 1)
|
||||
{
|
||||
Request.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
|
||||
}
|
||||
var formParameters = Request.Form
|
||||
.Where(pair => pair.Key.StartsWith(viewModel.FormParameterPrefix))
|
||||
.ToDictionary(pair => pair.Key.Replace(viewModel.FormParameterPrefix, string.Empty), pair => pair.Value)
|
||||
.ToMultiValueDictionary(p => p.Key, p => p.Value.ToString());
|
||||
|
||||
form.ApplyValuesFromForm(Request.Form.Where(pair => formFieldNames.Contains(pair.Key)));
|
||||
|
||||
if (FormDataService.Validate(form, ModelState))
|
||||
{
|
||||
var appInfo = await GetAppInfo(appId);
|
||||
var req = new ContributeToCrowdfund()
|
||||
{
|
||||
RedirectToCheckout = true,
|
||||
Amount = amount == 0 ? null : amount,
|
||||
ChoiceKey = choiceKey,
|
||||
ViewCrowdfundViewModel = appInfo
|
||||
};
|
||||
|
||||
return ContributeToCrowdfund(appId, req, formResponse: FormDataService.GetValues(form).ToString()).Result;
|
||||
}
|
||||
|
||||
viewModel.FormName = formData.Name;
|
||||
viewModel.Form = form;
|
||||
|
||||
viewModel.FormParameters = formParameters;
|
||||
return View("Views/UIForms/View", viewModel);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpGet("{appId}/settings/crowdfund")]
|
||||
public async Task<IActionResult> UpdateCrowdfund(string appId)
|
||||
|
@ -264,7 +399,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
|||
DisplayPerksValue = settings.DisplayPerksValue,
|
||||
SortPerksByPopularity = settings.SortPerksByPopularity,
|
||||
Sounds = string.Join(Environment.NewLine, settings.Sounds),
|
||||
AnimationColors = string.Join(Environment.NewLine, settings.AnimationColors)
|
||||
AnimationColors = string.Join(Environment.NewLine, settings.AnimationColors),
|
||||
FormId = settings.FormId
|
||||
};
|
||||
return View("Crowdfund/UpdateCrowdfund", vm);
|
||||
}
|
||||
|
@ -373,7 +509,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
|||
DisplayPerksRanking = vm.DisplayPerksRanking,
|
||||
SortPerksByPopularity = vm.SortPerksByPopularity,
|
||||
Sounds = parsedSounds,
|
||||
AnimationColors = parsedAnimationColors
|
||||
AnimationColors = parsedAnimationColors,
|
||||
FormId = vm.FormId
|
||||
};
|
||||
|
||||
app.TagAllInvoices = vm.UseAllStoreInvoices;
|
||||
|
|
|
@ -11,7 +11,6 @@ using BTCPayServer.Data;
|
|||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Plugins.Crowdfund.Controllers;
|
||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
@ -183,6 +182,10 @@ namespace BTCPayServer.Plugins.Crowdfund
|
|||
CustomCSSLink = settings.CustomCSSLink,
|
||||
EmbeddedCSS = settings.EmbeddedCSS
|
||||
};
|
||||
var formUrl = settings.FormId != null
|
||||
? _linkGenerator.GetPathByAction(nameof(UICrowdfundController.CrowdfundForm), "UICrowdfund",
|
||||
new { appId = appData.Id }, _options.Value.RootPath)
|
||||
: null;
|
||||
return new ViewCrowdfundViewModel
|
||||
{
|
||||
Title = settings.Title,
|
||||
|
@ -210,6 +213,7 @@ namespace BTCPayServer.Plugins.Crowdfund
|
|||
PerkCount = perkCount,
|
||||
PerkValue = perkValue,
|
||||
NeverReset = settings.ResetEvery == CrowdfundResetEvery.Never,
|
||||
FormUrl = formUrl,
|
||||
Sounds = settings.Sounds,
|
||||
AnimationColors = settings.AnimationColors,
|
||||
CurrencyData = _currencyNameTable.GetCurrencyData(settings.TargetCurrency, true),
|
||||
|
|
|
@ -117,6 +117,10 @@ namespace BTCPayServer.Plugins.Crowdfund.Models
|
|||
// NOTE: Improve validation if needed
|
||||
public bool ModelWithMinimumData => Description != null && Title != null && TargetCurrency != null;
|
||||
|
||||
|
||||
[Display(Name = "Request contributor data on checkout")]
|
||||
public string FormId { get; set; }
|
||||
|
||||
public bool Archived { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Models
|
|||
public string[] Sounds { get; set; }
|
||||
public int ResetEveryAmount { get; set; }
|
||||
public bool NeverReset { get; set; }
|
||||
|
||||
public string FormUrl { get; set; }
|
||||
public Dictionary<string, int> PerkCount { get; set; }
|
||||
|
||||
public CurrencyData CurrencyData { get; set; }
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace BTCPayServer.Services.Apps
|
|||
{
|
||||
|
||||
var result =
|
||||
await _crowdfundController.ContributeToCrowdfund(Context.Items["app"].ToString(), model, Context.ConnectionAborted);
|
||||
await _crowdfundController.ContributeToCrowdfund(Context.Items["app"].ToString(), model, cancellationToken: Context.ConnectionAborted);
|
||||
switch (result)
|
||||
{
|
||||
case OkObjectResult okObjectResult:
|
||||
|
|
|
@ -45,6 +45,9 @@ namespace BTCPayServer.Services.Apps
|
|||
public bool DisplayPerksRanking { get; set; }
|
||||
public bool DisplayPerksValue { get; set; }
|
||||
public bool SortPerksByPopularity { get; set; }
|
||||
public string FormId { get; set; } = null;
|
||||
|
||||
|
||||
public string[] AnimationColors { get; set; } =
|
||||
{
|
||||
"#FF6138", "#FFBE53", "#2980B9", "#282741"
|
||||
|
|
|
@ -3,22 +3,22 @@
|
|||
@inject BTCPayServer.Services.BTCPayServerEnvironment Env
|
||||
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
|
||||
@{
|
||||
ViewData["Title"] = Model.Title;
|
||||
ViewData["Title"] = Model.Title;
|
||||
ViewData["StoreBranding"] = Model.StoreBranding;
|
||||
Layout = null;
|
||||
Csp.UnsafeEval();
|
||||
if (!string.IsNullOrEmpty(Model.DisqusShortname))
|
||||
{
|
||||
Csp.Add("script-src", $"https://{Model.DisqusShortname}.disqus.com");
|
||||
Csp.Add("script-src", "https://c.disquscdn.com");
|
||||
}
|
||||
Layout = null;
|
||||
Csp.UnsafeEval();
|
||||
if (!string.IsNullOrEmpty(Model.DisqusShortname))
|
||||
{
|
||||
Csp.Add("script-src", $"https://{Model.DisqusShortname}.disqus.com");
|
||||
Csp.Add("script-src", "https://c.disquscdn.com");
|
||||
}
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html class="h-100" @(Env.IsDeveloping ? " data-devenv" : "")>
|
||||
<head>
|
||||
<partial name="LayoutHead"/>
|
||||
<link href="~/vendor/bootstrap-vue/bootstrap-vue.min.css" asp-append-version="true" rel="stylesheet"/>
|
||||
<link href="~/crowdfund/styles/main.css" asp-append-version="true" rel="stylesheet"/>
|
||||
<partial name="LayoutHead" />
|
||||
<link href="~/vendor/bootstrap-vue/bootstrap-vue.min.css" asp-append-version="true" rel="stylesheet" />
|
||||
<link href="~/crowdfund/styles/main.css" asp-append-version="true" rel="stylesheet" />
|
||||
<style>
|
||||
#app { --wrap-max-width: 1320px; }
|
||||
#crowdfund-main-image {
|
||||
|
@ -38,363 +38,357 @@
|
|||
<vc:ui-extension-point location="crowdfund-head" model="@Model"/>
|
||||
</head>
|
||||
<body class="min-vh-100 p-2">
|
||||
@if (!Model.Enabled)
|
||||
{
|
||||
<div class="alert alert-warning text-center sticky-top mb-0 rounded-0" role="alert">
|
||||
This crowdfund page is not publically viewable!
|
||||
</div>
|
||||
}
|
||||
@if (Model.AnimationsEnabled)
|
||||
{
|
||||
<canvas id="fireworks" class="d-none"></canvas>
|
||||
}
|
||||
|
||||
<div class="public-page-wrap" id="app" @(Model.SimpleDisplay ? "" : "v-cloak")>
|
||||
@if (!string.IsNullOrEmpty(Model.MainImageUrl))
|
||||
@if (!Model.Enabled)
|
||||
{
|
||||
<img v-if="srvModel.mainImageUrl" :src="srvModel.mainImageUrl" :alt="srvModel.title" id="crowdfund-main-image" asp-append-version="true"/>
|
||||
<div class="alert alert-warning text-center sticky-top mb-0 rounded-0" role="alert">
|
||||
This crowdfund page is not publically viewable!
|
||||
</div>
|
||||
}
|
||||
<div class="d-flex flex-column justify-content-between p-3 text-center" id="crowdfund-header-container">
|
||||
<h1 class="mb-3">{{ srvModel.title }}</h1>
|
||||
@if (!string.IsNullOrEmpty(Model.Tagline))
|
||||
@if (Model.AnimationsEnabled)
|
||||
{
|
||||
<canvas id="fireworks" class="d-none"></canvas>
|
||||
}
|
||||
|
||||
<div class="public-page-wrap" id="app" @(Model.SimpleDisplay ? "" : "v-cloak")>
|
||||
@if (!string.IsNullOrEmpty(Model.MainImageUrl))
|
||||
{
|
||||
<h2 class="h3 mb-3 fw-semibold" v-if="srvModel.tagline" v-text="srvModel.tagline"></h2>
|
||||
<img v-if="srvModel.mainImageUrl" :src="srvModel.mainImageUrl" :alt="srvModel.title" id="crowdfund-main-image" asp-append-version="true"/>
|
||||
}
|
||||
<div class="d-flex flex-column justify-content-between p-3 text-center" id="crowdfund-header-container">
|
||||
<h1 class="mb-3">{{ srvModel.title }}</h1>
|
||||
@if (!string.IsNullOrEmpty(Model.Tagline))
|
||||
{
|
||||
<h2 class="h3 mb-3 fw-semibold" v-if="srvModel.tagline" v-text="srvModel.tagline"></h2>
|
||||
}
|
||||
@if (Model.TargetAmount.HasValue)
|
||||
{
|
||||
<span v-if="srvModel.targetAmount" class="mt-3" id="crowdfund-header-target">
|
||||
<h3 class="d-inline-block">
|
||||
<span class="badge bg-info px-3" v-text="`${targetAmount} ${targetCurrency}`">@Math.Round(Model.TargetAmount.GetValueOrDefault(0)) @Model.TargetCurrency</span>
|
||||
</h3>
|
||||
@if (Model.ResetEveryAmount > 0 && !Model.NeverReset)
|
||||
{
|
||||
<span v-if="srvModel.resetEvery !== 'Never'"
|
||||
class="h5 ms-2"
|
||||
v-b-tooltip
|
||||
:title="'Goal resets every ' + srvModel.resetEveryAmount + ' ' + srvModel.resetEvery + ((srvModel.resetEveryAmount>1)?'s': '')">
|
||||
Dynamic
|
||||
</span>
|
||||
}
|
||||
@if (Model.EnforceTargetAmount)
|
||||
{
|
||||
<span v-if="srvModel.enforceTargetAmount" class="h5 ms-2" v-b-tooltip title="No contributions allowed after the goal has been reached">
|
||||
Hardcap Goal
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span v-if="!srvModel.enforceTargetAmount" class="h5 ms-2" v-b-tooltip title="Contributions allowed even after goal is reached">
|
||||
Softcap Goal
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
@if (!Model.Started && Model.StartDate.HasValue)
|
||||
{
|
||||
<h6 class="text-muted fst-italic mt-3" v-if="!started && srvModel.startDate" v-b-tooltip :title="startDate" v-text="`Starts in ${startDiff}`" data-test="time-state">
|
||||
Starts @TimeZoneInfo.ConvertTimeFromUtc(Model.StartDate.Value, TimeZoneInfo.Local)
|
||||
</h6>
|
||||
}
|
||||
else if (Model.Started && !Model.Ended && Model.EndDate.HasValue)
|
||||
{
|
||||
<h6 class="text-muted fst-italic mt-3" v-if="started && !ended && srvModel.endDate" v-b-tooltip :title="endDate" v-text="`Ends in ${endDiff}`" data-test="time-state">
|
||||
Ends @TimeZoneInfo.ConvertTimeFromUtc(Model.EndDate.Value, TimeZoneInfo.Local)
|
||||
</h6>
|
||||
}
|
||||
else if (Model.Started && !Model.Ended && !Model.EndDate.HasValue)
|
||||
{
|
||||
<h6 class="text-muted fst-italic mt-3" v-if="started && !ended && !srvModel.endDate" v-b-tooltip title="No set end date" data-test="time-state">
|
||||
Currently active!
|
||||
</h6>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (Model.TargetAmount.HasValue)
|
||||
{
|
||||
<span v-if="srvModel.targetAmount" class="mt-3" id="crowdfund-header-target">
|
||||
<h3 class="d-inline-block">
|
||||
<span class="badge bg-info px-3" v-text="`${targetAmount} ${targetCurrency}`">@Math.Round(Model.TargetAmount.GetValueOrDefault(0)) @Model.TargetCurrency</span>
|
||||
</h3>
|
||||
@if (Model.ResetEveryAmount > 0 && !Model.NeverReset)
|
||||
{
|
||||
<span v-if="srvModel.resetEvery !== 'Never'"
|
||||
class="h5 ms-2"
|
||||
v-b-tooltip
|
||||
:title="'Goal resets every ' + srvModel.resetEveryAmount + ' ' + srvModel.resetEvery + ((srvModel.resetEveryAmount>1)?'s': '')">
|
||||
Dynamic
|
||||
</span>
|
||||
}
|
||||
@if (Model.EnforceTargetAmount)
|
||||
{
|
||||
<span v-if="srvModel.enforceTargetAmount" class="h5 ms-2" v-b-tooltip title="No contributions allowed after the goal has been reached">
|
||||
Hardcap Goal
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span v-if="!srvModel.enforceTargetAmount" class="h5 ms-2" v-b-tooltip title="Contributions allowed even after goal is reached">
|
||||
Softcap Goal
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
@if (!Model.Started && Model.StartDate.HasValue)
|
||||
{
|
||||
<h6 class="text-muted fst-italic mt-3" v-if="!started && srvModel.startDate" v-b-tooltip :title="startDate" v-text="`Starts in ${startDiff}`" data-test="time-state">
|
||||
Starts @TimeZoneInfo.ConvertTimeFromUtc(Model.StartDate.Value, TimeZoneInfo.Local)
|
||||
</h6>
|
||||
}
|
||||
else if (Model.Started && !Model.Ended && Model.EndDate.HasValue)
|
||||
{
|
||||
<h6 class="text-muted fst-italic mt-3" v-if="started && !ended && srvModel.endDate" v-b-tooltip :title="endDate" v-text="`Ends in ${endDiff}`" data-test="time-state">
|
||||
Ends @TimeZoneInfo.ConvertTimeFromUtc(Model.EndDate.Value, TimeZoneInfo.Local)
|
||||
</h6>
|
||||
}
|
||||
else if (Model.Started && !Model.Ended && !Model.EndDate.HasValue)
|
||||
{
|
||||
<h6 class="text-muted fst-italic mt-3" v-if="started && !ended && !srvModel.endDate" v-b-tooltip title="No set end date" data-test="time-state">
|
||||
Currently active!
|
||||
</h6>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (Model.TargetAmount.HasValue)
|
||||
{
|
||||
<div class="progress rounded-pill" v-if="srvModel.targetAmount" id="crowdfund-progress-bar">
|
||||
<div class="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style="width:@(Model.Info.ProgressPercentage + "%")"
|
||||
:aria-valuenow="srvModel.info.progressPercentage"
|
||||
v-bind:style="{ width: srvModel.info.progressPercentage + '%' }"
|
||||
aria-valuemin="0"
|
||||
id="crowdfund-progress-bar-confirmed-bar"
|
||||
v-b-tooltip
|
||||
:title="parseFloat(srvModel.info.progressPercentage).toFixed(2) + '% contributions'"
|
||||
aria-valuemax="100">
|
||||
<div class="progress rounded-pill" v-if="srvModel.targetAmount" id="crowdfund-progress-bar">
|
||||
<div class="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
style="width:@(Model.Info.ProgressPercentage + "%")"
|
||||
:aria-valuenow="srvModel.info.progressPercentage"
|
||||
v-bind:style="{ width: srvModel.info.progressPercentage + '%' }"
|
||||
aria-valuemin="0"
|
||||
id="crowdfund-progress-bar-confirmed-bar"
|
||||
v-b-tooltip
|
||||
:title="parseFloat(srvModel.info.progressPercentage).toFixed(2) + '% contributions'"
|
||||
aria-valuemax="100">
|
||||
</div>
|
||||
<div class="progress-bar bg-warning"
|
||||
role="progressbar"
|
||||
id="crowdfund-progress-bar-pending-bar"
|
||||
style="width:@(Model.Info.PendingProgressPercentage + "%")"
|
||||
:aria-valuenow="srvModel.info.pendingProgressPercentage"
|
||||
v-bind:style="{ width: srvModel.info.pendingProgressPercentage + '%' }"
|
||||
v-b-tooltip
|
||||
:title="parseFloat(srvModel.info.pendingProgressPercentage).toFixed(2) + '% contributions pending confirmation'"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-bar bg-warning"
|
||||
role="progressbar"
|
||||
id="crowdfund-progress-bar-pending-bar"
|
||||
style="width:@(Model.Info.PendingProgressPercentage + "%")"
|
||||
:aria-valuenow="srvModel.info.pendingProgressPercentage"
|
||||
v-bind:style="{ width: srvModel.info.pendingProgressPercentage + '%' }"
|
||||
v-b-tooltip
|
||||
:title="parseFloat(srvModel.info.pendingProgressPercentage).toFixed(2) + '% contributions pending confirmation'"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<div class="row py-2 text-center crowdfund-stats">
|
||||
<div class="col-sm border-end p-3 text-center" id="crowdfund-body-raised-amount">
|
||||
<h3 v-text="`${raisedAmount} ${targetCurrency}`">@Math.Round(Model.Info.CurrentAmount + Model.Info.CurrentPendingAmount, Model.CurrencyData.Divisibility) @Model.TargetCurrency</h3>
|
||||
<h5 class="text-muted fst-italic mb-0">Raised</h5>
|
||||
<b-tooltip target="crowdfund-body-raised-amount" v-if="paymentStats && paymentStats.length > 0" class="only-for-js">
|
||||
<ul class="p-0 text-uppercase">
|
||||
<li v-for="stat of paymentStats" class="list-unstyled">
|
||||
{{stat.label}} <span v-if="stat.lightning" class="fa fa-bolt"></span> {{stat.value}}
|
||||
</li>
|
||||
</ul>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="col-sm border-end p-3 text-center" id="crowdfund-body-goal-raised">
|
||||
<h3 v-text="`${percentageRaisedAmount}%`">@Math.Round(Model.Info.PendingProgressPercentage.GetValueOrDefault(0) + Model.Info.ProgressPercentage.GetValueOrDefault(0))%</h3>
|
||||
<h5 class="text-muted fst-italic mb-0">Of Goal</h5>
|
||||
<b-tooltip target="crowdfund-body-goal-raised" v-if="srvModel.resetEvery !== 'Never'" class="only-for-js">
|
||||
Goal resets every {{srvModel.resetEveryAmount}} {{srvModel.resetEvery}} {{srvModel.resetEveryAmount>1?'s': ''}}
|
||||
</b-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="col-sm border-end p-3 text-center" id="crowdfund-body-total-contributors">
|
||||
<h3 v-text="new Intl.NumberFormat().format(srvModel.info.totalContributors)">@Model.Info.TotalContributors</h3>
|
||||
<h5 class="text-muted fst-italic mb-0">Contributors</h5>
|
||||
</div>
|
||||
|
||||
@if (Model.StartDate.HasValue || Model.EndDate.HasValue)
|
||||
{
|
||||
<div class="col-sm border-end p-3 text-center" id="crowdfund-body-campaign-dates">
|
||||
@if (!Model.Started && Model.StartDate.HasValue)
|
||||
{
|
||||
<div v-if="startDiff">
|
||||
<h3 v-text="startDiff">@TimeZoneInfo.ConvertTimeFromUtc(Model.StartDate.Value, TimeZoneInfo.Local)</h3>
|
||||
<h5 class="text-muted fst-italic mb-0" v-text="'Left to start'">Start Date</h5>
|
||||
</div>
|
||||
}
|
||||
else if (Model.Started && !Model.Ended && Model.EndDate.HasValue)
|
||||
{
|
||||
<div v-if="!startDiff && endDiff">
|
||||
<h3 v-text="endDiff">@TimeZoneInfo.ConvertTimeFromUtc(Model.EndDate.Value, TimeZoneInfo.Local)</h3>
|
||||
<h5 class="text-muted fst-italic mb-0" v-text="'Left'">End Date</h5>
|
||||
</div>
|
||||
}
|
||||
else if (Model.Ended)
|
||||
{
|
||||
<div v-if="ended">
|
||||
<h3 class="mb-0">Campaign not active</h3>
|
||||
</div>
|
||||
}
|
||||
<b-tooltip v-if="startDate || endDate" target="crowdfund-body-campaign-dates" class="only-for-js">
|
||||
<ul class="p-0">
|
||||
@if (Model.StartDate.HasValue)
|
||||
{
|
||||
<li v-if="startDate" class="list-unstyled">
|
||||
{{started? "Started" : "Starts"}} {{startDate}}
|
||||
</li>
|
||||
}
|
||||
@if (Model.EndDate.HasValue)
|
||||
{
|
||||
<li v-if="endDate" class="list-unstyled">
|
||||
{{ended? "Ended" : "Ends"}} {{endDate}}
|
||||
</li>
|
||||
}
|
||||
<div class="row py-2 text-center crowdfund-stats">
|
||||
<div class="col-sm border-end p-3 text-center" id="crowdfund-body-raised-amount">
|
||||
<h3 v-text="`${raisedAmount} ${targetCurrency}`">@Math.Round(Model.Info.CurrentAmount + Model.Info.CurrentPendingAmount, Model.CurrencyData.Divisibility) @Model.TargetCurrency</h3>
|
||||
<h5 class="text-muted fst-italic mb-0">Raised</h5>
|
||||
<b-tooltip target="crowdfund-body-raised-amount" v-if="paymentStats && paymentStats.length > 0" class="only-for-js">
|
||||
<ul class="p-0 text-uppercase">
|
||||
<li v-for="stat of paymentStats" class="list-unstyled">
|
||||
{{stat.label}} <span v-if="stat.lightning" class="fa fa-bolt"></span> {{stat.value}}
|
||||
</li>
|
||||
</ul>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-4" id="crowdfund-body-header">
|
||||
<button v-if="active" id="crowdfund-body-header-cta" class="btn btn-lg btn-primary py-2 px-5 only-for-js" v-on:click="contribute">Contribute</button>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4 justify-content-between gap-5">
|
||||
<div :class="{ 'col-lg-7 col-sm-12': hasPerks, 'col-12': !hasPerks }" id="crowdfund-body-description-container">
|
||||
<template v-if="srvModel.disqusEnabled && srvModel.disqusShortname">
|
||||
<b-tabs>
|
||||
<b-tab title="Details" active>
|
||||
<div class="overflow-hidden pt-3" v-html="srvModel.description" id="crowdfund-body-description">
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab title="Discussion">
|
||||
<div id="disqus_thread" class="mt-4"></div>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="overflow-hidden" v-html="srvModel.description" id="crowdfund-body-description">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-12" id="crowdfund-body-contribution-container" v-if="hasPerks">
|
||||
<contribute :target-currency="srvModel.targetCurrency"
|
||||
:loading="loading"
|
||||
:display-perks-ranking="srvModel.displayPerksRanking"
|
||||
:perks-value="srvModel.perksValue"
|
||||
:active="active"
|
||||
:in-modal="false"
|
||||
:perks="perks">
|
||||
</contribute>
|
||||
</div>
|
||||
</div>
|
||||
<noscript v-pre>
|
||||
<div class="row justify-content-between">
|
||||
<div class="col-md-7 col-sm-12">
|
||||
<div class="overflow-hidden">@Safe.Raw(Model.Description)</div>
|
||||
<div class="col-sm border-end p-3 text-center" id="crowdfund-body-goal-raised">
|
||||
<h3 v-text="`${percentageRaisedAmount}%`">@Math.Round(Model.Info.PendingProgressPercentage.GetValueOrDefault(0) + Model.Info.ProgressPercentage.GetValueOrDefault(0))%</h3>
|
||||
<h5 class="text-muted fst-italic mb-0">Of Goal</h5>
|
||||
<b-tooltip target="crowdfund-body-goal-raised" v-if="srvModel.resetEvery !== 'Never'" class="only-for-js">
|
||||
Goal resets every {{srvModel.resetEveryAmount}} {{srvModel.resetEvery}} {{srvModel.resetEveryAmount>1?'s': ''}}
|
||||
</b-tooltip>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
|
||||
<div class="col-sm border-end p-3 text-center" id="crowdfund-body-total-contributors">
|
||||
<h3 v-text="new Intl.NumberFormat().format(srvModel.info.totalContributors)">@Model.Info.TotalContributors</h3>
|
||||
<h5 class="text-muted fst-italic mb-0">Contributors</h5>
|
||||
</div>
|
||||
|
||||
@if (Model.StartDate.HasValue || Model.EndDate.HasValue)
|
||||
{
|
||||
<div class="col-sm border-end p-3 text-center" id="crowdfund-body-campaign-dates">
|
||||
@if (!Model.Started && Model.StartDate.HasValue)
|
||||
{
|
||||
<div v-if="startDiff">
|
||||
<h3 v-text="startDiff">@TimeZoneInfo.ConvertTimeFromUtc(Model.StartDate.Value, TimeZoneInfo.Local)</h3>
|
||||
<h5 class="text-muted fst-italic mb-0" v-text="'Left to start'">Start Date</h5>
|
||||
</div>
|
||||
}
|
||||
else if (Model.Started && !Model.Ended && Model.EndDate.HasValue)
|
||||
{
|
||||
<div v-if="!startDiff && endDiff">
|
||||
<h3 v-text="endDiff">@TimeZoneInfo.ConvertTimeFromUtc(Model.EndDate.Value, TimeZoneInfo.Local)</h3>
|
||||
<h5 class="text-muted fst-italic mb-0" v-text="'Left'">End Date</h5>
|
||||
</div>
|
||||
}
|
||||
else if (Model.Ended)
|
||||
{
|
||||
<div v-if="ended">
|
||||
<h3 class="mb-0">Campaign not active</h3>
|
||||
</div>
|
||||
}
|
||||
<b-tooltip v-if="startDate || endDate" target="crowdfund-body-campaign-dates" class="only-for-js">
|
||||
<ul class="p-0">
|
||||
@if (Model.StartDate.HasValue)
|
||||
{
|
||||
<li v-if="startDate" class="list-unstyled">
|
||||
{{started? "Started" : "Starts"}} {{startDate}}
|
||||
</li>
|
||||
}
|
||||
@if (Model.EndDate.HasValue)
|
||||
{
|
||||
<li v-if="endDate" class="list-unstyled">
|
||||
{{ended? "Ended" : "Ends"}} {{endDate}}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-4" id="crowdfund-body-header">
|
||||
<button v-if="active" id="crowdfund-body-header-cta" class="btn btn-lg btn-primary py-2 px-5 only-for-js" v-on:click="contribute">Contribute</button>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4 justify-content-between gap-5">
|
||||
<div :class="{ 'col-lg-7 col-sm-12': hasPerks, 'col-12': !hasPerks }" id="crowdfund-body-description-container">
|
||||
<template v-if="srvModel.disqusEnabled && srvModel.disqusShortname">
|
||||
<b-tabs>
|
||||
<b-tab title="Details" active>
|
||||
<div class="overflow-hidden pt-3" v-html="srvModel.description" id="crowdfund-body-description">
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab title="Discussion">
|
||||
<div id="disqus_thread" class="mt-4"></div>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="overflow-hidden" v-html="srvModel.description" id="crowdfund-body-description">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-12" id="crowdfund-body-contribution-container" v-if="hasPerks">
|
||||
<contribute :target-currency="srvModel.targetCurrency"
|
||||
:loading="loading"
|
||||
:display-perks-ranking="srvModel.displayPerksRanking"
|
||||
:perks-value="srvModel.perksValue"
|
||||
:active="active"
|
||||
:in-modal="false"
|
||||
:perks="perks">
|
||||
</contribute>
|
||||
</div>
|
||||
</div>
|
||||
<noscript v-pre>
|
||||
<div class="row justify-content-between">
|
||||
<div class="col-md-7 col-sm-12">
|
||||
<div class="overflow-hidden">@Safe.Raw(Model.Description)</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<partial
|
||||
name="Crowdfund/Public/ContributeForm"
|
||||
model="@(new ContributeToCrowdfund { ViewCrowdfundViewModel = Model, RedirectToCheckout = true })">
|
||||
</partial>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
<b-modal title="Contribute" v-model="contributeModalOpen" size="lg" ok-only="true" ok-variant="secondary" ok-title="Close" ref="modalContribute">
|
||||
<contribute v-if="contributeModalOpen"
|
||||
:target-currency="srvModel.targetCurrency"
|
||||
:active="active"
|
||||
:perks="srvModel.perks"
|
||||
:loading="loading"
|
||||
:in-modal="true">
|
||||
</contribute>
|
||||
</b-modal>
|
||||
<footer class="store-footer">
|
||||
<p class="text-muted" v-text="`Updated ${lastUpdated}`">Updated @Model.Info.LastUpdated</p>
|
||||
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
|
||||
Powered by <partial name="_StoreFooterLogo" />
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<template id="perks-template">
|
||||
<div class="perks-container">
|
||||
<perk v-if="!perks || perks.length === 0"
|
||||
:perk="{title: 'Donate Custom Amount', priceType: 'Topup', price: { type: 'Topup' } }"
|
||||
:target-currency="targetCurrency"
|
||||
:active="active"
|
||||
:loading="loading"
|
||||
:in-modal="inModal">
|
||||
</perk>
|
||||
<perk v-for="(perk, index) in perks"
|
||||
:key="perk.id"
|
||||
:perk="perk"
|
||||
:target-currency="targetCurrency"
|
||||
:active="active"
|
||||
:display-perks-ranking="displayPerksRanking"
|
||||
:perks-value="perksValue"
|
||||
:index="index"
|
||||
:loading="loading"
|
||||
:in-modal="inModal">
|
||||
</perk>
|
||||
</div>
|
||||
</template>
|
||||
<template id="perk-template">
|
||||
<div class="card perk" v-bind:class="{ 'expanded': expanded, 'unexpanded': !expanded, 'mb-4':!inModal }" v-on:click="expand" :id="perk.id">
|
||||
<span v-if="displayPerksRanking && perk.sold"
|
||||
class="btn btn-sm rounded-circle px-0 perk-badge"
|
||||
v-bind:class="{ 'btn-primary': index==0, 'btn-light': index!=0}">
|
||||
#{{index+1}}
|
||||
</span>
|
||||
<div class="perk-zoom" v-if="canExpand">
|
||||
<div class="perk-zoom-bg"></div>
|
||||
<div class="perk-zoom-text w-100 py-2 px-4 text-center text-primary fw-semibold fs-5 lh-sm">
|
||||
Select this contribution perk
|
||||
</div>
|
||||
</div>
|
||||
<form v-on:submit="onContributeFormSubmit" class="mb-0">
|
||||
<input type="hidden" :value="perk.id" id="choiceKey"/>
|
||||
<img v-if="perk.image && perk.image != 'null'" class="card-img-top" :src="perk.image"/>
|
||||
<div class="card-body">
|
||||
<div class="card-title d-flex justify-content-between" :class="{ 'mb-0': !perk.description }">
|
||||
<span class="h5" :class="{ 'mb-0': !perk.description }">{{perk.title ? perk.title : perk.id}}</span>
|
||||
<span class="text-muted">
|
||||
|
||||
<template v-if="perk.priceType === 'Fixed' && amount ==0">
|
||||
Free
|
||||
</template>
|
||||
<template v-else-if="amount">
|
||||
{{formatAmount(perk.price.noExponents(), srvModel.currencyData.divisibility)}}
|
||||
{{targetCurrency}}
|
||||
<template v-if="perk.price.type === 'Minimum'">or more</template>
|
||||
</template>
|
||||
|
||||
<template v-else-if="perk.priceType === 'Topup' || (!amount && perk.priceType === 'Minimum')">
|
||||
Any amount
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<p class="card-text overflow-hidden" v-if="perk.description" v-html="perk.description"></p>
|
||||
|
||||
<div class="input-group" style="max-width:500px;" v-if="expanded" :id="'perk-form'+ perk.id">
|
||||
<template v-if="perk.priceType !== 'Topup' && !(perk.priceType === 'Fixed' && amount == 0)">
|
||||
<input
|
||||
:disabled="!active"
|
||||
:readonly="perk.priceType === 'Fixed'"
|
||||
class="form-control hide-number-spin"
|
||||
type="number"
|
||||
v-model="amount"
|
||||
:min="perk.price"
|
||||
step="any"
|
||||
placeholder="Contribution Amount"
|
||||
required>
|
||||
<span class="input-group-text">{{targetCurrency}}</span>
|
||||
</template>
|
||||
<button
|
||||
class="btn btn-primary d-flex align-items-center "
|
||||
v-bind:class="{ 'btn-disabled': loading}"
|
||||
:disabled="!active || loading"
|
||||
type="submit">
|
||||
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
{{perk.buyButtonText || 'Continue'}}
|
||||
</button>
|
||||
model="@(new ContributeToCrowdfund { ViewCrowdfundViewModel = Model, RedirectToCheckout = true })">
|
||||
</partial>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-between" v-if="perk.sold || perk.inventory != null">
|
||||
<span v-if="perk.inventory != null && perk.inventory > 0" class="text-center text-muted">{{new Intl.NumberFormat().format(perk.inventory)}} left</span>
|
||||
<span v-if="perk.inventory != null && perk.inventory <= 0" class="text-center text-muted">Sold out</span>
|
||||
<span v-if="perk.sold">{{new Intl.NumberFormat().format(perk.sold)}} Contributor{{perk.sold === 1 ? "": "s"}}</span>
|
||||
<span v-if="perk.value">{{formatAmount(perk.value, srvModel.currencyData.divisibility)}} {{targetCurrency}} total</span>
|
||||
</noscript>
|
||||
<b-modal title="Contribute" v-model="contributeModalOpen" size="lg" ok-only="true" ok-variant="secondary" ok-title="Close" ref="modalContribute">
|
||||
<contribute v-if="contributeModalOpen"
|
||||
:target-currency="srvModel.targetCurrency"
|
||||
:active="active"
|
||||
:perks="srvModel.perks"
|
||||
:loading="loading"
|
||||
:in-modal="true">
|
||||
</contribute>
|
||||
</b-modal>
|
||||
<footer class="store-footer">
|
||||
<p class="text-muted" v-text="`Updated ${lastUpdated}`">Updated @Model.Info.LastUpdated</p>
|
||||
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
|
||||
Powered by <partial name="_StoreFooterLogo" />
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<template id="perks-template">
|
||||
<div class="perks-container">
|
||||
<perk v-if="!perks || perks.length === 0"
|
||||
:perk="{title: 'Donate Custom Amount', priceType: 'Topup', price: { type: 'Topup' } }"
|
||||
:target-currency="targetCurrency"
|
||||
:active="active"
|
||||
:loading="loading"
|
||||
:in-modal="inModal">
|
||||
</perk>
|
||||
<perk v-for="(perk, index) in perks"
|
||||
:key="perk.id"
|
||||
:perk="perk"
|
||||
:target-currency="targetCurrency"
|
||||
:active="active"
|
||||
:display-perks-ranking="displayPerksRanking"
|
||||
:perks-value="perksValue"
|
||||
:index="index"
|
||||
:loading="loading"
|
||||
:in-modal="inModal">
|
||||
</perk>
|
||||
</div>
|
||||
</template>
|
||||
<template id="perk-template">
|
||||
<div class="card perk" v-bind:class="{ 'expanded': expanded, 'unexpanded': !expanded, 'mb-4':!inModal }" v-on:click="expand" :id="perk.id">
|
||||
<span v-if="displayPerksRanking && perk.sold"
|
||||
class="btn btn-sm rounded-circle px-0 perk-badge"
|
||||
v-bind:class="{ 'btn-primary': index==0, 'btn-light': index!=0}">
|
||||
#{{index+1}}
|
||||
</span>
|
||||
<div class="perk-zoom" v-if="canExpand">
|
||||
<div class="perk-zoom-bg"></div>
|
||||
<div class="perk-zoom-text w-100 py-2 px-4 text-center text-primary fw-semibold fs-5 lh-sm">
|
||||
Select this contribution perk
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<form v-on:submit="onContributeFormSubmit" class="mb-0">
|
||||
<input type="hidden" :value="perk.id" id="choiceKey" />
|
||||
<img v-if="perk.image && perk.image != 'null'" class="card-img-top" :src="perk.image" />
|
||||
<div class="card-body">
|
||||
<div class="card-title d-flex justify-content-between" :class="{ 'mb-0': !perk.description }">
|
||||
<span class="h5" :class="{ 'mb-0': !perk.description }">{{perk.title ? perk.title : perk.id}}</span>
|
||||
<span class="text-muted">
|
||||
|
||||
<template id="contribute-template">
|
||||
<div>
|
||||
<h3 v-if="!inModal" class="mb-3">Contribute</h3>
|
||||
<perks
|
||||
:perks="perks"
|
||||
:loading="loading"
|
||||
:in-modal="inModal"
|
||||
:display-perks-ranking="displayPerksRanking"
|
||||
:perks-value="perksValue"
|
||||
:target-currency="targetCurrency"
|
||||
:active="active">
|
||||
</perks>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="perk.priceType === 'Fixed' && amount ==0">
|
||||
Free
|
||||
</template>
|
||||
<template v-else-if="amount">
|
||||
{{formatAmount(perk.price.noExponents(), srvModel.currencyData.divisibility)}}
|
||||
{{targetCurrency}}
|
||||
<template v-if="perk.price.type === 'Minimum'">or more</template>
|
||||
</template>
|
||||
|
||||
@if (!Model.SimpleDisplay)
|
||||
{
|
||||
<script>var srvModel = @Safe.Json(Model);</script>
|
||||
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/moment/moment.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/vue-qrcode/vue-qrcode.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/vue-toasted/vue-toasted.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/bootstrap-vue/bootstrap-vue.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/signalr/signalr.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/animejs/anime.min.js" asp-append-version="true"></script>
|
||||
<script src="~/crowdfund/app.js" asp-append-version="true"></script>
|
||||
<script src="~/crowdfund/services/audioplayer.js" asp-append-version="true"></script>
|
||||
<script src="~/crowdfund/services/fireworks.js" asp-append-version="true"></script>
|
||||
<script src="~/crowdfund/services/listener.js" asp-append-version="true"></script>
|
||||
<script src="~/modal/btcpay.js" asp-append-version="true"></script>
|
||||
}
|
||||
<template v-else-if="perk.priceType === 'Topup' || (!amount && perk.priceType === 'Minimum')">
|
||||
Any amount
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<p class="card-text overflow-hidden" v-if="perk.description" v-html="perk.description"></p>
|
||||
<div class="input-group mt-3" style="max-width:500px;" v-if="expanded" :id="'perk-form'+ perk.id">
|
||||
<template v-if="perk.priceType !== 'Topup' && !(perk.priceType === 'Fixed' && amount == 0)">
|
||||
<input type="number" class="form-control hide-number-spin"
|
||||
v-model="amount"
|
||||
:disabled="!active"
|
||||
:readonly="perk.priceType === 'Fixed'"
|
||||
:min="perk.price"
|
||||
step="any"
|
||||
placeholder="Contribution Amount"
|
||||
required>
|
||||
<span class="input-group-text">{{targetCurrency}}</span>
|
||||
</template>
|
||||
<button class="btn btn-primary d-flex align-items-center"
|
||||
:class="{'btn-disabled': loading}"
|
||||
type="submit">
|
||||
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
{{perk.buyButtonText || 'Continue'}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-between" v-if="perk.sold || perk.inventory != null">
|
||||
<span v-if="perk.inventory != null && perk.inventory > 0" class="text-center text-muted">{{new Intl.NumberFormat().format(perk.inventory)}} left</span>
|
||||
<span v-if="perk.inventory != null && perk.inventory <= 0" class="text-center text-muted">Sold out</span>
|
||||
<span v-if="perk.sold">{{new Intl.NumberFormat().format(perk.sold)}} Contributor{{perk.sold === 1 ? "": "s"}}</span>
|
||||
<span v-if="perk.value">{{formatAmount(perk.value, srvModel.currencyData.divisibility)}} {{targetCurrency}} total</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="contribute-template">
|
||||
<div>
|
||||
<h3 v-if="!inModal" class="mb-3">Contribute</h3>
|
||||
<perks :perks="perks"
|
||||
:loading="loading"
|
||||
:in-modal="inModal"
|
||||
:display-perks-ranking="displayPerksRanking"
|
||||
:perks-value="perksValue"
|
||||
:target-currency="targetCurrency"
|
||||
:active="active">
|
||||
</perks>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@if (!Model.SimpleDisplay)
|
||||
{
|
||||
<script>var srvModel = @Safe.Json(Model);</script>
|
||||
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/moment/moment.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/vue-qrcode/vue-qrcode.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/vue-toasted/vue-toasted.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/bootstrap-vue/bootstrap-vue.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/signalr/signalr.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/animejs/anime.min.js" asp-append-version="true"></script>
|
||||
<script src="~/crowdfund/app.js" asp-append-version="true"></script>
|
||||
<script src="~/crowdfund/services/audioplayer.js" asp-append-version="true"></script>
|
||||
<script src="~/crowdfund/services/fireworks.js" asp-append-version="true"></script>
|
||||
<script src="~/crowdfund/services/listener.js" asp-append-version="true"></script>
|
||||
<script src="~/modal/btcpay.js" asp-append-version="true"></script>
|
||||
}
|
||||
<partial name="LayoutFoot"/>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
@using BTCPayServer.TagHelpers
|
||||
@using BTCPayServer.Views.Apps
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using BTCPayServer.Forms
|
||||
@inject FormDataService FormDataService
|
||||
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
|
||||
@model BTCPayServer.Plugins.Crowdfund.Models.UpdateCrowdfundViewModel
|
||||
@{
|
||||
ViewData.SetActivePage(AppsNavPages.Update, "Update Crowdfund", Model.AppId);
|
||||
Csp.UnsafeEval();
|
||||
var checkoutFormOptions = await FormDataService.GetSelect(Model.StoreId, Model.FormId);
|
||||
}
|
||||
|
||||
@section PageHeadContent {
|
||||
|
@ -201,6 +204,13 @@
|
|||
<label asp-for="UseAllStoreInvoices" class="form-check-label"></label>
|
||||
<span asp-validation-for="UseAllStoreInvoices" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<h3 class="mt-5 mb-4">Checkout</h3>
|
||||
<div class="form-group">
|
||||
<label asp-for="FormId" class="form-label"></label>
|
||||
<select asp-for="FormId" class="form-select w-auto" asp-items="@checkoutFormOptions"></select>
|
||||
<span asp-validation-for="FormId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<h3 class="mt-5 mb-2">Additional Options</h3>
|
||||
<div class="form-group">
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-link py-0 px-2 mt-2 mb-2 gap-1 add fw-semibold d-inline-flex align-items-center" v-on:click.stop="$emit('add-item', $event)">
|
||||
<button type="button" id="btAddItem" class="btn btn-link py-0 px-2 mt-2 mb-2 gap-1 add fw-semibold d-inline-flex align-items-center" v-on:click.stop="$emit('add-item', $event)">
|
||||
<vc:icon symbol="new" />
|
||||
Add Item
|
||||
</button>
|
||||
|
|
|
@ -28,8 +28,8 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
|||
},
|
||||
computed: {
|
||||
canExpand: function(){
|
||||
return !this.expanded
|
||||
&& this.active &&
|
||||
return !this.expanded
|
||||
&& this.active &&
|
||||
(this.perk.inventory==null || this.perk.inventory > 0)
|
||||
}
|
||||
},
|
||||
|
@ -41,152 +41,157 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
|||
if (!this.active || this.loading){
|
||||
return;
|
||||
}
|
||||
|
||||
eventAggregator.$emit("contribute", {amount: parseFloat(this.amount), choiceKey: this.perk.id});
|
||||
const formUrl = this.$root.srvModel.formUrl;
|
||||
if (formUrl) {
|
||||
location.href = formUrl + "?amount=" + this.amount + "&choiceKey=" + this.perk.id;
|
||||
return;
|
||||
} else {
|
||||
eventAggregator.$emit("contribute", { amount: parseFloat(this.amount), choiceKey: this.perk.id });
|
||||
}
|
||||
},
|
||||
expand: function(){
|
||||
if(this.canExpand){
|
||||
this.expanded = true;
|
||||
}
|
||||
},
|
||||
setAmount: function (amount) {
|
||||
if(typeof amount === "string"){
|
||||
amount = parseFloat(amount);
|
||||
}
|
||||
this.amount = this.perk.priceType === "Topup"? null : (amount || 0).noExponents();
|
||||
this.expanded = false;
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.setAmount(this.perk.price);
|
||||
},
|
||||
watch: {
|
||||
perk: function (newValue, oldValue) {
|
||||
if(newValue.price.type === "Topup"){
|
||||
this.setAmount();
|
||||
}else if (newValue.price != oldValue.price) {
|
||||
this.setAmount(newValue.price);
|
||||
}
|
||||
}
|
||||
this.expanded = true;
|
||||
}
|
||||
});
|
||||
|
||||
app = new Vue({
|
||||
el: '#app',
|
||||
data: function(){
|
||||
return {
|
||||
srvModel: window.srvModel,
|
||||
connectionStatus: "",
|
||||
endDate: "",
|
||||
startDate: "",
|
||||
started: false,
|
||||
ended: false,
|
||||
contributeModalOpen: false,
|
||||
endDiff: "",
|
||||
startDiff: "",
|
||||
active: true,
|
||||
animation: true,
|
||||
sound: true,
|
||||
lastUpdated: "",
|
||||
loading: false,
|
||||
timeoutState: 0
|
||||
}
|
||||
},
|
||||
setAmount: function (amount) {
|
||||
if(typeof amount === "string"){
|
||||
amount = parseFloat(amount);
|
||||
}
|
||||
this.amount = this.perk.priceType === "Topup"? null : (amount || 0).noExponents();
|
||||
this.expanded = false;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mounted: function () {
|
||||
this.setAmount(this.perk.price);
|
||||
},
|
||||
watch: {
|
||||
perk: function (newValue, oldValue) {
|
||||
if(newValue.price.type === "Topup"){
|
||||
this.setAmount();
|
||||
}else if (newValue.price != oldValue.price) {
|
||||
this.setAmount(newValue.price);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app = new Vue({
|
||||
el: '#app',
|
||||
data: function(){
|
||||
return {
|
||||
srvModel: window.srvModel,
|
||||
connectionStatus: "",
|
||||
endDate: "",
|
||||
startDate: "",
|
||||
started: false,
|
||||
ended: false,
|
||||
contributeModalOpen: false,
|
||||
endDiff: "",
|
||||
startDiff: "",
|
||||
active: true,
|
||||
animation: true,
|
||||
sound: true,
|
||||
lastUpdated: "",
|
||||
loading: false,
|
||||
timeoutState: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
raisedAmount: function(){
|
||||
return this.formatAmount(this.srvModel.info.currentAmount + this.srvModel.info.currentPendingAmount);
|
||||
},
|
||||
return this.formatAmount(this.srvModel.info.currentAmount + this.srvModel.info.currentPendingAmount);
|
||||
},
|
||||
targetAmount: function(){
|
||||
return this.formatAmount(this.srvModel.targetAmount);
|
||||
},
|
||||
return this.formatAmount(this.srvModel.targetAmount);
|
||||
},
|
||||
percentageRaisedAmount: function(){
|
||||
return parseFloat(this.srvModel.info.progressPercentage + this.srvModel.info.pendingProgressPercentage ).toFixed(2);
|
||||
},
|
||||
},
|
||||
targetCurrency: function(){
|
||||
return this.srvModel.targetCurrency.toUpperCase();
|
||||
},
|
||||
return this.srvModel.targetCurrency.toUpperCase();
|
||||
},
|
||||
paymentStats: function(){
|
||||
var result= [];
|
||||
var combinedStats = {};
|
||||
var keys = Object.keys(this.srvModel.info.paymentStats);
|
||||
var combinedStats = {};
|
||||
var keys = Object.keys(this.srvModel.info.paymentStats);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if(combinedStats[keys[i]]){
|
||||
combinedStats[keys[i]] +=this.srvModel.info.paymentStats[keys[i]];
|
||||
}else{
|
||||
combinedStats[keys[i]] =this.srvModel.info.paymentStats[keys[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys = Object.keys(this.srvModel.info.pendingPaymentStats);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
keys = Object.keys(this.srvModel.info.pendingPaymentStats);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if(combinedStats[keys[i]]){
|
||||
combinedStats[keys[i]] +=this.srvModel.info.pendingPaymentStats[keys[i]];
|
||||
}else{
|
||||
combinedStats[keys[i]] =this.srvModel.info.pendingPaymentStats[keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
keys = Object.keys(combinedStats);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if(!combinedStats[keys[i]]){
|
||||
continue;
|
||||
}
|
||||
var paymentMethodId = keys[i].split("_");
|
||||
var value = combinedStats[keys[i]].toFixed(this.srvModel.currencyDataPayments[paymentMethodId[0]].divisibility);
|
||||
var newItem = {key:keys[i], value: value, label: paymentMethodId[0]};
|
||||
|
||||
if(paymentMethodId.length > 1 && paymentMethodId[1].endsWith("LightningLike")){
|
||||
newItem.lightning = true;
|
||||
}
|
||||
result.push(newItem);
|
||||
}
|
||||
|
||||
if(result.length === 1 && result[0].label === srvModel.targetCurrency){
|
||||
return [];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
perks: function(){
|
||||
const result = [];
|
||||
for (let i = 0; i < this.srvModel.perks.length; i++) {
|
||||
const currentPerk = this.srvModel.perks[i];
|
||||
if(this.srvModel.perkCount.hasOwnProperty(currentPerk.id)){
|
||||
currentPerk.sold = this.srvModel.perkCount[currentPerk.id];
|
||||
}
|
||||
if(this.srvModel.perkValue.hasOwnProperty(currentPerk.id)){
|
||||
currentPerk.value = this.srvModel.perkValue[currentPerk.id];
|
||||
}
|
||||
result.push(currentPerk);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
hasPerks() {
|
||||
return this.srvModel.perks && this.srvModel.perks.length > 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateComputed: function () {
|
||||
if (this.srvModel.endDate) {
|
||||
var endDateM = moment(this.srvModel.endDate);
|
||||
this.endDate = endDateM.format('MMMM Do YYYY');
|
||||
this.ended = endDateM.isBefore(moment());
|
||||
|
||||
}else{
|
||||
this.ended = false;
|
||||
this.endDate = null;
|
||||
}
|
||||
|
||||
if (this.srvModel.startDate) {
|
||||
var startDateM = moment(this.srvModel.startDate);
|
||||
this.startDate = startDateM.format('MMMM Do YYYY');
|
||||
this.started = startDateM.isBefore(moment());
|
||||
}else{
|
||||
this.started = true;
|
||||
this.startDate = null;
|
||||
keys = Object.keys(combinedStats);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if(!combinedStats[keys[i]]){
|
||||
continue;
|
||||
}
|
||||
var paymentMethodId = keys[i].split("_");
|
||||
var value = combinedStats[keys[i]].toFixed(this.srvModel.currencyDataPayments[paymentMethodId[0]].divisibility);
|
||||
var newItem = {key:keys[i], value: value, label: paymentMethodId[0]};
|
||||
|
||||
if(paymentMethodId.length > 1 && paymentMethodId[1].endsWith("LightningLike")){
|
||||
newItem.lightning = true;
|
||||
}
|
||||
result.push(newItem);
|
||||
}
|
||||
|
||||
if(result.length === 1 && result[0].label === srvModel.targetCurrency){
|
||||
return [];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
perks: function(){
|
||||
const result = [];
|
||||
for (let i = 0; i < this.srvModel.perks.length; i++) {
|
||||
const currentPerk = this.srvModel.perks[i];
|
||||
if(this.srvModel.perkCount.hasOwnProperty(currentPerk.id)){
|
||||
currentPerk.sold = this.srvModel.perkCount[currentPerk.id];
|
||||
}
|
||||
if(this.srvModel.perkValue.hasOwnProperty(currentPerk.id)){
|
||||
currentPerk.value = this.srvModel.perkValue[currentPerk.id];
|
||||
}
|
||||
result.push(currentPerk);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
hasPerks() {
|
||||
return this.srvModel.perks && this.srvModel.perks.length > 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateComputed: function () {
|
||||
if (this.srvModel.endDate) {
|
||||
var endDateM = moment(this.srvModel.endDate);
|
||||
this.endDate = endDateM.format('MMMM Do YYYY');
|
||||
this.ended = endDateM.isBefore(moment());
|
||||
|
||||
}else{
|
||||
this.ended = false;
|
||||
this.endDate = null;
|
||||
}
|
||||
|
||||
if (this.srvModel.startDate) {
|
||||
var startDateM = moment(this.srvModel.startDate);
|
||||
this.startDate = startDateM.format('MMMM Do YYYY');
|
||||
this.started = startDateM.isBefore(moment());
|
||||
}else{
|
||||
this.started = true;
|
||||
this.startDate = null;
|
||||
}
|
||||
if(this.started && !this.ended && this.srvModel.endDate){
|
||||
var mDiffD = moment(this.srvModel.endDate).diff(moment(), "days");
|
||||
var mDiffH = moment(this.srvModel.endDate).diff(moment(), "hours");
|
||||
|
@ -194,8 +199,8 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
|||
var mDiffS = moment(this.srvModel.endDate).diff(moment(), "seconds");
|
||||
this.endDiff = mDiffD > 0? mDiffD + " days" : mDiffH> 0? mDiffH + " hours" : mDiffM> 0? mDiffM+ " minutes" : mDiffS> 0? mDiffS + " seconds": "";
|
||||
}else{
|
||||
this.endDiff = null;
|
||||
}
|
||||
this.endDiff = null;
|
||||
}
|
||||
if(!this.started && this.srvModel.startDate){
|
||||
var mDiffD = moment(this.srvModel.startDate).diff(moment(), "days");
|
||||
var mDiffH = moment(this.srvModel.startDate).diff(moment(), "hours");
|
||||
|
@ -203,138 +208,143 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
|||
var mDiffS = moment(this.srvModel.startDate).diff(moment(), "seconds");
|
||||
this.startDiff = mDiffD > 0? mDiffD + " days" : mDiffH> 0? mDiffH + " hours" : mDiffM> 0? mDiffM+ " minutes" : mDiffS> 0? mDiffS + " seconds": "";
|
||||
}else {
|
||||
this.startDiff = null;
|
||||
}
|
||||
this.lastUpdated = moment(this.srvModel.info.lastUpdated).calendar();
|
||||
this.active = this.started && !this.ended;
|
||||
setTimeout(this.updateComputed, 1000);
|
||||
},
|
||||
this.startDiff = null;
|
||||
}
|
||||
this.lastUpdated = moment(this.srvModel.info.lastUpdated).calendar();
|
||||
this.active = this.started && !this.ended;
|
||||
setTimeout(this.updateComputed, 1000);
|
||||
},
|
||||
setLoading: function(val){
|
||||
this.loading = val;
|
||||
this.loading = val;
|
||||
if(this.timeoutState){
|
||||
clearTimeout(this.timeoutState);
|
||||
}
|
||||
},
|
||||
formatAmount: function(amount) {
|
||||
return formatAmount(amount, this.srvModel.currencyData.divisibility)
|
||||
},
|
||||
contribute() {
|
||||
if (!this.active || this.loading) return;
|
||||
|
||||
if (this.hasPerks){
|
||||
this.contributeModalOpen = true
|
||||
} else {
|
||||
eventAggregator.$emit("contribute", {amount: null, choiceKey: null});
|
||||
}
|
||||
clearTimeout(this.timeoutState);
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
hubListener.connect();
|
||||
var self = this;
|
||||
this.sound = this.srvModel.soundsEnabled;
|
||||
this.animation = this.srvModel.animationsEnabled;
|
||||
eventAggregator.$on("invoice-created", function (invoiceId) {
|
||||
btcpay.appendAndShowInvoiceFrame(invoiceId);
|
||||
formatAmount: function(amount) {
|
||||
return formatAmount(amount, this.srvModel.currencyData.divisibility)
|
||||
},
|
||||
contribute() {
|
||||
if (!this.active || this.loading) return;
|
||||
|
||||
self.contributeModalOpen = false;
|
||||
self.setLoading(false);
|
||||
});
|
||||
if (this.hasPerks) {
|
||||
this.contributeModalOpen = true
|
||||
} else {
|
||||
if (this.srvModel.formUrl) {
|
||||
window.location.href = this.srvModel.formUrl;
|
||||
return;
|
||||
} else {
|
||||
eventAggregator.$emit("contribute", { amount: null, choiceKey: null });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
hubListener.connect();
|
||||
var self = this;
|
||||
this.sound = this.srvModel.soundsEnabled;
|
||||
this.animation = this.srvModel.animationsEnabled;
|
||||
eventAggregator.$on("invoice-created", function (invoiceId) {
|
||||
btcpay.appendAndShowInvoiceFrame(invoiceId);
|
||||
|
||||
self.contributeModalOpen = false;
|
||||
self.setLoading(false);
|
||||
});
|
||||
|
||||
eventAggregator.$on("contribute", function () {
|
||||
self.setLoading(true);
|
||||
|
||||
eventAggregator.$on("contribute", function () {
|
||||
self.setLoading(true);
|
||||
|
||||
self.timeoutState = setTimeout(function(){
|
||||
self.setLoading(false);
|
||||
self.setLoading(false);
|
||||
},5000);
|
||||
});
|
||||
});
|
||||
eventAggregator.$on("invoice-error", function(error){
|
||||
|
||||
self.setLoading(false);
|
||||
var msg = "";
|
||||
self.setLoading(false);
|
||||
var msg = "";
|
||||
if(typeof error === "string"){
|
||||
msg = error;
|
||||
msg = error;
|
||||
}else if(!error){
|
||||
msg = "Unknown Error";
|
||||
msg = "Unknown Error";
|
||||
}else{
|
||||
msg = JSON.stringify(error);
|
||||
}
|
||||
|
||||
Vue.toasted.show("Error creating invoice: " + msg, {
|
||||
iconPack: "fontawesome",
|
||||
icon: "exclamation-triangle",
|
||||
fullWidth: false,
|
||||
theme: "bubble",
|
||||
type: "error",
|
||||
position: "top-center",
|
||||
duration: 10000
|
||||
} );
|
||||
});
|
||||
eventAggregator.$on("payment-received", function (amount, cryptoCode, type) {
|
||||
var onChain = type.toLowerCase() !== "lightninglike";
|
||||
if(self.sound) {
|
||||
playRandomSound();
|
||||
}
|
||||
if(self.animation) {
|
||||
fireworks();
|
||||
}
|
||||
amount = parseFloat(amount).noExponents();
|
||||
if(onChain){
|
||||
Vue.toasted.show('New payment of ' + amount+ " "+ cryptoCode + " " + (onChain? "On Chain": "LN "), {
|
||||
iconPack: "fontawesome",
|
||||
icon: "plus",
|
||||
duration: 10000
|
||||
} );
|
||||
}else{
|
||||
Vue.toasted.show('New payment of ' + amount+ " "+ cryptoCode + " " + (onChain? "On Chain": "LN "), {
|
||||
iconPack: "fontawesome",
|
||||
icon: "bolt",
|
||||
duration: 10000
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
if(srvModel.disqusEnabled){
|
||||
window.disqus_config = function () {
|
||||
// Replace PAGE_URL with your page's canonical URL variable
|
||||
this.page.url = window.location.href;
|
||||
|
||||
// Replace PAGE_IDENTIFIER with your page's unique identifier variable
|
||||
this.page.identifier = self.srvModel.appId;
|
||||
};
|
||||
|
||||
(function() { // REQUIRED CONFIGURATION VARIABLE: EDIT THE SHORTNAME BELOW
|
||||
var d = document, s = d.createElement('script');
|
||||
|
||||
// IMPORTANT: Replace EXAMPLE with your forum shortname!
|
||||
s.src = "https://"+self.srvModel.disqusShortname+".disqus.com/embed.js";
|
||||
s.async= true;
|
||||
s.setAttribute('data-timestamp', +new Date());
|
||||
(d.head || d.body).appendChild(s);
|
||||
|
||||
var s2 = d.createElement('script');
|
||||
s2.src="//"+self.srvModel.disqusShortname+".disqus.com/count.js";
|
||||
s2.async= true;
|
||||
s.setAttribute('data-timestamp', +new Date());
|
||||
(d.head || d.body).appendChild(s);
|
||||
})();
|
||||
msg = JSON.stringify(error);
|
||||
}
|
||||
eventAggregator.$on("info-updated", function (model) {
|
||||
console.warn("UPDATED", self.srvModel, arguments);
|
||||
self.srvModel = model;
|
||||
|
||||
Vue.toasted.show("Error creating invoice: " + msg, {
|
||||
iconPack: "fontawesome",
|
||||
icon: "exclamation-triangle",
|
||||
fullWidth: false,
|
||||
theme: "bubble",
|
||||
type: "error",
|
||||
position: "top-center",
|
||||
duration: 10000
|
||||
});
|
||||
eventAggregator.$on("connection-pending", function () {
|
||||
self.connectionStatus = "pending";
|
||||
});
|
||||
eventAggregator.$on("connection-failed", function () {
|
||||
self.connectionStatus = "failed";
|
||||
});
|
||||
eventAggregator.$on("connection-lost", function () {
|
||||
self.connectionStatus = "connection lost";
|
||||
});
|
||||
this.updateComputed();
|
||||
});
|
||||
eventAggregator.$on("payment-received", function (amount, cryptoCode, type) {
|
||||
var onChain = type.toLowerCase() !== "lightninglike";
|
||||
if (self.sound) {
|
||||
playRandomSound();
|
||||
}
|
||||
if (self.animation) {
|
||||
fireworks();
|
||||
}
|
||||
amount = parseFloat(amount).noExponents();
|
||||
if (onChain) {
|
||||
Vue.toasted.show('New payment of ' + amount + " " + cryptoCode + " " + (onChain ? "On Chain" : "LN "), {
|
||||
iconPack: "fontawesome",
|
||||
icon: "plus",
|
||||
duration: 10000
|
||||
});
|
||||
} else {
|
||||
Vue.toasted.show('New payment of ' + amount + " " + cryptoCode + " " + (onChain ? "On Chain" : "LN "), {
|
||||
iconPack: "fontawesome",
|
||||
icon: "bolt",
|
||||
duration: 10000
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
if (srvModel.disqusEnabled) {
|
||||
window.disqus_config = function () {
|
||||
// Replace PAGE_URL with your page's canonical URL variable
|
||||
this.page.url = window.location.href;
|
||||
|
||||
// Replace PAGE_IDENTIFIER with your page's unique identifier variable
|
||||
this.page.identifier = self.srvModel.appId;
|
||||
};
|
||||
|
||||
(function () { // REQUIRED CONFIGURATION VARIABLE: EDIT THE SHORTNAME BELOW
|
||||
var d = document, s = d.createElement('script');
|
||||
|
||||
// IMPORTANT: Replace EXAMPLE with your forum shortname!
|
||||
s.src = "https://" + self.srvModel.disqusShortname + ".disqus.com/embed.js";
|
||||
s.async = true;
|
||||
s.setAttribute('data-timestamp', +new Date());
|
||||
(d.head || d.body).appendChild(s);
|
||||
|
||||
var s2 = d.createElement('script');
|
||||
s2.src = "//" + self.srvModel.disqusShortname + ".disqus.com/count.js";
|
||||
s2.async = true;
|
||||
s.setAttribute('data-timestamp', +new Date());
|
||||
(d.head || d.body).appendChild(s);
|
||||
})();
|
||||
}
|
||||
});
|
||||
eventAggregator.$on("info-updated", function (model) {
|
||||
console.warn("UPDATED", self.srvModel, arguments);
|
||||
self.srvModel = model;
|
||||
});
|
||||
eventAggregator.$on("connection-pending", function () {
|
||||
self.connectionStatus = "pending";
|
||||
});
|
||||
eventAggregator.$on("connection-failed", function () {
|
||||
self.connectionStatus = "failed";
|
||||
});
|
||||
eventAggregator.$on("connection-lost", function () {
|
||||
self.connectionStatus = "connection lost";
|
||||
});
|
||||
this.updateComputed();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue