Init
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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1,10 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<NavMenu />
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
@Body
|
||||
</div>
|
||||
</section>
|
||||
|
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
@ -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
@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"outputFile": "wwwroot/css/style.css",
|
||||
"inputFile": "SCSS/all.scss"
|
||||
}
|
||||
]
|
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
@ -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
1466
drinkMe/Client/wwwroot/css/bulma-steps.css
Normal file
3
drinkMe/Client/wwwroot/css/bulma-steps.min.css
vendored
Normal file
11705
drinkMe/Client/wwwroot/css/bulma.css
vendored
Normal file
1
drinkMe/Client/wwwroot/css/bulma.min.css
vendored
Normal file
5605
drinkMe/Client/wwwroot/css/duotone.css
Normal file
5
drinkMe/Client/wwwroot/css/duotone.min.css
vendored
Normal file
7134
drinkMe/Client/wwwroot/css/fontawesome.css
vendored
Normal file
5
drinkMe/Client/wwwroot/css/fontawesome.min.css
vendored
Normal file
215
drinkMe/Client/wwwroot/css/style.css
Normal file
@ -0,0 +1,215 @@
|
||||
:root {
|
||||
--background: #fadcc7;
|
||||
--text-color: #542707;
|
||||
--black: #0a0a0a;
|
||||
--black-bis: #121212;
|
||||
--black-ter: #242424;
|
||||
--white: #fffefe;
|
||||
--primary-color: #fadcc7;
|
||||
--primary-color-light: white;
|
||||
--primary-color-dark: #f09351;
|
||||
--primary-gradiend-light: #fce8d9;
|
||||
--primary-gradiend-dark: #f8d0b4;
|
||||
--secondary-color: #f6d5f7;
|
||||
--secondary-color-light: white;
|
||||
--secondary-color-dark: #e895eb;
|
||||
--light-shadow: rgba(255, 255, 255, 0.5);
|
||||
--dark-shadow: rgba(240, 147, 81, 0.5);
|
||||
/* --fa-primary-color: $text-color;
|
||||
--fa-secondary-color: $main-dark-pc;
|
||||
--fa-primary-opacity: 0.80;
|
||||
--fa-secondary-opacity: 0.80;*/
|
||||
--danger-color: #fbd5d5;
|
||||
--warning-color: #fbf3d5;
|
||||
--info-color: #d5f1fb; }
|
||||
|
||||
/*$breakpoint argument choices:
|
||||
- phone
|
||||
- tab-port
|
||||
- tab-land
|
||||
- desk
|
||||
- big-desktop
|
||||
*/
|
||||
html {
|
||||
overflow-y: auto; }
|
||||
|
||||
.steps {
|
||||
list-style-type: none !important; }
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.mt-2-mobile {
|
||||
margin-top: .5rem !important; } }
|
||||
|
||||
.control.has-icons-left .icon, .control.has-icons-right .icon {
|
||||
z-index: 5; }
|
||||
|
||||
.neomorph {
|
||||
box-shadow: -8px -8px 16px var(--light-shadow), 8px 8px 16px var(--dark-shadow); }
|
||||
.neomorphSmall {
|
||||
box-shadow: -6px -6px 12px var(--light-shadow), 6px 6px 12px var(--dark-shadow); }
|
||||
.neomorphXSmall {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow); }
|
||||
.neomorphXXSmall {
|
||||
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow); }
|
||||
.neomorphBottom {
|
||||
filter: drop-shadow(8px 8px 14px var(--primary-color-dark)); }
|
||||
.neomorphInset {
|
||||
box-shadow: inset 8px 8px 16px var(--dark-shadow), inset -8px -8px 16px var(--light-shadow); }
|
||||
.neomorphInsetSmall {
|
||||
box-shadow: inset 6px 6px 12px var(--dark-shadow), inset -6px -6px 12px var(--light-shadow); }
|
||||
.neomorphInsetXSmall {
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow); }
|
||||
.neomorphInsetXXSmall {
|
||||
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow); }
|
||||
|
||||
.neoBtn {
|
||||
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; }
|
||||
.neoBtn:focus {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow) !important;
|
||||
border: none !important; }
|
||||
.neoBtnInsetPlain {
|
||||
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; }
|
||||
.neoBtnInsetPlain:focus {
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow) !important;
|
||||
border: none !important; }
|
||||
.neoBtnSmall {
|
||||
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; }
|
||||
.neoBtnSmall:focus {
|
||||
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow);
|
||||
border: none !important; }
|
||||
.neoBtnSmall:focus:not(:active) {
|
||||
box-shadow: -2px -2px 4px var(--light-shadow), 2px 2px 4px var(--dark-shadow); }
|
||||
.neoBtnSmallInsetPlain {
|
||||
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; }
|
||||
.neoBtnSmallInsetPlain:focus {
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow) !important;
|
||||
border: none !important; }
|
||||
.neoBtnSmallXInsetPlain {
|
||||
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; }
|
||||
.neoBtnSmallXInsetPlain:focus {
|
||||
box-shadow: inset 2px 2px 4px var(--dark-shadow), inset -2px -2px 4px var(--light-shadow) !important;
|
||||
border: none !important; }
|
||||
.neoBtnSmallPlain {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow);
|
||||
border: none !important; }
|
||||
.neoBtnSmallPlain:focus {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow) !important;
|
||||
border: none !important; }
|
||||
|
||||
.neoFile {
|
||||
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; }
|
||||
@media screen and (max-width: 768px) {
|
||||
.neoFile {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow) !important;
|
||||
background: var(--primary-color) !important; } }
|
||||
.neoFile:hover {
|
||||
box-shadow: -3px -3px 6px var(--light-shadow), 3px 3px 6px var(--dark-shadow) !important; }
|
||||
.neoFile.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; }
|
||||
.neoFile.is-active, .neoFile.active {
|
||||
box-shadow: inset 3px 3px 6px var(--dark-shadow), inset -3px -3px 6px var(--light-shadow) !important;
|
||||
color: var(--black) !important; }
|
||||
|
||||
.neoInput {
|
||||
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; }
|
||||
.neoInput:focus {
|
||||
border: none !important; }
|
||||
|
||||
.neoSelect > 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; }
|
||||
.neoSelect > select:focus {
|
||||
border: none !important; }
|
||||
|
||||
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; }
|
||||
|
||||
.drinkCartList {
|
||||
display: flex;
|
||||
flex-direction: column; }
|
||||
|
||||
.drinkCartItem {
|
||||
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; }
|
||||
.drinkCartItem:not(:last-child) {
|
||||
margin-bottom: 1rem; }
|
||||
|
||||
.drinkCartPItem {
|
||||
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; }
|
||||
.drinkCartPItem:not(:last-child) {
|
||||
margin-bottom: 1rem; }
|
||||
|
||||
.drinkCartName {
|
||||
grid-area: drinkName; }
|
||||
|
||||
.drinkCartCross {
|
||||
grid-area: drinkCross; }
|
||||
|
||||
.drinkCartQuantity {
|
||||
grid-area: drinkQuantity; }
|
||||
|
||||
.drinkCartPrice {
|
||||
grid-area: drinkPrice;
|
||||
white-space: nowrap; }
|
||||
|
||||
.column > .box > .title {
|
||||
padding: 10%; }
|
1
drinkMe/Client/wwwroot/css/style.min.css
vendored
Normal file
BIN
drinkMe/Client/wwwroot/favicon.ico
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
drinkMe/Client/wwwroot/icon-512.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
drinkMe/Client/wwwroot/images/background.jpg
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
drinkMe/Client/wwwroot/images/drinkMe-logo.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
drinkMe/Client/wwwroot/images/drinks/american-coffee.jpg
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
drinkMe/Client/wwwroot/images/drinks/chocolate.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
drinkMe/Client/wwwroot/images/drinks/italian-coffee.jpg
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
drinkMe/Client/wwwroot/images/drinks/missing.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
drinkMe/Client/wwwroot/images/drinks/tea.png
Normal file
After Width: | Height: | Size: 923 KiB |
30
drinkMe/Client/wwwroot/index.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>drinkMe</title>
|
||||
<base href="/" />
|
||||
<link href="css/bulma.min.css" rel="stylesheet" />
|
||||
<link href="css/bulma-steps.min.css" rel="stylesheet" />
|
||||
<link href="css/bulma-divider.min.css" rel="stylesheet" />
|
||||
<link href="css/fontawesome.min.css" rel="stylesheet" />
|
||||
<link href="css/duotone.min.css" rel="stylesheet" />
|
||||
<link href="css/style.min.css" rel="stylesheet" />
|
||||
<link href="manifest.json" rel="manifest" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="is-flex is-align-items-center loading">
|
||||
<p><img src="images/drinkMe-logo.png" alt="loading logo" /></p>
|
||||
<h1 class="title">Loading...</h1>
|
||||
</div>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script>navigator.serviceWorker.register('service-worker.js');</script>
|
||||
</body>
|
||||
|
||||
</html>
|
15
drinkMe/Client/wwwroot/manifest.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "drinkMe",
|
||||
"short_name": "drinkMe",
|
||||
"start_url": "./",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#03173d",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
]
|
||||
}
|
4
drinkMe/Client/wwwroot/service-worker.js
Normal file
@ -0,0 +1,4 @@
|
||||
// In development, always fetch from the network and do not enable offline support.
|
||||
// This is because caching would make development more difficult (changes would not
|
||||
// be reflected on the first load after each change).
|
||||
self.addEventListener('fetch', () => { });
|
48
drinkMe/Client/wwwroot/service-worker.published.js
Normal file
@ -0,0 +1,48 @@
|
||||
// Caution! Be sure you understand the caveats before publishing an application with
|
||||
// offline support. See https://aka.ms/blazor-offline-considerations
|
||||
|
||||
self.importScripts('./service-worker-assets.js');
|
||||
self.addEventListener('install', event => event.waitUntil(onInstall(event)));
|
||||
self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
|
||||
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
|
||||
|
||||
const cacheNamePrefix = 'offline-cache-';
|
||||
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
|
||||
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
|
||||
const offlineAssetsExclude = [ /^service-worker\.js$/ ];
|
||||
|
||||
async function onInstall(event) {
|
||||
console.info('Service worker: Install');
|
||||
|
||||
// Fetch and cache all matching items from the assets manifest
|
||||
const assetsRequests = self.assetsManifest.assets
|
||||
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
|
||||
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
|
||||
.map(asset => new Request(asset.url, { integrity: asset.hash }));
|
||||
await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
|
||||
}
|
||||
|
||||
async function onActivate(event) {
|
||||
console.info('Service worker: Activate');
|
||||
|
||||
// Delete unused caches
|
||||
const cacheKeys = await caches.keys();
|
||||
await Promise.all(cacheKeys
|
||||
.filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
|
||||
.map(key => caches.delete(key)));
|
||||
}
|
||||
|
||||
async function onFetch(event) {
|
||||
let cachedResponse = null;
|
||||
if (event.request.method === 'GET') {
|
||||
// For all navigation requests, try to serve index.html from cache
|
||||
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
|
||||
const shouldServeIndexHtml = event.request.mode === 'navigate';
|
||||
|
||||
const request = shouldServeIndexHtml ? 'index.html' : event.request;
|
||||
const cache = await caches.open(cacheName);
|
||||
cachedResponse = await cache.match(request);
|
||||
}
|
||||
|
||||
return cachedResponse || fetch(event.request);
|
||||
}
|
BIN
drinkMe/Client/wwwroot/webfonts/fa-duotone-900.eot
Normal file
15319
drinkMe/Client/wwwroot/webfonts/fa-duotone-900.svg
Normal file
After Width: | Height: | Size: 2.5 MiB |
BIN
drinkMe/Client/wwwroot/webfonts/fa-duotone-900.ttf
Normal file
BIN
drinkMe/Client/wwwroot/webfonts/fa-duotone-900.woff
Normal file
BIN
drinkMe/Client/wwwroot/webfonts/fa-duotone-900.woff2
Normal file
95
drinkMe/Server/Controllers/DrinksController.cs
Normal file
@ -0,0 +1,95 @@
|
||||
using collAnon.Pub.Shared;
|
||||
using drinkMe.Server.Models;
|
||||
using drinkMe.Server.Services.Interfaces;
|
||||
using drinkMe.Shared;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Server.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]/[action]")]
|
||||
public class DrinksController : ControllerBase
|
||||
{
|
||||
readonly IDataService DataService;
|
||||
readonly IPaymentService PaymentService;
|
||||
|
||||
public DrinksController(IDataService dataService, IPaymentService paymentService)
|
||||
{
|
||||
DataService = dataService;
|
||||
PaymentService = paymentService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> IsValidDiscountCode([FromQuery] string code)
|
||||
{
|
||||
try
|
||||
{
|
||||
var validationResult = await DataService.IsValidDiscountCode(code);
|
||||
if (!validationResult.IsValid)
|
||||
return StatusCode(validationResult.StatusCode, validationResult);
|
||||
|
||||
var validationData = validationResult.Data as DiscountCode;
|
||||
var resultData = new DiscountCodeViewModel
|
||||
{
|
||||
Code = validationData.Code,
|
||||
ApplicableProducts = validationData.ApplicableProducts,
|
||||
DiscountPercentage = validationData.DiscountPercentage
|
||||
};
|
||||
|
||||
return Ok(resultData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new WebResult().Invalidate($"Error at ${nameof(IsValidDiscountCode)}", default, ex));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetDrinks()
|
||||
{
|
||||
try
|
||||
{
|
||||
var drinks = DataService.GetDrinks();
|
||||
if (drinks.Count == 0)
|
||||
return NotFound(new WebResult().Invalidate("No drinks available.", StatusCodes.Status404NotFound));
|
||||
|
||||
var viewDrinks = drinks.Select(d => new DrinkViewModel
|
||||
{
|
||||
Id = d.Id,
|
||||
Name = d.Name,
|
||||
Description = d.Description,
|
||||
Price = d.Price,
|
||||
PictureName = d.PictureName
|
||||
}).ToArray();
|
||||
|
||||
return Ok(viewDrinks);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new WebResult().Invalidate($"Error at ${nameof(GetDrinks)}", default, ex));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Pay(PurchaseCart purchaseCart)
|
||||
{
|
||||
try
|
||||
{
|
||||
var successfulPayment = await PaymentService.Pay(purchaseCart);
|
||||
|
||||
if (!successfulPayment)
|
||||
return BadRequest(new WebResult().Invalidate("Payment failed.", StatusCodes.Status400BadRequest));
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new WebResult().Invalidate($"Error at ${nameof(Pay)}", default, ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
drinkMe/Server/Program.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Server
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
30
drinkMe/Server/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.Server": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
drinkMe/Server/Services/DataService.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using collAnon.Pub.Shared;
|
||||
using drinkMe.Server.Models;
|
||||
using drinkMe.Server.Services.Interfaces;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Server.Services
|
||||
{
|
||||
public class DataService : IDataService
|
||||
{
|
||||
List<DiscountCode> DiscountCodes = new List<DiscountCode>
|
||||
{
|
||||
new DiscountCode{ Code ="ITS_M0NDAY", DiscountPercentage = 15, ApplicableProducts = new[] { 2 } },
|
||||
new DiscountCode{ Code ="ITS_K1NDA_COLD", DiscountPercentage = 30, ApplicableProducts = new[] { 4 } },
|
||||
new DiscountCode{ Code ="I_NEED_R3LAX", DiscountPercentage = 25, ApplicableProducts = new[] { 3 } },
|
||||
new DiscountCode{ Code ="D1AMOND_HANDS", DiscountPercentage = 100, ApplicableProducts = new[] { 1 } }
|
||||
};
|
||||
|
||||
public List<Drink> GetDrinks() =>
|
||||
new List<Drink>
|
||||
{
|
||||
new Drink
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Italian Coffee",
|
||||
Description = "Short but strong.",
|
||||
Price = 3.50f,
|
||||
PictureName = "italian-coffee.jpg"
|
||||
},
|
||||
new Drink
|
||||
{
|
||||
Id = 2,
|
||||
Name = "American Coffee",
|
||||
Description = "Dilluted in water to a cup size, not as strong as the italian one.",
|
||||
Price = 3.0f,
|
||||
PictureName = "american-coffee.jpg"
|
||||
},
|
||||
new Drink
|
||||
{
|
||||
Id = 3,
|
||||
Name = "Tea",
|
||||
Description = "Black Earl Grey, good alternative to coffee from time to time.",
|
||||
Price = 2.0f,
|
||||
PictureName = "tea.png"
|
||||
},
|
||||
new Drink
|
||||
{
|
||||
Id = 4,
|
||||
Name = "Chocolate",
|
||||
Description = "Your dose of sugar and calories, just don't loose the hang.",
|
||||
Price = 4.0f,
|
||||
PictureName = "chocolate.jpg"
|
||||
}
|
||||
};
|
||||
|
||||
public async Task<WebResult> IsValidDiscountCode(string code)
|
||||
{
|
||||
var result = new WebResult();
|
||||
if (!DiscountCodes.Any(dc => dc.Code == code))
|
||||
return result.Invalidate("Invalid discount code.", StatusCodes.Status404NotFound);
|
||||
|
||||
result.Data = DiscountCodes.First(dc => dc.Code == code);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
13
drinkMe/Server/Services/Interfaces/IDataService.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using collAnon.Pub.Shared;
|
||||
using drinkMe.Server.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Server.Services.Interfaces
|
||||
{
|
||||
public interface IDataService
|
||||
{
|
||||
List<Drink> GetDrinks();
|
||||
Task<WebResult> IsValidDiscountCode(string code);
|
||||
}
|
||||
}
|
13
drinkMe/Server/Services/Interfaces/IPaymentService.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.Server.Services.Interfaces
|
||||
{
|
||||
public interface IPaymentService
|
||||
{
|
||||
Task<bool> Pay(PurchaseCart purchaseCart);
|
||||
}
|
||||
}
|
18
drinkMe/Server/Services/PaymentService.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using drinkMe.Server.Services.Interfaces;
|
||||
using drinkMe.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Server.Services
|
||||
{
|
||||
public class PaymentService : IPaymentService
|
||||
{
|
||||
public async Task<bool> Pay(PurchaseCart purchaseCart)
|
||||
{
|
||||
//completing the purchase
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
66
drinkMe/Server/Startup.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using drinkMe.Server.Services;
|
||||
using drinkMe.Server.Services.Interfaces;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.HttpsPolicy;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Linq;
|
||||
|
||||
namespace drinkMe.Server
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IDataService, DataService>()
|
||||
.AddScoped<IPaymentService, PaymentService>();
|
||||
|
||||
services.AddControllers().AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.IgnoreReadOnlyFields = true;
|
||||
options.JsonSerializerOptions.IgnoreReadOnlyProperties = true;
|
||||
});
|
||||
services.AddRazorPages();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseWebAssemblyDebugging();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapRazorPages();
|
||||
endpoints.MapControllers();
|
||||
endpoints.MapFallbackToFile("index.html");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
9
drinkMe/Server/appsettings.Development.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
10
drinkMe/Server/appsettings.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
18
drinkMe/Server/drinkMe.Server.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Client\drinkMe.Client.csproj" />
|
||||
<ProjectReference Include="..\drinkMe.Server.Models\drinkMe.Server.Models.csproj" />
|
||||
<ProjectReference Include="..\Shared\drinkMe.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
14
drinkMe/Shared/CartItem.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Shared
|
||||
{
|
||||
public class CartItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Quantity { get; set; } = 1;
|
||||
}
|
||||
}
|
9
drinkMe/Shared/DiscountCodeViewModel.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace drinkMe.Shared
|
||||
{
|
||||
public class DiscountCodeViewModel
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public float DiscountPercentage { get; set; }
|
||||
public int[] ApplicableProducts { get; set; }
|
||||
}
|
||||
}
|
18
drinkMe/Shared/DrinkViewModel.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace drinkMe.Shared
|
||||
{
|
||||
public class DrinkViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "Missing name";
|
||||
public string Description { get; set; } = "Missing description";
|
||||
public string PictureName { get; set; } = "missing.png";
|
||||
[Range(1, 20)]
|
||||
public int Quantity { get; set; } = 1;
|
||||
public float Price { get; set; } = 0;
|
||||
|
||||
public bool IsInTheCart { get; set; } = false;
|
||||
}
|
||||
}
|
15
drinkMe/Shared/PurchaseCart.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace drinkMe.Shared
|
||||
{
|
||||
public class PurchaseCart
|
||||
{
|
||||
public bool IsPayedWithCash { get; set; } = false;
|
||||
public List<CartItem> PurchasingItems { get; set; } = new List<CartItem>();
|
||||
public List<DiscountCodeViewModel> Discounts { get; set; } = new List<DiscountCodeViewModel>();
|
||||
public string CreditCardNumber { get; set; }
|
||||
public string CreditCardExpirationMonth { get; set; }
|
||||
public string CreditCardExpirationYear { get; set; }
|
||||
public string CreditCardCVVCode { get; set; }
|
||||
}
|
||||
}
|
47
drinkMe/Shared/WebResult.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace collAnon.Pub.Shared
|
||||
{
|
||||
public class WebResult
|
||||
{
|
||||
public WebResult Invalidate<T>(string errorMessage, int statusCode = StatusCodes.Status400BadRequest, Exception exception = null, T defaultData = default)
|
||||
{
|
||||
IsValid = false;
|
||||
ErrorMessage = errorMessage;
|
||||
StatusCode = statusCode;
|
||||
Exception = exception;
|
||||
Data = defaultData;
|
||||
return this;
|
||||
}
|
||||
public WebResult Invalidate(string errorMessage, int statusCode = StatusCodes.Status400BadRequest, Exception exception = null)
|
||||
{
|
||||
IsValid = false;
|
||||
ErrorMessage = errorMessage;
|
||||
StatusCode = statusCode;
|
||||
Exception = exception;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WebResult Invalidate(WebResult result)
|
||||
{
|
||||
IsValid = result.IsValid;
|
||||
ErrorMessage = result.ErrorMessage;
|
||||
StatusCode = result.StatusCode;
|
||||
Exception = result.Exception;
|
||||
Data = result.Data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int StatusCode { get; set; } = StatusCodes.Status200OK;
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public bool IsValid { get; set; } = true;
|
||||
[JsonIgnore]
|
||||
public object Data { get; set; }
|
||||
[JsonIgnore]
|
||||
public Exception Exception { get; set; }
|
||||
}
|
||||
|
||||
}
|
14
drinkMe/Shared/drinkMe.Shared.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SupportedPlatform Include="browser" />
|
||||
</ItemGroup>
|
||||
</Project>
|
25
drinkMe/drinkMe.Client.Models/CreditCardForm.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace drinkMe.Client.Models
|
||||
{
|
||||
public class CreditCardForm
|
||||
{
|
||||
[Required, Display(Name = "Credit card number"),
|
||||
MaxLength(16), MinLength(16)]
|
||||
public string Number { get; set; }
|
||||
[Required, Display(Name = "Expiration month"),
|
||||
Range(2, 2), RegularExpression(@"^((0[1-9])|(1[0-2]))$", ErrorMessage = "Invalid month")]
|
||||
public string ExpirationMonth { get; set; }
|
||||
[Required, Display(Name = "Expiration year"),
|
||||
Range(2, 2), RegularExpression(@"^(\d{2})$", ErrorMessage = "Invalid year")]
|
||||
public string ExpirationYear { get; set; }
|
||||
[Required, Display(Name = "CVV code"),
|
||||
Range(3, 3), RegularExpression(@"^(\d{3})$", ErrorMessage = "Invalid CVV code")]
|
||||
public string CVVCode { get; set; }
|
||||
}
|
||||
}
|
11
drinkMe/drinkMe.Client.Models/DiscountForm.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace drinkMe.Client.Models
|
||||
{
|
||||
public class DiscountForm
|
||||
{
|
||||
[Required,
|
||||
Display(Name = "Discount code")]
|
||||
public string DiscountCode { get; set; }
|
||||
}
|
||||
}
|
9
drinkMe/drinkMe.Client.Models/PaymentMethod.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace drinkMe.Client.Models
|
||||
{
|
||||
public enum PaymentMethod
|
||||
{
|
||||
Undefined,
|
||||
Cash,
|
||||
CreditCard
|
||||
}
|
||||
}
|
9
drinkMe/drinkMe.Client.Models/PurchaseStep.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace drinkMe.Client.Models
|
||||
{
|
||||
public enum PurchaseStep
|
||||
{
|
||||
ChoosingProducts,
|
||||
PaymentSetup,
|
||||
SuccessfulPurchase
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
9
drinkMe/drinkMe.Server.Models/DiscountCode.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace drinkMe.Server.Models
|
||||
{
|
||||
public class DiscountCode
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public float DiscountPercentage { get; set; }
|
||||
public int[] ApplicableProducts { get; set; }
|
||||
}
|
||||
}
|
11
drinkMe/drinkMe.Server.Models/Drink.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace drinkMe.Server.Models
|
||||
{
|
||||
public class Drink
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "Missing name";
|
||||
public string Description { get; set; } = "Missing description";
|
||||
public float Price { get; set; } = 0;
|
||||
public string PictureName { get; set; } = "missing.png";
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|