add in more info and simplify backend model

This commit is contained in:
Kukks 2019-01-02 10:41:54 +01:00
parent 2aa097be46
commit 5a3f7b5b70
8 changed files with 428 additions and 359 deletions

View file

@ -1,22 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Rating;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
namespace BTCPayServer.Hubs
{
@ -52,232 +39,4 @@ namespace BTCPayServer.Hubs
}
}
public class CrowdfundHubStreamer
{
public const string CrowdfundInvoiceOrderIdPrefix = "crowdfund-app:";
private readonly EventAggregator _EventAggregator;
private readonly IHubContext<CrowdfundHub> _HubContext;
private readonly IMemoryCache _MemoryCache;
private readonly AppsHelper _AppsHelper;
private readonly RateFetcher _RateFetcher;
private readonly BTCPayNetworkProvider _BtcPayNetworkProvider;
private readonly InvoiceRepository _InvoiceRepository;
private Dictionary<string, CancellationTokenSource> _CacheTokens = new Dictionary<string, CancellationTokenSource>();
public CrowdfundHubStreamer(EventAggregator eventAggregator,
IHubContext<CrowdfundHub> hubContext,
IMemoryCache memoryCache,
AppsHelper appsHelper,
RateFetcher rateFetcher,
BTCPayNetworkProvider btcPayNetworkProvider,
InvoiceRepository invoiceRepository)
{
_EventAggregator = eventAggregator;
_HubContext = hubContext;
_MemoryCache = memoryCache;
_AppsHelper = appsHelper;
_RateFetcher = rateFetcher;
_BtcPayNetworkProvider = btcPayNetworkProvider;
_InvoiceRepository = invoiceRepository;
SubscribeToEvents();
}
public Task<ViewCrowdfundViewModel> GetCrowdfundInfo(string appId)
{
var key = GetCacheKey(appId);
return _MemoryCache.GetOrCreateAsync(key, async entry =>
{
if (_CacheTokens.ContainsKey(key))
{
_CacheTokens.Remove(key);
}
var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund, true);
var result = await GetInfo(app);
entry.SetValue(result);
var token = new CancellationTokenSource();
_CacheTokens.Add(key, token);
entry.AddExpirationToken(new CancellationChangeToken(token.Token));
TimeSpan? expire = null;
if (result.StartDate.HasValue && result.StartDate < DateTime.Now)
{
expire = result.StartDate.Value.Subtract(DateTime.Now);
}
else if (result.EndDate.HasValue && result.EndDate > DateTime.Now)
{
expire = result.EndDate.Value.Subtract(DateTime.Now);
}
if(!expire.HasValue || expire?.TotalMinutes > 5)
{
expire = TimeSpan.FromMinutes(5);
}
entry.AbsoluteExpirationRelativeToNow = expire;
return result;
});
}
private void SubscribeToEvents()
{
_EventAggregator.Subscribe<InvoiceEvent>(Subscription);
_EventAggregator.Subscribe<AppsController.CrowdfundAppUpdated>(updated =>
{
InvalidateCacheForApp(updated.AppId);
});
}
private string GetCacheKey(string appId)
{
return $"{CrowdfundInvoiceOrderIdPrefix}:{appId}";
}
private void Subscription(InvoiceEvent invoiceEvent)
{
if (!invoiceEvent.Invoice.OrderId.StartsWith(CrowdfundInvoiceOrderIdPrefix, StringComparison.InvariantCultureIgnoreCase))
{
return;
}
var appId = invoiceEvent.Invoice.OrderId.Replace(CrowdfundInvoiceOrderIdPrefix, "", StringComparison.InvariantCultureIgnoreCase);
switch (invoiceEvent.Name)
{
case InvoiceEvent.ReceivedPayment:
_HubContext.Clients.Group(appId).SendCoreAsync(CrowdfundHub.PaymentReceived, new object[]
{
invoiceEvent.Payment.GetCryptoPaymentData().GetValue(),
invoiceEvent.Payment.GetCryptoCode(),
Enum.GetName(typeof(PaymentTypes),
invoiceEvent.Payment.GetPaymentMethodId().PaymentType)
} );
InvalidateCacheForApp(appId);
break;
case InvoiceEvent.Completed:
InvalidateCacheForApp(appId);
break;
}
}
private void InvalidateCacheForApp(string appId)
{
if (_CacheTokens.ContainsKey(appId))
{
_CacheTokens[appId].Cancel();
}
GetCrowdfundInfo(appId).ContinueWith(task =>
{
_HubContext.Clients.Group(appId).SendCoreAsync(CrowdfundHub.InfoUpdated, new object[]{ task.Result} );
}, TaskScheduler.Default);
}
private static async Task<decimal> GetCurrentContributionAmount(InvoiceEntity[] invoices, string primaryCurrency,
RateFetcher rateFetcher, RateRules rateRules)
{
decimal result = 0;
var groupingByCurrency = invoices.GroupBy(entity => entity.ProductInformation.Currency);
var ratesTask = rateFetcher.FetchRates(
groupingByCurrency
.Select((entities) => new CurrencyPair(entities.Key, primaryCurrency))
.ToHashSet(),
rateRules);
var finalTasks = new List<Task>();
foreach (var rateTask in ratesTask)
{
finalTasks.Add(Task.Run(async () =>
{
var tResult = await rateTask.Value;
var rate = tResult.BidAsk?.Bid;
if (rate == null) return;
var currencyGroup = groupingByCurrency.Single(entities => entities.Key == rateTask.Key.Left);
result += currencyGroup.Sum(entity => entity.ProductInformation.Price / rate.Value);
}));
}
await Task.WhenAll(finalTasks);
return result;
}
private async Task<ViewCrowdfundViewModel> GetInfo(AppData appData, string statusMessage= null)
{
var settings = appData.GetSettings<AppsController.CrowdfundSettings>();
var invoices = await GetInvoicesForApp(appData, _InvoiceRepository);
var rateRules = appData.StoreData.GetStoreBlob().GetRateRules(_BtcPayNetworkProvider);
var currentAmount = await GetCurrentContributionAmount(
invoices.Where(entity => entity.Status == InvoiceStatus.Complete).ToArray(),
settings.TargetCurrency, _RateFetcher, rateRules);
var currentPendingAmount = await GetCurrentContributionAmount(
invoices.Where(entity => entity.Status != InvoiceStatus.Complete).ToArray(),
settings.TargetCurrency, _RateFetcher, rateRules);
var active = (settings.StartDate == null || DateTime.Now >= settings.StartDate) &&
(settings.EndDate == null || DateTime.Now <= settings.EndDate) &&
(!settings.EnforceTargetAmount || settings.TargetAmount > currentAmount);
return new ViewCrowdfundViewModel()
{
Title = settings.Title,
Tagline = settings.Tagline,
Description = settings.Description,
CustomCSSLink = settings.CustomCSSLink,
MainImageUrl = settings.MainImageUrl,
EmbeddedCSS = settings.EmbeddedCSS,
StoreId = appData.StoreDataId,
AppId = appData.Id,
StartDate = settings.StartDate,
EndDate = settings.EndDate,
TargetAmount = settings.TargetAmount,
TargetCurrency = settings.TargetCurrency,
EnforceTargetAmount = settings.EnforceTargetAmount,
StatusMessage = statusMessage,
Perks = _AppsHelper.Parse(settings.PerksTemplate, settings.TargetCurrency),
DisqusEnabled = settings.DisqusEnabled,
SoundsEnabled = settings.SoundsEnabled,
DisqusShortname = settings.DisqusShortname,
AnimationsEnabled = settings.AnimationsEnabled,
Info = new ViewCrowdfundViewModel.CrowdfundInfo()
{
TotalContributors = invoices.Length,
CurrentPendingAmount = currentPendingAmount,
CurrentAmount = currentAmount,
Active = active,
DaysLeft = settings.EndDate.HasValue? (settings.EndDate - DateTime.UtcNow).Value.Days: (int?) null,
DaysLeftToStart = settings.StartDate.HasValue? (settings.StartDate - DateTime.UtcNow).Value.Days: (int?) null,
ShowProgress = settings.TargetAmount.HasValue,
ProgressPercentage = (currentAmount/ settings.TargetAmount) * 100,
PendingProgressPercentage = ( currentPendingAmount/ settings.TargetAmount) * 100,
LastUpdated = DateTime.Now
}
};
}
private static async Task<InvoiceEntity[]> GetInvoicesForApp(AppData appData, InvoiceRepository invoiceRepository)
{
return await invoiceRepository.GetInvoices(new InvoiceQuery()
{
OrderId = $"{CrowdfundInvoiceOrderIdPrefix}{appData.Id}",
Status = new string[]{
InvoiceState.ToString(InvoiceStatus.New),
InvoiceState.ToString(InvoiceStatus.Paid),
InvoiceState.ToString(InvoiceStatus.Confirmed),
InvoiceState.ToString(InvoiceStatus.Complete)}
});
}
}
}

