Added subscription ui for user

This commit is contained in:
Marcel Baumgartner
2023-04-04 01:23:07 +02:00
parent 2298bab71e
commit bb7be3b820
8 changed files with 329 additions and 42 deletions

View File

@@ -5,10 +5,12 @@ namespace Moonlight.App.Services.Interop;
public class AlertService
{
private readonly SweetAlertService SweetAlertService;
private readonly SmartTranslateService SmartTranslateService;
public AlertService(SweetAlertService service)
public AlertService(SweetAlertService service, SmartTranslateService smartTranslateService)
{
SweetAlertService = service;
SmartTranslateService = smartTranslateService;
}
public async Task Info(string title, string desciption)
@@ -21,6 +23,11 @@ public class AlertService
});
}
public async Task Info(string desciption)
{
await Info("", desciption);
}
public async Task Success(string title, string desciption)
{
await SweetAlertService.FireAsync(new SweetAlertOptions()
@@ -31,6 +38,11 @@ public class AlertService
});
}
public async Task Success(string desciption)
{
await Success("", desciption);
}
public async Task Warning(string title, string desciption)
{
await SweetAlertService.FireAsync(new SweetAlertOptions()
@@ -41,6 +53,11 @@ public class AlertService
});
}
public async Task Warning(string desciption)
{
await Warning("", desciption);
}
public async Task Error(string title, string desciption)
{
await SweetAlertService.FireAsync(new SweetAlertOptions()
@@ -51,6 +68,11 @@ public class AlertService
});
}
public async Task Error(string desciption)
{
await Error("", desciption);
}
public async Task<bool> YesNo(string title, string desciption, string yesText, string noText)
{
var result = await SweetAlertService.FireAsync(new SweetAlertOptions()
@@ -79,4 +101,27 @@ public class AlertService
return result.Value;
}
public async Task<bool> ConfirmMath()
{
var r = new Random();
var i1 = r.Next(5, 15);
var i2 = r.Next(5, 15);
var input = await Text(
SmartTranslateService.Translate("Confirm"),
SmartTranslateService.Translate($"{i1} + {i2} ="),
""
);
if (int.TryParse(input, out int i))
{
if (i == i1 + i2)
{
return true;
}
}
return false;
}
}

View File

@@ -15,4 +15,9 @@ public class ClipboardService
{
await JsRuntime.InvokeVoidAsync("copyTextToClipboard", data);
}
public async Task Copy(string data)
{
await JsRuntime.InvokeVoidAsync("copyTextToClipboard", data);
}
}

View File

@@ -78,55 +78,45 @@ public class SubscriptionService
await OneTimeJwtService.Revoke(code);
}
public async Task Cancel()
{
if (await GetCurrent() != null)
{
var user = await GetCurrentUser();
user.CurrentSubscription = null;
UserRepository.Update(user);
}
}
public async Task<SubscriptionLimit> GetLimit(string identifier)
{
var configSection = ConfigService.GetSection("Moonlight").GetSection("Subscriptions");
var defaultLimits = configSection.GetValue<SubscriptionLimit[]>("DefaultLimits");
var subscription = await GetCurrent();
if (subscription == null)
{
if (defaultLimits != null)
{
var foundDefault = defaultLimits.FirstOrDefault(x => x.Identifier == identifier);
if (foundDefault != null)
return foundDefault;
}
return new()
{
Identifier = identifier,
Amount = 0
};
}
else
var subscriptionLimits =
JsonConvert.DeserializeObject<SubscriptionLimit[]>(subscription.LimitsJson)
?? Array.Empty<SubscriptionLimit>();
var foundLimit = subscriptionLimits.FirstOrDefault(x => x.Identifier == identifier);
if (foundLimit != null)
return foundLimit;
return new()
{
var subscriptionLimits =
JsonConvert.DeserializeObject<SubscriptionLimit[]>(subscription.LimitsJson)
?? Array.Empty<SubscriptionLimit>();
var foundLimit = subscriptionLimits.FirstOrDefault(x => x.Identifier == identifier);
if (foundLimit != null)
return foundLimit;
if (defaultLimits != null)
{
var foundDefault = defaultLimits.FirstOrDefault(x => x.Identifier == identifier);
if (foundDefault != null)
return foundDefault;
}
return new()
{
Identifier = identifier,
Amount = 0
};
}
Identifier = identifier,
Amount = 0
};
}
private async Task<User?> GetCurrentUser()

