Init
This commit is contained in:
parent
9182a1051c
commit
f537d097ec
49
drinkMe.sln
Normal file
49
drinkMe.sln
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31129.286
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "drinkMe.Server", "drinkMe\Server\drinkMe.Server.csproj", "{4347ED6F-C6EC-44D2-A718-68490CBFFDF0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "drinkMe.Client", "drinkMe\Client\drinkMe.Client.csproj", "{33E45585-7F5D-428D-8A27-FAB29A1A2E61}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "drinkMe.Shared", "drinkMe\Shared\drinkMe.Shared.csproj", "{85CCE095-22AC-434E-B77C-75998FF8DAD3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "drinkMe.Client.Models", "drinkMe\drinkMe.Client.Models\drinkMe.Client.Models.csproj", "{7C00959D-3C7F-4617-B1DA-7909E5879C08}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "drinkMe.Server.Models", "drinkMe\drinkMe.Server.Models\drinkMe.Server.Models.csproj", "{EC73DC9D-FD40-4505-989D-83B49018401D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4347ED6F-C6EC-44D2-A718-68490CBFFDF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4347ED6F-C6EC-44D2-A718-68490CBFFDF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4347ED6F-C6EC-44D2-A718-68490CBFFDF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4347ED6F-C6EC-44D2-A718-68490CBFFDF0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{33E45585-7F5D-428D-8A27-FAB29A1A2E61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{33E45585-7F5D-428D-8A27-FAB29A1A2E61}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{33E45585-7F5D-428D-8A27-FAB29A1A2E61}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{33E45585-7F5D-428D-8A27-FAB29A1A2E61}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{85CCE095-22AC-434E-B77C-75998FF8DAD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{85CCE095-22AC-434E-B77C-75998FF8DAD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{85CCE095-22AC-434E-B77C-75998FF8DAD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{85CCE095-22AC-434E-B77C-75998FF8DAD3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7C00959D-3C7F-4617-B1DA-7909E5879C08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7C00959D-3C7F-4617-B1DA-7909E5879C08}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C00959D-3C7F-4617-B1DA-7909E5879C08}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7C00959D-3C7F-4617-B1DA-7909E5879C08}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EC73DC9D-FD40-4505-989D-83B49018401D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EC73DC9D-FD40-4505-989D-83B49018401D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EC73DC9D-FD40-4505-989D-83B49018401D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EC73DC9D-FD40-4505-989D-83B49018401D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {AB58BAA9-F7FC-4BF0-BC33-174BAB7F8320}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
10
drinkMe/Client/App.razor
Normal file
10
drinkMe/Client/App.razor
Normal file
@ -0,0 +1,10 @@
|
||||
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
35
drinkMe/Client/Components/PurchaseSteps.razor
Normal file
35
drinkMe/Client/Components/PurchaseSteps.razor
Normal file
@ -0,0 +1,35 @@
|
||||
<section class="section has-content-centered">
|
||||
<ul class="steps is-centered has-content-centered is-medium">
|
||||
<li class="steps-segment @VUtilities.IfTrueThen(PurchaseStep == PurchaseStep.ChoosingProducts, ActiveClass, InactiveClass)">
|
||||
<span class="steps-marker is-primary">
|
||||
<i class="fad fa-cart-plus"></i>
|
||||
</span>
|
||||
<div class="steps-content">
|
||||
<p class="is-size-4">Choose drinks</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="steps-segment @VUtilities.IfTrueThen(PurchaseStep == PurchaseStep.PaymentSetup, ActiveClass, InactiveClass)">
|
||||
<span class="steps-marker @VUtilities.IfTrueThen(PurchaseStep is PurchaseStep.PaymentSetup or PurchaseStep.SuccessfulPurchase, PrimaryClass)">
|
||||
<i class="fad fa-money-bill-wave"></i>
|
||||
</span>
|
||||
<div class="steps-content">
|
||||
<p class="is-size-4">Payment</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="steps-segment @VUtilities.IfTrueThen(PurchaseStep == PurchaseStep.SuccessfulPurchase, ActiveClass, InactiveClass)">
|
||||
<span class="steps-marker @VUtilities.IfTrueThen(PurchaseStep == PurchaseStep.SuccessfulPurchase, PrimaryClass)">
|
||||
<i class="fad fa-check-double"></i>
|
||||
</span>
|
||||
<div class="steps-content">
|
||||
<p class="is-size-4">Done!</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[Parameter] public PurchaseStep PurchaseStep { get; set; }
|
||||
const string ActiveClass = "is-active has-gaps";
|
||||
const string InactiveClass = "is-primary has-gaps";
|
||||
const string PrimaryClass = "is-primary";
|
||||
}
|
13
drinkMe/Client/Helpers/VUtilities.cs
Normal file
13
drinkMe/Client/Helpers/VUtilities.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace drinkMe.Client.Helpers
|
||||
{
|
||||
public static class VUtilities
|
||||
{
|
||||
public static T IfTrueThen<T>(bool conditionIsTrue, T ifTrue, T ifFalse = default)
|
||||
{
|
||||
if (conditionIsTrue)
|
||||
return ifTrue;
|
||||
else
|
||||
return ifFalse;
|
||||
}
|
||||
}
|
||||
}
|
23
drinkMe/Client/Pages/Done.razor
Normal file
23
drinkMe/Client/Pages/Done.razor
Normal file
@ -0,0 +1,23 @@
|
||||
@page "/done"
|
||||
|
||||
<PurchaseSteps PurchaseStep="PurchaseStep.SuccessfulPurchase" />
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
|
||||
<div class="columns">
|
||||
|
||||
<div class="column">
|
||||
<div class="box">
|
||||
<h1 class="title has-text-centered">
|
||||
<span>🎉</span>
|
||||
<span>Purchase completed with success!</span>
|
||||
<span>🎉</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
163
drinkMe/Client/Pages/Index.razor
Normal file
163
drinkMe/Client/Pages/Index.razor
Normal file
@ -0,0 +1,163 @@
|
||||
@page "/"
|
||||
|
||||
<PurchaseSteps PurchaseStep="PurchaseStep.ChoosingProducts" />
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
|
||||
<div class="columns">
|
||||
|
||||
<div class="column is-half">
|
||||
<div class="columns is-multiline">
|
||||
|
||||
@foreach (var drink in AvailableDrinks)
|
||||
{
|
||||
<div @key="drink.Id" class="column is-full">
|
||||
<div class="box">
|
||||
<article class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-64x64">
|
||||
<img src="images/drinks/@drink.PictureName" alt="drink image">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<p>
|
||||
<strong>@drink.Name</strong>
|
||||
<br>
|
||||
<span>@drink.Description</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<strong>@drink.Price €</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right mt-2-mobile">
|
||||
<div class="level-item has-text-centered">
|
||||
<button class="button is-small is-primary has-icons-left"
|
||||
disabled="@drink.IsInTheCart" @onclick="async () => await AddToCart(drink)">
|
||||
<span class="icon"><i class="fad fa-cart-arrow-down"></i></span>
|
||||
<span>Add to cart</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-half">
|
||||
|
||||
<div class="box">
|
||||
@if (CartDrinks.Count == 0)
|
||||
{
|
||||
<h1 class="has-text-centered">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fad fa-shopping-cart"></i>
|
||||
</span>
|
||||
<span>Empty cart</span>
|
||||
</span>
|
||||
</h1>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="drinkCartList">
|
||||
@foreach (var cartDrink in CartDrinks)
|
||||
{
|
||||
<div class="drinkCartItem">
|
||||
|
||||
<div class="drinkCartName"><strong>@cartDrink.Name</strong></div>
|
||||
<div class="drinkCartQuantity">
|
||||
<div class="field has-addons">
|
||||
<p class="control">
|
||||
<button class="button is-small is-danger" @onclick="async () => await SubtractQuantity(cartDrink)">
|
||||
<span class="icon"><i class="fad fa-minus"></i></span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="control">
|
||||
<input class="input is-small has-text-centered" type="text" readonly @bind-value="cartDrink.Quantity" />
|
||||
</p>
|
||||
<p class="control">
|
||||
<button class="button is-small is-success" @onclick="async () => await AddQuantity(cartDrink)">
|
||||
<span class="icon"><i class="fad fa-plus"></i></span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="drinkCartCross has-text-centered">
|
||||
<span class="icon">
|
||||
<i class="fad fa-times"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="drinkCartPrice has-text-centered">@cartDrink.Price €</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<hr />
|
||||
<EditForm Model="DiscountForm" OnValidSubmit="TryAddDiscount">
|
||||
<DataAnnotationsValidator />
|
||||
<label class="label" for="discount">
|
||||
<span>Any discount?</span>
|
||||
</label>
|
||||
<div class="field has-addons">
|
||||
<p class="control is-expanded has-icons-left">
|
||||
<span class="icon is-left"><i class="fad fa-tags"></i></span>
|
||||
<input class="input" type="text" id="discount" placeholder="discount code" @onchange="OnChangeDiscountCode">
|
||||
</p>
|
||||
<p class="control">
|
||||
<button type="submit" class="button is-primary has-icons-left">
|
||||
<span class="icon is-left">
|
||||
<i class="fad fa-badge-dollar"></i>
|
||||
</span>
|
||||
<span>Add discount</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
<p class="help is-danger">
|
||||
<ValidationMessage For="() => DiscountForm.DiscountCode" />
|
||||
@InvalidDiscountCodeError
|
||||
</p>
|
||||
<ul>
|
||||
@foreach (var discountCode in DiscountCodes)
|
||||
{
|
||||
<li class="has-text-right">
|
||||
<span class="tag is-dark">@discountCode.Code</span>
|
||||
<span class="tag is-success">-@discountCode.DiscountPercentage %</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</EditForm>
|
||||
<hr />
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<h2><strong>Totale price</strong></h2>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<h2>
|
||||
<strong>@(Math.Round(TotalPrice, 2)) €</strong>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="buttons is-right">
|
||||
<NavLink href="payment" class="button is-primary has-icons-left">
|
||||
<span class="icon"><i class="fad fa-money-bill-wave"></i></span>
|
||||
<span>Go to payment</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
106
drinkMe/Client/Pages/Index.razor.cs
Normal file
106
drinkMe/Client/Pages/Index.razor.cs
Normal file
@ -0,0 +1,106 @@
|
||||
using Blazored.LocalStorage;
|
||||
using drinkMe.Client.Models;
|
||||
using drinkMe.Client.Services.Interfaces;
|
||||
using drinkMe.Shared;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Client.Pages
|
||||
{
|
||||
public partial class Index : ComponentBase
|
||||
{
|
||||
[Inject] ILocalStorageService LocalStorage { get; set; }
|
||||
[Inject] IPriceService PriceService { get; set; }
|
||||
[Inject] IDataService DataService { get; set; }
|
||||
protected List<DrinkViewModel> AvailableDrinks { get; set; } = new List<DrinkViewModel>();
|
||||
protected List<DiscountCodeViewModel> DiscountCodes { get; set; } = new List<DiscountCodeViewModel>();
|
||||
protected List<DrinkViewModel> CartDrinks { get; set; } = new List<DrinkViewModel>();
|
||||
protected string GetDrinksError { get; set; }
|
||||
protected string InvalidDiscountCodeError { get; set; }
|
||||
protected DiscountForm DiscountForm { get; set; } = new DiscountForm();
|
||||
protected float TotalPrice { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var getDrinksResult = await DataService.GetDrinks();
|
||||
if (!getDrinksResult.IsValid)
|
||||
{
|
||||
GetDrinksError = getDrinksResult.ErrorMessage;
|
||||
return;
|
||||
}
|
||||
AvailableDrinks = (List<DrinkViewModel>)getDrinksResult.Data;
|
||||
DiscountCodes = await LocalStorage.GetItemAsync<List<DiscountCodeViewModel>>(nameof(DiscountCodes)) ?? new List<DiscountCodeViewModel>();
|
||||
CartDrinks = await LocalStorage.GetItemAsync<List<DrinkViewModel>>(nameof(CartDrinks)) ?? new List<DrinkViewModel>();
|
||||
if (CartDrinks.Count > 0)
|
||||
{
|
||||
TotalPrice = PriceService.GetTotalPrice(DiscountCodes, CartDrinks);
|
||||
AvailableDrinks.ForEach(ad => ad.IsInTheCart = CartDrinks.Any(cd => cd.Id == ad.Id));
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task AddQuantity(DrinkViewModel drink)
|
||||
{
|
||||
drink.Quantity = drink.Quantity is > 0 and < 20 ? ++drink.Quantity : drink.Quantity;
|
||||
await LocalStorage.SetItemAsync(nameof(CartDrinks), CartDrinks);
|
||||
TotalPrice = PriceService.GetTotalPrice(DiscountCodes, CartDrinks);
|
||||
}
|
||||
|
||||
protected async Task SubtractQuantity(DrinkViewModel drink)
|
||||
{
|
||||
if (drink.Quantity - 1 == 0)
|
||||
{
|
||||
var availableDrink = AvailableDrinks.First(ad => ad.Id == drink.Id);
|
||||
availableDrink.IsInTheCart = false;
|
||||
CartDrinks.Remove(drink);
|
||||
await LocalStorage.SetItemAsync(nameof(CartDrinks), CartDrinks);
|
||||
return;
|
||||
}
|
||||
|
||||
drink.Quantity = drink.Quantity is > 1 and < 21 ? --drink.Quantity : drink.Quantity;
|
||||
await LocalStorage.SetItemAsync(nameof(CartDrinks), CartDrinks);
|
||||
TotalPrice = PriceService.GetTotalPrice(DiscountCodes, CartDrinks);
|
||||
}
|
||||
|
||||
protected async Task AddToCart(DrinkViewModel drink)
|
||||
{
|
||||
drink.IsInTheCart = true;
|
||||
CartDrinks.Add(drink);
|
||||
await LocalStorage.SetItemAsync(nameof(CartDrinks), CartDrinks);
|
||||
TotalPrice = PriceService.GetTotalPrice(DiscountCodes, CartDrinks);
|
||||
}
|
||||
|
||||
protected async Task TryAddDiscount()
|
||||
{
|
||||
InvalidDiscountCodeError = default;
|
||||
|
||||
if (DiscountCodes.Any(dc => dc.Code == DiscountForm.DiscountCode))
|
||||
{
|
||||
InvalidDiscountCodeError = "Discount code already added.";
|
||||
return;
|
||||
}
|
||||
|
||||
var validationResult = await DataService.IsValidDiscountCode(DiscountForm.DiscountCode);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
InvalidDiscountCodeError = validationResult.ErrorMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
var discount = validationResult.Data as DiscountCodeViewModel;
|
||||
|
||||
DiscountCodes.Add(discount);
|
||||
await LocalStorage.SetItemAsync(nameof(DiscountCodes), DiscountCodes);
|
||||
TotalPrice = PriceService.GetTotalPrice(DiscountCodes, CartDrinks);
|
||||
}
|
||||
|
||||
protected void OnChangeDiscountCode(ChangeEventArgs changeEvent)
|
||||
{
|
||||
InvalidDiscountCodeError = default;
|
||||
DiscountForm.DiscountCode = (string)changeEvent.Value;
|
||||
}
|
||||
}
|
||||
}
|
172
drinkMe/Client/Pages/Payment.razor
Normal file
172
drinkMe/Client/Pages/Payment.razor
Normal file
@ -0,0 +1,172 @@
|
||||
@page "/payment"
|
||||
|
||||
<PurchaseSteps PurchaseStep="PurchaseStep.PaymentSetup" />
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
|
||||
<div class="columns">
|
||||
|
||||
<div class="column is-half">
|
||||
|
||||
<div class="box">
|
||||
@if (CartDrinks.Count == 0)
|
||||
{
|
||||
<h1 class="has-text-centered">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fad fa-shopping-cart"></i>
|
||||
</span>
|
||||
<span>Empty cart</span>
|
||||
</span>
|
||||
</h1>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="drinkCartList">
|
||||
@foreach (var cartDrink in CartDrinks)
|
||||
{
|
||||
<div class="drinkCartPItem">
|
||||
|
||||
<div class="drinkCartName"><strong>@cartDrink.Name</strong></div>
|
||||
<div class="drinkCartQuantity has-text-centered">
|
||||
<div class="field">
|
||||
<p class="control">
|
||||
@cartDrink.Quantity
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="drinkCartCross has-text-centered">
|
||||
<span class="icon">
|
||||
<i class="fad fa-times"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="drinkCartPrice has-text-centered">@cartDrink.Price €</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
if (DiscountCodes.Count > 0)
|
||||
{
|
||||
<hr />
|
||||
<ul>
|
||||
@foreach (var discountCode in DiscountCodes)
|
||||
{
|
||||
<li class="has-text-right">
|
||||
<span class="tag is-dark">@discountCode.Code</span>
|
||||
<span class="tag is-success">-@discountCode.DiscountPercentage %</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
<hr />
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<h2><strong>Totale price</strong></h2>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<h2>
|
||||
<strong>@(Math.Round(TotalPrice, 2)) €</strong>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="column is-half">
|
||||
<div class="box">
|
||||
<EditForm Model="CreditCardForm" OnValidSubmit="Pay">
|
||||
<DataAnnotationsValidator />
|
||||
<InputRadioGroup Name="PaymentMethod" @bind-Value="PaymentMethod">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="radio">
|
||||
<InputRadio Name="PaymentMethod" Value="PaymentMethod.Cash" disabled="@(TotalPrice > 10f)" />
|
||||
<span class="icon-text"><span class="icon"><i class="fad fa-money-bill-wave"></i></span> <span>I'm going with cash</span></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-divider" data-content="OR"></div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="radio">
|
||||
<InputRadio Name="PaymentMethod" Value="PaymentMethod.CreditCard" />
|
||||
<span class="icon-text"><span class="icon"><i class="fad fa-credit-card-front"></i></span> <span>Here goes the card</span></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</InputRadioGroup>
|
||||
<fieldset class="@VUtilities.IfTrueThen(PaymentMethod == PaymentMethod.Cash, "is-hidden")" disabled="@(PaymentMethod != PaymentMethod.CreditCard)">
|
||||
<div class="field">
|
||||
<label class="label">Credit card number</label>
|
||||
<div class="control has-icons-left">
|
||||
<span class="icon is-left"><i class="fad fa-credit-card-front"></i></span>
|
||||
<input @bind-value="CreditCardForm.Number" class="input" placeholder="">
|
||||
</div>
|
||||
<p class="help is-danger">
|
||||
<ValidationMessage For="() => CreditCardForm.Number" />
|
||||
</p>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column is-half">
|
||||
<label class="label">Expiration month / year</label>
|
||||
<div class="field has-addons has-addons-right">
|
||||
<div class="control">
|
||||
<input @bind-value="CreditCardForm.ExpirationMonth" class="input has-text-centered" placeholder="01">
|
||||
</div>
|
||||
<div class="control">
|
||||
<input class="input has-text-centered" value="/" readonly disabled>
|
||||
</div>
|
||||
<div class="control">
|
||||
<input @bind-value="CreditCardForm.ExpirationYear" class="input has-text-centered" placeholder="@((DateTime.Now.Year + 1).ToString().Substring(2,2))">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help is-danger">
|
||||
<ValidationMessage For="() => CreditCardForm.ExpirationMonth" />
|
||||
<ValidationMessage For="() => CreditCardForm.ExpirationYear" />
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-half">
|
||||
<div class="field">
|
||||
<label class="label">CVV code</label>
|
||||
<div class="control has-icons-left">
|
||||
<span class="icon is-left"><i class="fad fa-key"></i></span>
|
||||
<input type="text" @bind-value="CreditCardForm.CVVCode" class="input has-text-centered" placeholder="***" />
|
||||
</div>
|
||||
<p class="help is-danger">
|
||||
<ValidationMessage For="() => CreditCardForm.CVVCode" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (PaymentMethod == PaymentMethod.CreditCard)
|
||||
{
|
||||
<div class="field has-text-right">
|
||||
<button type="submit" class="button is-primary has-icons-left">
|
||||
<span class="icon"><i class="fad fa-credit-card-blank"></i></span>
|
||||
<span>Pay</span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</fieldset>
|
||||
|
||||
</EditForm>
|
||||
|
||||
@if (PaymentMethod == PaymentMethod.Cash)
|
||||
{
|
||||
<div class="field mt-5 has-text-right">
|
||||
<button class="button is-primary has-icons-left" @onclick="Pay">
|
||||
<span class="icon"><i class="fad fa-money-bill-wave"></i></span>
|
||||
<span>Pay at checkout</span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
69
drinkMe/Client/Pages/Payment.razor.cs
Normal file
69
drinkMe/Client/Pages/Payment.razor.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using Blazored.LocalStorage;
|
||||
using drinkMe.Client.Models;
|
||||
using drinkMe.Client.Services.Interfaces;
|
||||
using drinkMe.Shared;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Client.Pages
|
||||
{
|
||||
public partial class Payment : ComponentBase
|
||||
{
|
||||
[Inject] ILocalStorageService LocalStorage { get; set; }
|
||||
[Inject] IPriceService PriceService { get; set; }
|
||||
[Inject] IPaymentService PaymentService { get; set; }
|
||||
[Inject] NavigationManager Navigation { get; set; }
|
||||
protected List<DrinkViewModel> CartDrinks { get; set; } = new List<DrinkViewModel>();
|
||||
protected List<DiscountCodeViewModel> DiscountCodes { get; set; } = new List<DiscountCodeViewModel>();
|
||||
protected CreditCardForm CreditCardForm { get; set; } = new CreditCardForm();
|
||||
protected PaymentMethod PaymentMethod { get; set; } = PaymentMethod.Undefined;
|
||||
protected float TotalPrice { get; set; }
|
||||
protected string PaymentError { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
CartDrinks = await LocalStorage.GetItemAsync<List<DrinkViewModel>>(nameof(CartDrinks));
|
||||
DiscountCodes = await LocalStorage.GetItemAsync<List<DiscountCodeViewModel>>(nameof(DiscountCodes)) ?? new List<DiscountCodeViewModel>();
|
||||
|
||||
TotalPrice = PriceService.GetTotalPrice(DiscountCodes, CartDrinks);
|
||||
if (TotalPrice > 10f)
|
||||
PaymentMethod = PaymentMethod.CreditCard;
|
||||
}
|
||||
|
||||
protected async Task Pay()
|
||||
{
|
||||
PaymentError = default;
|
||||
var paymentCart = new PurchaseCart();
|
||||
paymentCart.Discounts = DiscountCodes;
|
||||
paymentCart.PurchasingItems = CartDrinks.Select(cd => new CartItem
|
||||
{
|
||||
Id = cd.Id,
|
||||
Quantity = cd.Quantity
|
||||
}).ToList();
|
||||
paymentCart.IsPayedWithCash = PaymentMethod == PaymentMethod.Cash;
|
||||
if (!paymentCart.IsPayedWithCash)
|
||||
{
|
||||
paymentCart.CreditCardNumber = CreditCardForm.Number;
|
||||
paymentCart.CreditCardExpirationMonth = CreditCardForm.ExpirationMonth;
|
||||
paymentCart.CreditCardExpirationYear = CreditCardForm.ExpirationYear;
|
||||
paymentCart.CreditCardCVVCode = CreditCardForm.CVVCode;
|
||||
}
|
||||
var paymentResult = await PaymentService.Pay(paymentCart);
|
||||
|
||||
if (!paymentResult.IsValid)
|
||||
{
|
||||
PaymentError = paymentResult.ErrorMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
await LocalStorage.RemoveItemAsync(nameof(CartDrinks));
|
||||
await LocalStorage.RemoveItemAsync(nameof(DiscountCodes));
|
||||
|
||||
Navigation.NavigateTo("done");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
39
drinkMe/Client/Program.cs
Normal file
39
drinkMe/Client/Program.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using drinkMe.Client.Services;
|
||||
using drinkMe.Client.Services.Interfaces;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Blazored.LocalStorage;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace drinkMe.Client
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) })
|
||||
.AddScoped<IDataService, DataService>()
|
||||
.AddScoped<IPriceService, PriceService>()
|
||||
.AddScoped<IPaymentService, PaymentService>();
|
||||
|
||||
builder.Services.AddBlazoredLocalStorage(config =>
|
||||
{
|
||||
config.JsonSerializerOptions.WriteIndented = false;
|
||||
config.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||
});
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
}
|
||||
}
|
||||
}
|
30
drinkMe/Client/Properties/launchSettings.json
Normal file
30
drinkMe/Client/Properties/launchSettings.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:14962",
|
||||
"sslPort": 44321
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"drinkMe": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": "true",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
drinkMe/Client/SCSS/all.scss
Normal file
6
drinkMe/Client/SCSS/all.scss
Normal file
@ -0,0 +1,6 @@
|
||||
@import "variables.scss";
|
||||
@import "mixins.scss";
|
||||
@import "framework-override.scss";
|
||||
@import "neomorph.scss";
|
||||
@import "base.scss";
|
||||
@import "chooseDrinks.scss";
|
41
drinkMe/Client/SCSS/base.scss
Normal file
41
drinkMe/Client/SCSS/base.scss
Normal file
@ -0,0 +1,41 @@
|
||||
html {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
*, ::after, ::before {
|
||||
scrollbar-width: inherit;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button {
|
||||
width: 8px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border: thin solid transparent;
|
||||
box-shadow: none;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border: thin solid transparent;
|
||||
border-radius: 10px;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
body {
|
||||
background-image: url("/images/background.jpg");
|
||||
background-size: cover;
|
||||
min-height: 100vh
|
||||
}
|
||||
|
||||
.loading {
|
||||
height: 100vh;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
55
drinkMe/Client/SCSS/chooseDrinks.scss
Normal file
55
drinkMe/Client/SCSS/chooseDrinks.scss
Normal file
@ -0,0 +1,55 @@
|
||||
.drinkCart {
|
||||
|
||||
&List {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&Item {
|
||||
display: grid;
|
||||
grid-template-areas: "drinkName drinkQuantity drinkCross drinkPrice";
|
||||
grid-template-columns: 1fr minmax(100px, 20%) 5% 10%;
|
||||
grid-template-rows: 100%;
|
||||
grid-gap: 5px;
|
||||
align-items: center;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&PItem {
|
||||
display: grid;
|
||||
grid-template-areas: "drinkName drinkQuantity drinkCross drinkPrice";
|
||||
grid-template-columns: 1fr 10% 5% 10%;
|
||||
grid-template-rows: 100%;
|
||||
grid-gap: 5px;
|
||||
align-items: center;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&Name {
|
||||
grid-area: drinkName;
|
||||
}
|
||||
|
||||
|
||||
&Cross {
|
||||
grid-area: drinkCross;
|
||||
}
|
||||
|
||||
&Quantity {
|
||||
grid-area: drinkQuantity;
|
||||
}
|
||||
|
||||
&Price {
|
||||
grid-area: drinkPrice;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.column>.box>.title{
|
||||
padding: 10%;
|
||||
}
|
16
drinkMe/Client/SCSS/framework-override.scss
Normal file
16
drinkMe/Client/SCSS/framework-override.scss
Normal file
@ -0,0 +1,16 @@
|
||||
html {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.steps {
|
||||
list-style-type: none !important;
|
||||
}
|
||||
|
||||
@include MediaQuery(phone) {
|
||||
.mt-2-mobile {
|
||||
margin-top: .5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.control.has-icons-left .icon, .control.has-icons-right .icon {
|
||||
z-index: 5;
|
||||
}
|
47
drinkMe/Client/SCSS/mixins.scss
Normal file
47
drinkMe/Client/SCSS/mixins.scss
Normal file
@ -0,0 +1,47 @@
|
||||
//MEDIA QUERY MANAGER
|
||||
|
||||
/*$breakpoint argument choices:
|
||||
- phone
|
||||
- tab-port
|
||||
- tab-land
|
||||
- desk
|
||||
- big-desktop
|
||||
*/
|
||||
|
||||
@mixin MediaQuery($breakpoint) {
|
||||
@if $breakpoint==phone {
|
||||
@media screen and (max-width: 768px) {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
@if $breakpoint==tab-p {
|
||||
@media screen and (min-width: 768px) and (max-width: 900px) {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
@if $breakpoint==tab-l {
|
||||
@media screen and (min-width: 901px) and (max-width: 1200px) {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
@if $breakpoint==laptop {
|
||||
@media screen and (min-width: 1201px) and (max-width: 1366px) {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
@if $breakpoint==desk {
|
||||
@media screen and (min-width: 1201px) and (max-width: 1800px) {
|
||||
@content
|
||||
}
|
||||
}
|
||||
|
||||
@if $breakpoint==big-d {
|
||||
@media screen and (min-width: 1801px) and (max-width: 4000px) {
|
||||
@content
|
||||
}
|
||||
}
|
||||
}
|
155
drinkMe/Client/SCSS/neomorph.scss
Normal file
155
drinkMe/Client/SCSS/neomorph.scss
Normal file
@ -0,0 +1,155 @@
|
||||
.neomorph {
|
||||
box-shadow: -8px -8px 16px var(--light-shadow), 8px 8px 16px var(--dark-shadow);
|
||||
|
||||
&Small {
|
||||
box-shadow: -6px -6px 12px var(--light-shadow), 6px 6px 12px var(--dark-shadow);
|
||||
}
|
||||
|
||||
&XSmall {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
|
||||
}
|
||||
|
||||
&XXSmall {
|
||||
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow);
|
||||
}
|
||||
|
||||
&Bottom {
|
||||
filter: drop-shadow(8px 8px 14px var(--primary-color-dark));
|
||||
}
|
||||
|
||||
&Inset {
|
||||
box-shadow: inset 8px 8px 16px var(--dark-shadow), inset -8px -8px 16px var(--light-shadow);
|
||||
|
||||
&Small {
|
||||
box-shadow: inset 6px 6px 12px var(--dark-shadow), inset -6px -6px 12px var(--light-shadow);
|
||||
}
|
||||
|
||||
&XSmall {
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
|
||||
}
|
||||
|
||||
&XXSmall {
|
||||
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.neo {
|
||||
&Btn {
|
||||
background: linear-gradient(145deg,var(--primary-gradiend-light),var(--primary-gradiend-dark));
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
|
||||
border: none !important;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
|
||||
&:focus {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
&InsetPlain {
|
||||
background: linear-gradient(145deg,var(--primary-gradiend-dark),var(--primary-gradiend-light));
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
|
||||
border: none !important;
|
||||
|
||||
&:focus {
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow) !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&Small {
|
||||
background: linear-gradient(145deg,var(--primary-gradiend-light),var(--primary-gradiend-dark));
|
||||
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow);
|
||||
border: none !important;
|
||||
|
||||
|
||||
&:focus {
|
||||
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow);
|
||||
border: none !important;
|
||||
|
||||
&:not(:active) {
|
||||
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
&InsetPlain {
|
||||
background: linear-gradient(145deg,var(--primary-gradiend-dark),var(--primary-gradiend-light));
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow);
|
||||
border: none !important;
|
||||
|
||||
&:focus {
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow) !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&XInsetPlain {
|
||||
background: linear-gradient(145deg,var(--primary-gradiend-dark),var(--primary-gradiend-light));
|
||||
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow);
|
||||
border: none !important;
|
||||
|
||||
&:focus {
|
||||
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow) !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&Plain {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
|
||||
border: none !important;
|
||||
|
||||
&:focus {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow) !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&File {
|
||||
box-shadow: 0px 0px 0px var(--light-shadow), 0px 0px 0px var(--dark-shadow) !important;
|
||||
transition: all .2s linear;
|
||||
-webkit-backface-visibility: hidden !important;
|
||||
backface-visibility: hidden !important;
|
||||
|
||||
@include MediaQuery(phone) {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow) !important;
|
||||
background: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow) !important;
|
||||
}
|
||||
|
||||
&.isSelected {
|
||||
background: linear-gradient(145deg,var(--primary-gradiend-dark),var(--primary-gradiend-light));
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow),inset -3px -3px 6px var(--light-shadow) !important;
|
||||
}
|
||||
|
||||
&.is-active, &.active {
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow),inset -3px -3px 6px var(--light-shadow) !important;
|
||||
color: var(--black) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&Input {
|
||||
box-shadow: inset 2px 2px 4px var(--dark-shadow),inset -2px -2px 4px var(--light-shadow) !important;
|
||||
background: linear-gradient(145deg,var(--primary-gradiend-dark),var(--primary-gradiend-light));
|
||||
border: none !important;
|
||||
|
||||
&:focus {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&Select > select {
|
||||
box-shadow: inset 2px 2px 4px var(--dark-shadow),inset -2px -2px 4px var(--light-shadow) !important;
|
||||
background: linear-gradient(145deg,var(--primary-gradiend-dark),var(--primary-gradiend-light)) !important;
|
||||
border: none !important;
|
||||
|
||||
&:focus {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
56
drinkMe/Client/SCSS/variables.scss
Normal file
56
drinkMe/Client/SCSS/variables.scss
Normal file
@ -0,0 +1,56 @@
|
||||
$main-pc: hsl(25,84,88); //#fbe9d7;
|
||||
$main-gradient-light: hsl(25,84,92);
|
||||
$main-gradient-dark: hsl(25,84,84);
|
||||
$main-light-pc: lighten($main-pc,15%);
|
||||
$main-dark-pc: darken($main-pc,25%);
|
||||
$main-sc: #f6d5f7;
|
||||
$main-light-sc: lighten($main-sc,15%);
|
||||
$main-dark-sc: darken($main-sc,15%);
|
||||
|
||||
$icon-primary: hsl(0,96,25);
|
||||
$icon-secondary: hsl(0,100,55);
|
||||
|
||||
$text-color: darken($main-pc,70%);
|
||||
|
||||
$black: hsl(0, 0%, 4%) !default;
|
||||
$black-bis: hsl(0, 0%, 7%) !default;
|
||||
$black-ter: hsl(0, 0%, 14%) !default;
|
||||
|
||||
$grey-darker: hsl(0, 0%, 21%) !default;
|
||||
$grey-dark: hsl(0, 0%, 29%) !default;
|
||||
$grey: hsl(0, 0%, 48%) !default;
|
||||
$grey-light: hsl(0, 0%, 71%) !default;
|
||||
$grey-lighter: hsl(0, 0%, 86%) !default;
|
||||
|
||||
$white-ter: hsl(0, 4%, 96%) !default;
|
||||
$white-bis: hsl(0, 4%, 98%) !default;
|
||||
$white: hsl(0, 4%, 99.8%) !default;
|
||||
$danger: hsl(0, 82%, 91%);
|
||||
$warning: hsl(48, 82%, 91%);
|
||||
$info: hsl(196, 82%, 91%);
|
||||
|
||||
:root {
|
||||
--background: $main-pc;
|
||||
--text-color: $text-color;
|
||||
--black: $black;
|
||||
--black-bis: $black-bis;
|
||||
--black-ter: $black-ter;
|
||||
--white: $white;
|
||||
--primary-color: $main-pc;
|
||||
--primary-color-light: $main-light-pc;
|
||||
--primary-color-dark: $main-dark-pc;
|
||||
--primary-gradiend-light: $main-gradient-light;
|
||||
--primary-gradiend-dark: $main-gradient-dark;
|
||||
--secondary-color: $main-sc;
|
||||
--secondary-color-light: $main-light-sc;
|
||||
--secondary-color-dark: $main-dark-sc;
|
||||
--light-shadow: rgba($main-light-pc, .5);
|
||||
--dark-shadow: rgba($main-dark-pc, .5);
|
||||
/* --fa-primary-color: $text-color;
|
||||
--fa-secondary-color: $main-dark-pc;
|
||||
--fa-primary-opacity: 0.80;
|
||||
--fa-secondary-opacity: 0.80;*/
|
||||
--danger-color: $danger;
|
||||
--warning-color: $warning;
|
||||
--info-color: $info;
|
||||
}
|
47
drinkMe/Client/Services/DataService.cs
Normal file
47
drinkMe/Client/Services/DataService.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using collAnon.Pub.Shared;
|
||||
using drinkMe.Client.Services.Interfaces;
|
||||
using drinkMe.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Client.Services
|
||||
{
|
||||
public class DataService : IDataService
|
||||
{
|
||||
readonly HttpClient HttpClient;
|
||||
public DataService(HttpClient httpClient)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<WebResult> IsValidDiscountCode(string code)
|
||||
{
|
||||
var result = new WebResult();
|
||||
var httpResponse = await HttpClient.GetAsync($"api/Drinks/{nameof(IsValidDiscountCode)}?{nameof(code)}={code}");
|
||||
|
||||
if (!httpResponse.IsSuccessStatusCode)
|
||||
return result.Invalidate(JsonSerializer.Deserialize<WebResult>(await httpResponse.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
|
||||
|
||||
result.Data = JsonSerializer.Deserialize<DiscountCodeViewModel>(await httpResponse.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<WebResult> GetDrinks()
|
||||
{
|
||||
var result = new WebResult();
|
||||
var httpResponse = await HttpClient.GetAsync($"api/Drinks/{nameof(GetDrinks)}");
|
||||
|
||||
if (!httpResponse.IsSuccessStatusCode)
|
||||
return result.Invalidate(JsonSerializer.Deserialize<WebResult>(await httpResponse.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
|
||||
|
||||
result.Data = JsonSerializer.Deserialize<List<DrinkViewModel>>(await httpResponse.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
11
drinkMe/Client/Services/Interfaces/IDataService.cs
Normal file
11
drinkMe/Client/Services/Interfaces/IDataService.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using collAnon.Pub.Shared;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Client.Services.Interfaces
|
||||
{
|
||||
public interface IDataService
|
||||
{
|
||||
Task<WebResult> GetDrinks();
|
||||
Task<WebResult> IsValidDiscountCode(string code);
|
||||
}
|
||||
}
|
14
drinkMe/Client/Services/Interfaces/IPaymentService.cs
Normal file
14
drinkMe/Client/Services/Interfaces/IPaymentService.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using collAnon.Pub.Shared;
|
||||
using drinkMe.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Client.Services.Interfaces
|
||||
{
|
||||
public interface IPaymentService
|
||||
{
|
||||
Task<WebResult> Pay(PurchaseCart purchaseCart);
|
||||
}
|
||||
}
|
13
drinkMe/Client/Services/Interfaces/IPriceService.cs
Normal file
13
drinkMe/Client/Services/Interfaces/IPriceService.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using drinkMe.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Client.Services.Interfaces
|
||||
{
|
||||
public interface IPriceService
|
||||
{
|
||||
float GetTotalPrice(List<DiscountCodeViewModel> discountCodes, List<DrinkViewModel> drinks);
|
||||
}
|
||||
}
|
33
drinkMe/Client/Services/PaymentService.cs
Normal file
33
drinkMe/Client/Services/PaymentService.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using collAnon.Pub.Shared;
|
||||
using drinkMe.Client.Services.Interfaces;
|
||||
using drinkMe.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Client.Services
|
||||
{
|
||||
public class PaymentService : IPaymentService
|
||||
{
|
||||
readonly HttpClient HttpClient;
|
||||
public PaymentService(HttpClient httpClient)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<WebResult> Pay(PurchaseCart purchaseCart)
|
||||
{
|
||||
var result = new WebResult();
|
||||
var httpResponse = await HttpClient.PostAsync($"api/Drinks/{nameof(Pay)}", JsonContent.Create(purchaseCart));
|
||||
|
||||
if (!httpResponse.IsSuccessStatusCode)
|
||||
return result.Invalidate(JsonSerializer.Deserialize<WebResult>(await httpResponse.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
34
drinkMe/Client/Services/PriceService.cs
Normal file
34
drinkMe/Client/Services/PriceService.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Blazored.LocalStorage;
|
||||
using drinkMe.Client.Services.Interfaces;
|
||||
using drinkMe.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Client.Services
|
||||
{
|
||||
public class PriceService : IPriceService
|
||||
{
|
||||
public float GetTotalPrice(List<DiscountCodeViewModel> discountCodes, List<DrinkViewModel> cartDrinks)
|
||||
{
|
||||
var total = default(float);
|
||||
|
||||
if (discountCodes.Count == 0)
|
||||
foreach (var cartDrink in cartDrinks)
|
||||
total += cartDrink.Price * cartDrink.Quantity;
|
||||
else
|
||||
foreach (var cartDrink in cartDrinks)
|
||||
if (discountCodes.Any(d => d.ApplicableProducts.Contains(cartDrink.Id)))
|
||||
{
|
||||
var discountPercentage = discountCodes.First(d => d.ApplicableProducts.Contains(cartDrink.Id)).DiscountPercentage / 100;
|
||||
var discountedPricePortion = cartDrink.Price * discountPercentage;
|
||||
total += (cartDrink.Price - discountedPricePortion) * cartDrink.Quantity;
|
||||
}
|
||||
else
|
||||
total += cartDrink.Price * cartDrink.Quantity;
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
}
|
10
drinkMe/Client/Shared/MainLayout.razor
Normal file
10
drinkMe/Client/Shared/MainLayout.razor
Normal file
@ -0,0 +1,10 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<NavMenu />
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
@Body
|
||||
</div>
|
||||
</section>
|
||||
|
37
drinkMe/Client/Shared/NavMenu.razor
Normal file
37
drinkMe/Client/Shared/NavMenu.razor
Normal file
@ -0,0 +1,37 @@
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<NavLink class="navbar-item" href="/">
|
||||
<img src="/images/drinkMe-logo.png" alt="logo">
|
||||
</NavLink>
|
||||
|
||||
<a role="button" class="navbar-burger @IsActive" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample"
|
||||
@onclick="OpenCloseMenu">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="navbarBasicExample" class="navbar-menu @IsActive">
|
||||
<div class="navbar-start">
|
||||
<NavLink class="navbar-item" href="/">
|
||||
<span class="icon-text">
|
||||
<span class="icon"><i class="fad fa-home"></i></span>
|
||||
<span>Home</span>
|
||||
</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@code{
|
||||
string IsActive { get; set; }
|
||||
|
||||
void OpenCloseMenu()
|
||||
{
|
||||
if (string.IsNullOrEmpty(IsActive))
|
||||
IsActive = "is-active";
|
||||
else
|
||||
IsActive = default;
|
||||
}
|
||||
}
|
13
drinkMe/Client/_Imports.razor
Normal file
13
drinkMe/Client/_Imports.razor
Normal file
@ -0,0 +1,13 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.JSInterop
|
||||
@using drinkMe.Client
|
||||
@using drinkMe.Client.Models
|
||||
@using drinkMe.Client.Helpers
|
||||
@using drinkMe.Client.Components
|
||||
@using drinkMe.Client.Shared
|
6
drinkMe/Client/compilerconfig.json
Normal file
6
drinkMe/Client/compilerconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"outputFile": "wwwroot/css/style.css",
|
||||
"inputFile": "SCSS/all.scss"
|
||||
}
|
||||
]
|
63
drinkMe/Client/compilerconfig.json.defaults
Normal file
63
drinkMe/Client/compilerconfig.json.defaults
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"compilers": {
|
||||
"less": {
|
||||
"autoPrefix": "",
|
||||
"cssComb": "none",
|
||||
"ieCompat": true,
|
||||
"strictMath": false,
|
||||
"strictUnits": false,
|
||||
"relativeUrls": true,
|
||||
"rootPath": "",
|
||||
"sourceMapRoot": "",
|
||||
"sourceMapBasePath": "",
|
||||
"sourceMap": false
|
||||
},
|
||||
"sass": {
|
||||
"autoPrefix": "",
|
||||
"includePath": "",
|
||||
"indentType": "space",
|
||||
"indentWidth": 2,
|
||||
"outputStyle": "nested",
|
||||
"Precision": 5,
|
||||
"relativeUrls": true,
|
||||
"sourceMapRoot": "",
|
||||
"lineFeed": "",
|
||||
"sourceMap": false
|
||||
},
|
||||
"stylus": {
|
||||
"sourceMap": false
|
||||
},
|
||||
"babel": {
|
||||
"sourceMap": false
|
||||
},
|
||||
"coffeescript": {
|
||||
"bare": false,
|
||||
"runtimeMode": "node",
|
||||
"sourceMap": false
|
||||
},
|
||||
"handlebars": {
|
||||
"root": "",
|
||||
"noBOM": false,
|
||||
"name": "",
|
||||
"namespace": "",
|
||||
"knownHelpersOnly": false,
|
||||
"forcePartial": false,
|
||||
"knownHelpers": [],
|
||||
"commonjs": "",
|
||||
"amd": false,
|
||||
"sourceMap": false
|
||||
}
|
||||
},
|
||||
"minifiers": {
|
||||
"css": {
|
||||
"enabled": true,
|
||||
"termSemicolons": true,
|
||||
"gzip": false
|
||||
},
|
||||
"javascript": {
|
||||
"enabled": true,
|
||||
"termSemicolons": true,
|
||||
"gzip": false
|
||||
}
|
||||
}
|
||||
}
|
43
drinkMe/Client/drinkMe.Client.csproj
Normal file
43
drinkMe/Client/drinkMe.Client.csproj
Normal file
@ -0,0 +1,43 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="compilerconfig.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\style.css" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="compilerconfig.json" />
|
||||
<None Include="wwwroot\webfonts\fa-duotone-900.svg" />
|
||||
<None Include="wwwroot\webfonts\fa-duotone-900.woff2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="3.0.0" />
|
||||
<PackageReference Include="Blazored.Modal" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.5" PrivateAssets="all" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\drinkMe.Client.Models\drinkMe.Client.Models.csproj" />
|
||||
<ProjectReference Include="..\Shared\drinkMe.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\images\drinks\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
1
drinkMe/Client/wwwroot/css/bulma-divider.min.css
vendored
Normal file
1
drinkMe/Client/wwwroot/css/bulma-divider.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1466
drinkMe/Client/wwwroot/css/bulma-steps.css
Normal file
1466
drinkMe/Client/wwwroot/css/bulma-steps.css
Normal file
File diff suppressed because one or more lines are too long
3
drinkMe/Client/wwwroot/css/bulma-steps.min.css
vendored
Normal file
3
drinkMe/Client/wwwroot/css/bulma-steps.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
11705
drinkMe/Client/wwwroot/css/bulma.css
vendored
Normal file
11705
drinkMe/Client/wwwroot/css/bulma.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
drinkMe/Client/wwwroot/css/bulma.min.css
vendored
Normal file
1
drinkMe/Client/wwwroot/css/bulma.min.css
vendored
Normal file