View file

@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Rating;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;
namespace BTCPayServer.Hubs
{
public class CrowdfundHubStreamer
{
public const string CrowdfundInvoiceOrderIdPrefix = "crowdfund-app:";
private readonly EventAggregator _EventAggregator;
private readonly IHubContext<CrowdfundHub> _HubContext;
private readonly IMemoryCache _MemoryCache;
private readonly AppsHelper _AppsHelper;
private readonly RateFetcher _RateFetcher;
private readonly BTCPayNetworkProvider _BtcPayNetworkProvider;
private readonly InvoiceRepository _InvoiceRepository;
private Dictionary<string, CancellationTokenSource> _CacheTokens = new Dictionary<string, CancellationTokenSource>();
public CrowdfundHubStreamer(EventAggregator eventAggregator,
IHubContext<CrowdfundHub> hubContext,
IMemoryCache memoryCache,
AppsHelper appsHelper,
RateFetcher rateFetcher,
BTCPayNetworkProvider btcPayNetworkProvider,
InvoiceRepository invoiceRepository)
{
_EventAggregator = eventAggregator;
_HubContext = hubContext;
_MemoryCache = memoryCache;
_AppsHelper = appsHelper;
_RateFetcher = rateFetcher;
_BtcPayNetworkProvider = btcPayNetworkProvider;
_InvoiceRepository = invoiceRepository;
SubscribeToEvents();
}
public Task<ViewCrowdfundViewModel> GetCrowdfundInfo(string appId)
{
var key = GetCacheKey(appId);
return _MemoryCache.GetOrCreateAsync(key, async entry =>
{
if (_CacheTokens.ContainsKey(key))
{
_CacheTokens.Remove(key);
}
var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund, true);
var result = await GetInfo(app);
entry.SetValue(result);
var token = new CancellationTokenSource();
_CacheTokens.Add(key, token);
entry.AddExpirationToken(new CancellationChangeToken(token.Token));
TimeSpan? expire = null;
if (result.StartDate.HasValue && result.StartDate < DateTime.Now)
{
expire = result.StartDate.Value.Subtract(DateTime.Now);
}
else if (result.EndDate.HasValue && result.EndDate > DateTime.Now)
{
expire = result.EndDate.Value.Subtract(DateTime.Now);
}
if(!expire.HasValue || expire?.TotalMinutes > 5 || expire?.TotalMilliseconds <= 0)
{
expire = TimeSpan.FromMinutes(5);
}
entry.AbsoluteExpirationRelativeToNow = expire;
return result;
});
}
private void SubscribeToEvents()
{
_EventAggregator.Subscribe<InvoiceEvent>(Subscription);
_EventAggregator.Subscribe<AppsController.CrowdfundAppUpdated>(updated =>
{
InvalidateCacheForApp(updated.AppId);
});
}
private string GetCacheKey(string appId)
{
return $"{CrowdfundInvoiceOrderIdPrefix}:{appId}";
}
private void Subscription(InvoiceEvent invoiceEvent)
{
if (!invoiceEvent.Invoice.OrderId.StartsWith(CrowdfundInvoiceOrderIdPrefix, StringComparison.InvariantCultureIgnoreCase))
{
return;
}
var appId = invoiceEvent.Invoice.OrderId.Replace(CrowdfundInvoiceOrderIdPrefix, "", StringComparison.InvariantCultureIgnoreCase);
switch (invoiceEvent.Name)
{
case InvoiceEvent.ReceivedPayment:
_HubContext.Clients.Group(appId).SendCoreAsync(CrowdfundHub.PaymentReceived, new object[]
{
invoiceEvent.Payment.GetCryptoPaymentData().GetValue(),
invoiceEvent.Payment.GetCryptoCode(),
Enum.GetName(typeof(PaymentTypes),
invoiceEvent.Payment.GetPaymentMethodId().PaymentType)
} );
InvalidateCacheForApp(appId);
break;
case InvoiceEvent.Completed:
InvalidateCacheForApp(appId);
break;
}
}
private void InvalidateCacheForApp(string appId)
{
if (_CacheTokens.ContainsKey(appId))
{
_CacheTokens[appId].Cancel();
}
GetCrowdfundInfo(appId).ContinueWith(task =>
{
_HubContext.Clients.Group(appId).SendCoreAsync(CrowdfundHub.InfoUpdated, new object[]{ task.Result} );
}, TaskScheduler.Default);
}
private static async Task<decimal> GetCurrentContributionAmount(InvoiceEntity[] invoices, string primaryCurrency,
RateFetcher rateFetcher, RateRules rateRules)
{
decimal result = 0;
var groupingByCurrency = invoices.GroupBy(entity => entity.ProductInformation.Currency);
var ratesTask = rateFetcher.FetchRates(
groupingByCurrency
.Select((entities) => new CurrencyPair(entities.Key, primaryCurrency))
.ToHashSet(),
rateRules);
var finalTasks = new List<Task>();
foreach (var rateTask in ratesTask)
{
finalTasks.Add(Task.Run(async () =>
{
var tResult = await rateTask.Value;
var rate = tResult.BidAsk?.Bid;
if (rate == null) return;
var currencyGroup = groupingByCurrency.Single(entities => entities.Key == rateTask.Key.Left);
result += currencyGroup.Sum(entity => entity.ProductInformation.Price / rate.Value);
}));
}
await Task.WhenAll(finalTasks);
return result;
}
private static Dictionary<string, decimal> GetCurrentContributionAmountStats(InvoiceEntity[] invoices)
{
var payments = invoices.SelectMany(entity => entity.GetPayments());
var groupedByMethod = payments.GroupBy(entity => entity.GetPaymentMethodId());
return groupedByMethod.ToDictionary(entities => entities.Key.ToString(),
entities => entities.Sum(entity => entity.GetCryptoPaymentData().GetValue()));
}
private async Task<ViewCrowdfundViewModel> GetInfo(AppData appData, string statusMessage= null)
{
var settings = appData.GetSettings<AppsController.CrowdfundSettings>();
var invoices = await GetInvoicesForApp(appData, _InvoiceRepository);
var completeInvoices = invoices.Where(entity => entity.Status == InvoiceStatus.Complete).ToArray();
var pendingInvoices = invoices.Where(entity => entity.Status != InvoiceStatus.Complete).ToArray();
var rateRules = appData.StoreData.GetStoreBlob().GetRateRules(_BtcPayNetworkProvider);
var currentAmount = await GetCurrentContributionAmount(
completeInvoices,
settings.TargetCurrency, _RateFetcher, rateRules);
var currentPendingAmount = await GetCurrentContributionAmount(
pendingInvoices,
settings.TargetCurrency, _RateFetcher, rateRules);
var pendingPaymentStats = GetCurrentContributionAmountStats(pendingInvoices);
var paymentStats = GetCurrentContributionAmountStats(completeInvoices);
return new ViewCrowdfundViewModel()
{
Title = settings.Title,
Tagline = settings.Tagline,
Description = settings.Description,
CustomCSSLink = settings.CustomCSSLink,
MainImageUrl = settings.MainImageUrl,
EmbeddedCSS = settings.EmbeddedCSS,
StoreId = appData.StoreDataId,
AppId = appData.Id,
StartDate = settings.StartDate,
EndDate = settings.EndDate,
TargetAmount = settings.TargetAmount,
TargetCurrency = settings.TargetCurrency,
EnforceTargetAmount = settings.EnforceTargetAmount,
StatusMessage = statusMessage,
Perks = _AppsHelper.Parse(settings.PerksTemplate, settings.TargetCurrency),
DisqusEnabled = settings.DisqusEnabled,
SoundsEnabled = settings.SoundsEnabled,
DisqusShortname = settings.DisqusShortname,
AnimationsEnabled = settings.AnimationsEnabled,
Info = new ViewCrowdfundViewModel.CrowdfundInfo()
{
TotalContributors = invoices.Length,
CurrentPendingAmount = currentPendingAmount,
CurrentAmount = currentAmount,
ProgressPercentage = (currentAmount/ settings.TargetAmount) * 100,
PendingProgressPercentage = ( currentPendingAmount/ settings.TargetAmount) * 100,
LastUpdated = DateTime.Now,
PaymentStats = paymentStats,
PendingPaymentStats = pendingPaymentStats
}
};
}
private static async Task<InvoiceEntity[]> GetInvoicesForApp(AppData appData, InvoiceRepository invoiceRepository)
{
return await invoiceRepository.GetInvoices(new InvoiceQuery()
{
OrderId = $"{CrowdfundInvoiceOrderIdPrefix}{appData.Id}",
Status = new string[]{
InvoiceState.ToString(InvoiceStatus.New),
InvoiceState.ToString(InvoiceStatus.Paid),
InvoiceState.ToString(InvoiceStatus.Confirmed),
InvoiceState.ToString(InvoiceStatus.Complete)}
});
}
}
}