View File

@@ -3,10 +3,15 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using BlazorTable
@using Moonlight.App.Services.Interop
@inject SmartTranslateService SmartTranslateService
@inject SubscriptionRepository SubscriptionRepository
@inject SubscriptionAdminService SubscriptionAdminService
@inject AlertService AlertService
@inject ClipboardService ClipboardService
<OnlyAdmin>
<div class="card">
<LazyLoader @ref="LazyLoader" Load="Load">
@@ -36,9 +41,16 @@
</a>
</Template>
</Column>
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Column TableItem="Subscription" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<DeleteButton Confirm="true" OnClick="() => Delete(context)" />
<div class="float-end">
<WButton Text="@(SmartTranslateService.Translate("Create code"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-primary"
OnClick="() => GenerateCode(context)">
</WButton>
<DeleteButton Confirm="true" OnClick="() => Delete(context)"/>
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
@@ -69,4 +81,21 @@
await LazyLoader.Reload();
}
private async Task GenerateCode(Subscription subscription)
{
var durationText = await AlertService.Text(
SmartTranslateService.Translate("Duration"),
SmartTranslateService.Translate("Enter duration of subscription"),
"30"
);
if (int.TryParse(durationText, out int duration))
{
var code = await SubscriptionAdminService.GenerateCode(subscription, duration);
await ClipboardService.Copy(code);
await AlertService.Success(SmartTranslateService.Translate("Copied code to clipboard"));
}
}
}

View File

@@ -0,0 +1,112 @@
@page "/profile/subscriptions"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Services.Interop
@inject ConfigService ConfigService
@inject AlertService AlertService
@inject SubscriptionService SubscriptionService
@inject SmartTranslateService SmartTranslateService
<ProfileNavigation Index="2"/>
<div class="card mb-3">
<div class="row g-0">
<div class="col-md-4 p-10">
<img src="/assets/media/svg/subscription.svg" class="img-fluid rounded-start" alt="Subscription">
</div>
<div class="col-md-8">
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
@if (Subscription == null)
{
var config = ConfigService
.GetSection("Moonlight")
.GetSection("Subscriptions")
.GetSection("Sellpass");
var enableSellpass = config.GetValue<bool>("Enable");
var url = config.GetValue<string>("Url");
<h3 class="mb-2">
<div class="input-group mb-3">
<input @bind="Code" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter code"))">
<WButton Text="@(SmartTranslateService.Translate("Submit"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn btn-primary"
OnClick="OnSubmit">
</WButton>
</div>
</h3>
if (enableSellpass)
{
<div class="d-flex justify-content-end pb-0 px-0">
<a href="@(url)" class="btn btn-light">Buy subscription</a>
</div>
}
}
else
{
var d = User.SubscriptionSince.AddDays(User.SubscriptionDuration).ToUniversalTime();
<h3 class="mb-2">
<TL>Active until</TL> @(Formatter.FormatDateOnly(d))
</h3>
<p class="fs-5 text-gray-600 fw-semibold">
<TL>Current subscription</TL>: @(Subscription.Name)
</p>
<p class="fs-6 text-gray-600 fw-semibold">
@(Subscription.Description)
</p>
<p class="fs-7 text-gray-600 fw-semibold">
<TL>We will send you a notification upon subscription expiration</TL>
</p>
<div class="d-flex justify-content-end pb-0 px-0">
<WButton Text="@(SmartTranslateService.Translate("Cancel"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn btn-light"
OnClick="Cancel">
</WButton>
</div>
}
</LazyLoader>
</div>
</div>
</div>
</div>
@code
{
[CascadingParameter]
public User User { get; set; }
private Subscription? Subscription;
private LazyLoader LazyLoader;
private string Code = "";
private async Task Load(LazyLoader arg)
{
Subscription = await SubscriptionService.GetCurrent();
}
private async Task Cancel()
{
if (await AlertService.ConfirmMath())
{
await SubscriptionService.Cancel();
await LazyLoader.Reload();
}
}
private async Task OnSubmit()
{
await SubscriptionService.ApplyCode(Code);
Code = "";
await LazyLoader.Reload();
}
}