View file

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace BTCPayServer.Models.AppViewModels
{
@ -32,20 +34,16 @@ namespace BTCPayServer.Models.AppViewModels
public class CrowdfundInfo
{
public int TotalContributors { get; set; }
public decimal CurrentAmount { get; set; }
public bool Active { get; set; }
public bool ShowProgress { get; set; }
public decimal? ProgressPercentage { get; set; }
public int? DaysLeft{ get; set; }
public int? DaysLeftToStart{ get; set; }
public decimal CurrentPendingAmount { get; set; }
public decimal CurrentAmount { get; set; }
public decimal? ProgressPercentage { get; set; }
public decimal? PendingProgressPercentage { get; set; }
public DateTime LastUpdated { get; set; }
public Dictionary<string, decimal> PaymentStats { get; set; }
public Dictionary<string, decimal> PendingPaymentStats { get; set; }
}
}
public class ContributeToCrowdfund
{

View file

@ -1,101 +1,101 @@
@using BTCPayServer.Models.AppViewModels
@model BTCPayServer.Models.AppViewModels.ViewCrowdfundViewModel
<div class="container p-0" >
<div class="row h-100 w-100 py-sm-0 py-md-4 mx-0">
<div class="card w-100 p-0 mx-0">
<partial name="_StatusMessage" for="@Model.StatusMessage"/>
@if (!string.IsNullOrEmpty(Model.MainImageUrl))
{
<img class="card-img-top" src="@Model.MainImageUrl" alt="Card image cap">
}
@if (Model.Info.ShowProgress)
{
<div class="progress rounded-0 striped" style="min-height: 30px">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="@Model.Info.ProgressPercentage" aria-valuemin="0" aria-valuemax="100">
@if (Model.Info.ProgressPercentage.Value > 0)
{
@(Model.Info.ProgressPercentage + "%")
}
</div>
</div>
}
<div class="card-body">
<div class="card-title row">
<div class="col-md-9 col-sm-12">
<h1 >
@Model.Title
@if (!string.IsNullOrEmpty(Model.Tagline))
{
<h2 class="text-muted">@Model.Tagline</h2>
}
@if (Model.Info.DaysLeftToStart.HasValue && Model.Info.DaysLeftToStart > 0)
{
<small>
@($"{Model.Info.DaysLeftToStart} day{(Model.Info.DaysLeftToStart.Value > 1 ? "s" : "")} left to start")
</small>
}
</h1>
</div>
<ul class="list-group list-group-flush col-md-3 col-sm-12">
<li class="list-group-item">@(Model.EndDate.HasValue? $"Ends {Model.EndDate.Value:dddd, dd MMMM yyyy HH:mm}" : "No specific end date")</li>
<li class="list-group-item">@(Model.TargetAmount.HasValue? $"{Model.TargetAmount:G29} {Model.TargetCurrency.ToUpperInvariant()} Goal" :
"No specific target goal")</li>
<li class="list-group-item">@(Model.EnforceTargetAmount? $"Hardcap Goal" : "Softcap Goal")</li>
</ul>
</div>
@if (Model.Info.Active)
{
<div class="card-deck mb-4 ">
<div class="card shadow">
<div class="card-body">
<h5 class="card-title text-center">@Model.Info.TotalContributors</h5>
<h6 class="card-text text-center"> Contributors</h6>
</div>
</div>
<div class="card shadow">
<div class="card-body">
<h5 class="card-title text-center">@Model.Info.CurrentAmount @Model.TargetCurrency.ToUpperInvariant()</h5>
<h6 class="card-text text-center"> Raised</h6>
</div>
</div>
@if (Model.Info.DaysLeft.HasValue && Model.Info.DaysLeft > 0)
{
<div class="card shadow">
<div class="card-body">
<h5 class="card-title text-center">@Model.Info.DaysLeft</h5>
<h6 class="card-text text-center">Day@(Model.Info.DaysLeft.Value > 1 ? "s" : "") left</h6>
</div>
</div>
}
</div>
}
<div class="card-text"> @Html.Raw(Model.Description)</div>
@if (Model.Info.Active)
{
<hr/>
<h3>Contribute</h3>
<partial name="Crowdfund/ContributeForm" model="@(new ContributeToCrowdfund()
{
RedirectToCheckout = true,
ViewCrowdfundViewModel = Model
})"/>
}
</div>
</div>
</div>
</div>
@* @using BTCPayServer.Models.AppViewModels *@
@* @model BTCPayServer.Models.AppViewModels.ViewCrowdfundViewModel *@
@* <div class="container p-0" > *@
@* *@
@* *@
@* <div class="row h-100 w-100 py-sm-0 py-md-4 mx-0"> *@
@* *@
@* <div class="card w-100 p-0 mx-0"> *@
@* <partial name="_StatusMessage" for="@Model.StatusMessage"/> *@
@* @if (!string.IsNullOrEmpty(Model.MainImageUrl)) *@
@* { *@
@* <img class="card-img-top" src="@Model.MainImageUrl" alt="Card image cap"> *@
@* } *@
@* @if (Model.Info.ShowProgress) *@
@* { *@
@* <div class="progress rounded-0 striped" style="min-height: 30px"> *@
@* <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="@Model.Info.ProgressPercentage" aria-valuemin="0" aria-valuemax="100"> *@
@* @if (Model.Info.ProgressPercentage.Value > 0) *@
@* { *@
@* @(Model.Info.ProgressPercentage + "%") *@
@* } *@
@* </div> *@
@* </div> *@
@* } *@
@* <div class="card-body"> *@
@* <div class="card-title row"> *@
@* <div class="col-md-9 col-sm-12"> *@
@* *@
@* <h1 > *@
@* @Model.Title *@
@* @if (!string.IsNullOrEmpty(Model.Tagline)) *@
@* { *@
@* <h2 class="text-muted">@Model.Tagline</h2> *@
@* } *@
@* *@
@* @if (Model.Info.DaysLeftToStart.HasValue && Model.Info.DaysLeftToStart > 0) *@
@* { *@
@* <small> *@
@* @($"{Model.Info.DaysLeftToStart} day{(Model.Info.DaysLeftToStart.Value > 1 ? "s" : "")} left to start") *@
@* *@
@* </small> *@
@* } *@
@* </h1> *@
@* *@
@* </div> *@
@* <ul class="list-group list-group-flush col-md-3 col-sm-12"> *@
@* <li class="list-group-item">@(Model.EndDate.HasValue? $"Ends {Model.EndDate.Value:dddd, dd MMMM yyyy HH:mm}" : "No specific end date")</li> *@
@* <li class="list-group-item">@(Model.TargetAmount.HasValue? $"{Model.TargetAmount:G29} {Model.TargetCurrency.ToUpperInvariant()} Goal" : *@
@* "No specific target goal")</li> *@
@* <li class="list-group-item">@(Model.EnforceTargetAmount? $"Hardcap Goal" : "Softcap Goal")</li> *@
@* </ul> *@
@* *@
@* </div> *@
@* @if (Model.Info.Active) *@
@* { *@
@* <div class="card-deck mb-4 "> *@
@* <div class="card shadow"> *@
@* <div class="card-body"> *@
@* <h5 class="card-title text-center">@Model.Info.TotalContributors</h5> *@
@* <h6 class="card-text text-center"> Contributors</h6> *@
@* </div> *@
@* </div> *@
@* <div class="card shadow"> *@
@* <div class="card-body"> *@
@* <h5 class="card-title text-center">@Model.Info.CurrentAmount @Model.TargetCurrency.ToUpperInvariant()</h5> *@
@* <h6 class="card-text text-center"> Raised</h6> *@
@* </div> *@
@* </div> *@
@* *@
@* @if (Model.Info.DaysLeft.HasValue && Model.Info.DaysLeft > 0) *@
@* { *@
@* <div class="card shadow"> *@
@* <div class="card-body"> *@
@* <h5 class="card-title text-center">@Model.Info.DaysLeft</h5> *@
@* <h6 class="card-text text-center">Day@(Model.Info.DaysLeft.Value > 1 ? "s" : "") left</h6> *@
@* </div> *@
@* </div> *@
@* } *@
@* *@
@* *@
@* </div> *@
@* } *@
@* *@
@* *@
@* <div class="card-text"> @Html.Raw(Model.Description)</div> *@
@* @if (Model.Info.Active) *@
@* { *@
@* <hr/> *@
@* <h3>Contribute</h3> *@
@* <partial name="Crowdfund/ContributeForm" model="@(new ContributeToCrowdfund() *@
@* { *@
@* RedirectToCheckout = true, *@
@* ViewCrowdfundViewModel = Model *@
@* })"/> *@
@* } *@
@* </div> *@
@* </div> *@
@* *@
@* *@
@* </div> *@
@* </div> *@

View file

@ -26,7 +26,7 @@
</div>
<div class="progress w-100 rounded-0 " v-if="srvModel.info.showProgress">
<div class="progress w-100 rounded-0 " v-if="srvModel.targetAmount">
<div class="progress-bar" role="progressbar"
:aria-valuenow="srvModel.info.progressPercentage"
v-bind:style="{ width: srvModel.info.progressPercentage + '%' }"
@ -44,9 +44,10 @@
</div>
<div class="card-body">
<div class="row py-2 text-center">
<div class="col-sm border-right">
<div class="col-sm border-right" id="raised-amount">
<h5>{{srvModel.info.currentAmount + srvModel.info.currentPendingAmount }} {{targetCurrency}} </h5>
<h5 class="text-muted">Raised</h5>
</div>
@ -72,13 +73,34 @@
</h5>
<h5 class="text-muted">Left to start</h5>
</div>
<div class="col-sm" v-if="ended">
<div class="col-sm" v-if="ended" id="inactive-campaign">
<h5>
Campaign
</h5>
<h5 >not active</h5>
<b-tooltip target="inactive-campaign" >
<ul class="p-0">
<li v-if="startDate" style="list-style-type: none">
{{started? "Started" : "Starts"}} {{startDate}}
</li>
<li v-if="endDate" style="list-style-type: none">
{{ended? "Ended" : "End"}} {{endDate}}
</li>
</ul>
</b-tooltip>
</div>
</div>
<b-tooltip target="raised-amount" >
<ul class="p-0">
<li v-for="stat of paymentStats" style="list-style-type: none">
{{stat.label}} {{stat.value}}
</li>
</ul>
</b-tooltip>
<div class="card-title">
<div class="row">
@ -165,7 +187,7 @@
<script type="text/x-template" id="perks-template">
<div >
<perk :perk="{title: 'Donate Custom Amount', price: { value: null}, custom: true}" :target-currency="targetCurrency" :active="active"
<perk v-if="!perks || perks.length ===0" :perk="{title: 'Donate Custom Amount', price: { value: null}, custom: true}" :target-currency="targetCurrency" :active="active"
:in-modal="inModal">
</perk>
<perk v-for="(perk, index) in perks" :perk="perk" :target-currency="targetCurrency" :active="active"