View File

@@ -1,14 +1,20 @@
@page "/servers/new"
@page "/servers/create"
@using Moonlight.App.Services
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Forms
@using Moonlight.App.Models.Misc
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Servers
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Exceptions
@inject SubscriptionService SubscriptionService
@inject ImageRepository ImageRepository
@inject SmartTranslateService SmartTranslateService
@inject SmartDeployService SmartDeployService
@inject ServerRepository ServerRepository
@inject NavigationManager NavigationManager
@inject ServerService ServerService
<LazyLoader Load="Load">
@if (DeployNode == null)
@@ -133,6 +139,9 @@
@code
{
[CascadingParameter]
public User User { get; set; }
private Node? DeployNode;
private Subscription? Subscription;
@@ -163,12 +172,55 @@
if (limit.Amount > 0)
{
Images.Add(image, limit);
var serversCount = ServerRepository
.Get()
.Include(x => x.Owner)
.Include(x => x.Image)
.Where(x => x.Owner.Id == User.Id)
.Count(x => x.Image.Id == image.Id);
if(serversCount < limit.Amount)
Images.Add(image, limit);
}
}
}
private async Task OnValidSubmit()
{
var limit = await SubscriptionService.GetLimit("image." + Model.Image.Id);
if (limit.Amount > 0)
{
var serversCount = ServerRepository
.Get()
.Include(x => x.Owner)
.Include(x => x.Image)
.Where(x => x.Owner.Id == User.Id)
.Count(x => x.Image.Id == Model.Image.Id);
if (serversCount < limit.Amount)
{
if(int.TryParse(limit.ReadValue("cpu"), out int cpu) &&
int.TryParse(limit.ReadValue("memory"), out int memory) &&
int.TryParse(limit.ReadValue("disk"), out int disk))
{
var server = await ServerService.Create(
Model.Name,
cpu,
memory,
disk,
User,
Model.Image,
DeployNode
);
NavigationManager.NavigateTo($"/server/{server.Uuid}");
}
else
{
throw new DisplayException("Limits cannot be parsed");
}
}
}
}
}

View File

@@ -475,3 +475,19 @@ No node found;No node found
No node found to deploy to found;No node found to deploy to found
You need to specify a server image;You need to specify a server image
CPU;CPU
Compress;Compress
Decompress;Decompress
Cleanup exception;Cleanup exception
Error creating server on wings;Error creating server on wings
Enter code;Enter code
Create code;Create code
Duration;Duration
Enter duration of subscription;Enter duration of subscription
Copied code to clipboard;Copied code to clipboard
Current subscription;Current subscription
7 + 7 =;7 + 7 =
1 + 8 =;1 + 8 =
8 + 4 =;8 + 4 =
8 + 1 =;8 + 1 =
1 + 1 =;1 + 1 =
6 + 6 =;6 + 6 =

View File