View file

@ -23,7 +23,8 @@
<script type="text/javascript">
var srvModel = @Html.Raw(Json.Serialize(Model));
</script>
<bundle name="wwwroot/bundles/crowdfund-bundle.min.js"></bundle>
<bundle name="wwwroot/bundles/crowdfund-bundle-1.min.js"></bundle>
<bundle name="wwwroot/bundles/crowdfund-bundle-2.min.js"></bundle>
<bundle name="wwwroot/bundles/crowdfund-bundle.min.css"></bundle>
}
@if (!string.IsNullOrEmpty(Model.EmbeddedCSS))

View file

@ -65,7 +65,7 @@
},
{
"outputFileName": "wwwroot/bundles/crowdfund.min.js",
"outputFileName": "wwwroot/bundles/crowdfund-bundle-1.min.js",
"inputFiles": [
"wwwroot/vendor/vuejs/vue.min.js",
"wwwroot/vendor/babel-polyfill/polyfill.min.js",
@ -79,9 +79,8 @@
},
{
"outputFileName": "wwwroot/bundles/crowdfund-bundle.min.js",
"outputFileName": "wwwroot/bundles/crowdfund-bundle-2.min.js",
"inputFiles": [
"wwwroot/bundles/crowdfund.min.js",
"wwwroot/vendor/moment/moment.js"
],
"minify": {

View file

@ -75,13 +75,50 @@ addLoadEvent(function (ev) {
contributeModalOpen: false,
endDiff: "",
startDiff: "",
active: true
active: true,
animation: true,
sound: true
}
},
computed: {
targetCurrency: function(){
return this.srvModel.targetCurrency.toUpperCase();
},
paymentStats: function(){
var result= [];
var combinedStats = {};
var keys = Object.keys(this.srvModel.info.paymentStats);
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++) {
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++) {
var newItem = {key:keys[i], value: combinedStats[keys[i]], label: keys[i].replace("_","")};
result.push(newItem);
}
return result;
}
},
methods: {