@@ -0,0 +1,38 @@
<svg xmlns="http://www.w3.org/2000/svg" width="733.82" height="503.768" viewBox="0 0 733.82 503.768" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Group_16" data-name="Group 16" transform="translate(-196.555 -165.086)">
<path id="Path_204-85" data-name="Path 204" d="M261.846,378.459a45.126,45.126,0,1,1,0-90.252H672.56a45.126,45.126,0,1,1,0,90.252Z" transform="translate(-20.165 -123.12)" fill="#e6e6e6"/>
<path id="Path_205-86" data-name="Path 205" d="M264.96,297.207a39.24,39.24,0,0,0,0,78.48H675.674a39.24,39.24,0,1,0,0-78.48Z" transform="translate(-23.279 -126.234)" fill="#fff"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="1.308" height="78.48" transform="translate(364.961 170.972)" fill="#e6e6e6"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="1.308" height="78.48" transform="translate(527.808 170.972)" fill="#e6e6e6"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="161.539" height="78.48" transform="translate(366.269 170.972)" fill="#6c63ff"/>
<path id="Path_198-87" data-name="Path 198" d="M276.268,206.815a24,24,0,1,0,24,24,24,24,0,0,0-24-24Zm0,7.2a7.2,7.2,0,1,1-7.2,7.2A7.2,7.2,0,0,1,276.268,214.016Zm0,34.662a17.484,17.484,0,0,1-14.4-7.685c.115-4.8,9.6-7.442,14.4-7.442s14.285,2.642,14.4,7.442a17.513,17.513,0,0,1-14.4,7.685Z" transform="translate(170.844 -20.53)" fill="#fff"/>
<path id="Path_200-88" data-name="Path 200" d="M620.7,206.815a24,24,0,1,0,24,24,24,24,0,0,0-24-24Zm0,7.2a7.2,7.2,0,1,1-7.2,7.2A7.2,7.2,0,0,1,620.7,214.015Zm0,34.662a17.484,17.484,0,0,1-14.4-7.685c.115-4.8,9.6-7.442,14.4-7.442s14.285,2.642,14.4,7.442a17.513,17.513,0,0,1-14.4,7.685Z" transform="translate(-336.439 -20.53)" fill="#e6e6e6"/>
<path id="Path_242-89" data-name="Path 242" d="M620.7,206.815a24,24,0,1,0,24,24,24,24,0,0,0-24-24Zm0,7.2a7.2,7.2,0,1,1-7.2,7.2A7.2,7.2,0,0,1,620.7,214.015Zm0,34.662a17.484,17.484,0,0,1-14.4-7.685c.115-4.8,9.6-7.442,14.4-7.442s14.285,2.642,14.4,7.442a17.513,17.513,0,0,1-14.4,7.685Z" transform="translate(-10.892 -20.53)" fill="#e6e6e6"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="56" height="56" rx="6" transform="translate(419.112 288.229)" fill="#6c63ff"/>
<ellipse id="Ellipse_29" data-name="Ellipse 29" cx="134.439" cy="18" rx="134.439" ry="18" transform="translate(661.497 632.854)" fill="#e6e6e6"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="56" height="56" rx="6" transform="translate(581.812 288.049)" fill="#e6e6e6"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="40.798" height="40.798" transform="translate(589.812 295.83)" fill="#fff"/>
<path id="Path_202-90" data-name="Path 202" d="M253.345,218.766l-7.075-9.1,4.114-3.2,3.35,4.307,11.318-11.946,3.785,3.585Z" transform="translate(191.14 106.158)" fill="#fff"/>
<path id="Path_203-91" data-name="Path 203" d="M425.345,218.766l-7.075-9.1,4.114-3.2,3.35,4.307,11.317-11.946,3.785,3.585Z" transform="translate(182.106 106.158)" fill="#e6e6e6"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="56" height="56" rx="6" transform="translate(256.265 288.049)" fill="#e6e6e6"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="40.798" height="40.798" transform="translate(264.265 295.83)" fill="#fff"/>
<path id="Path_243-92" data-name="Path 243" d="M425.345,218.766l-7.075-9.1,4.114-3.2,3.35,4.307,11.317-11.946,3.785,3.585Z" transform="translate(-143.441 106.158)" fill="#e6e6e6"/>
<g id="Group_15" data-name="Group 15">
<path id="Path_257-93" data-name="Path 257" d="M340.66,397.363H327.48l-6.268-50.837,19.452,0Z" transform="translate(545.904 239.259)" fill="#ffb8b8"/>
<path id="Path_258-94" data-name="Path 258" d="M320.6,387.355h25.418v16H304.6a16,16,0,0,1,16-16Z" transform="translate(543.364 245.5)" fill="#2f2e41"/>
<path id="Path_259-95" data-name="Path 259" d="M223.865,397.363h-13.18l-6.268-50.837,19.452,0Z" transform="translate(528.049 239.259)" fill="#ffb8b8"/>
<path id="Path_260-96" data-name="Path 260" d="M203.81,387.355h25.418v16H187.806a16,16,0,0,1,16-16Z" transform="translate(525.51 245.5)" fill="#2f2e41"/>
<path id="Path_261-97" data-name="Path 261" d="M487.471,249.585V243.82a37.18,37.18,0,0,1,37.18-37.18h0a37.18,37.18,0,0,1,37.18,37.18v5.764a26.8,26.8,0,0,1-26.8,26.8H514.275a26.8,26.8,0,0,1-26.8-26.8Z" transform="translate(308.465 9.946)" fill="#2f2e41"/>
<ellipse id="Ellipse_36" data-name="Ellipse 36" cx="28.316" cy="28.316" rx="28.316" ry="28.316" transform="translate(804.801 231.687)" fill="#ffb8b8"/>
<path id="Path_263-98" data-name="Path 263" d="M386.583,329.1a10.811,10.811,0,0,1,16.463,1.934l24.273-4.591,6.388,14.07-34.37,6A10.869,10.869,0,0,1,386.583,329.1Z" transform="translate(292.514 28.216)" fill="#ffb8b8"/>
<path id="Path_264-99" data-name="Path 264" d="M515.087,284.516l.317.481-39.8,26.221-67.164,21.447a4.044,4.044,0,0,0-2.781,4.31l1.465,12.62a4.036,4.036,0,0,0,4.854,3.48l63.212-13.549a22.833,22.833,0,0,0,8.5-3.742L528.4,303.969A11.5,11.5,0,0,0,515.4,285Z" transform="translate(295.954 21.634)" fill="#ccc"/>
<path id="Path_265-100" data-name="Path 265" d="M574.076,590.876a5.209,5.209,0,0,1-4.771-3.115l-60.421-149.3a1.729,1.729,0,0,0-3.238.182L456.351,583.993a5.189,5.189,0,0,1-6.781,3.333l-16.53-6.2a5.175,5.175,0,0,1-3.34-4.271c-7.437-64.782,57.413-228.3,58.069-229.946l.182-.455,59.116,13.077.123.134c23.585,25.73,42.971,188.012,46.618,220.283a5.163,5.163,0,0,1-3.425,5.472l-14.591,5.16a5.139,5.139,0,0,1-1.716.295Z" transform="translate(299.543 31.32)" fill="#2f2e41"/>
<path id="Path_266-101" data-name="Path 266" d="M515.547,375.9c-14.323,0-30.291-2.856-35.206-14.642l-.113-.271.153-.251c3.88-6.366,9.007-17.224,6.251-19.263-5.429-4.014-8.064-10.618-7.83-19.628.508-19.559,13.835-36.925,33.163-43.212h0a147.146,147.146,0,0,1,16.443-4.234,27.993,27.993,0,0,1,23.21,5.732,28.276,28.276,0,0,1,10.486,21.755c.2,20.9-3.015,50.015-19.5,70a5.128,5.128,0,0,1-3.036,1.765A140.9,140.9,0,0,1,515.547,375.9Z" transform="translate(307.138 20.219)" fill="#ccc"/>
<path id="Path_267-102" data-name="Path 267" d="M506.106,364.845a11.017,11.017,0,0,1,13.464-7.683,10.843,10.843,0,0,1,1.669.618l18.43-16.773,12.818,8.635L526.13,372.966a11,11,0,0,1-12.466,5.288,10.83,10.83,0,0,1-7.558-13.409Z" transform="translate(311.251 30.487)" fill="#ffb8b8"/>
<path id="Path_268-103" data-name="Path 268" d="M534.283,373.874A5.174,5.174,0,0,1,531,372.7l-7.268-5.939a5.188,5.188,0,0,1,.126-8.134l30.484-23.38a1.733,1.733,0,0,0,.327-2.415l-18.815-24.875a15.316,15.316,0,0,1,1.023-19.731h0a15.273,15.273,0,0,1,20.622-1.649l.119.126,19.647,28.133a17.515,17.515,0,0,1-.415,27.883l-39.481,30.134a5.2,5.2,0,0,1-3.088,1.017Z" transform="translate(313.718 21.67)" fill="#ccc"/>
<path id="Path_269-104" data-name="Path 269" d="M497.965,240.705V226.656L523.047,215.7l23.916,10.952v14.049a2.306,2.306,0,0,1-2.306,2.306H500.271a2.306,2.306,0,0,1-2.306-2.306Z" transform="translate(310.07 11.332)" fill="#2f2e41"/>
<circle id="Ellipse_30" data-name="Ellipse 30" cx="15.722" cy="15.722" r="15.722" transform="translate(838.852 199.377)" fill="#2f2e41"/>
<path id="Path_185-105" data-name="Path 185" d="M896.5,218.806a15.715,15.715,0,0,1,18.8-15.417,15.715,15.715,0,1,0-9.764,29.629,15.709,15.709,0,0,1-9.032-14.212Z" transform="translate(-56.438 -12.141)" fill="#2f2e41"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB