Commit 314998d3 authored by Ryan Nowak's avatar Ryan Nowak
Browse files

Implement step 4

parent f8d02d1f
......@@ -44,9 +44,13 @@ Lunch
- The OrderDetails should poll for updates to the order fromthe backend
- Go back to the index and make placing an order navigate you to the MyOrders page
1. DI and the AppState pattern
- Create a service for interacting with the backend, repository abstraction
- Refactor HttpClient code to use service instead
- Talk to DI scopes
- Notice that we lose track of any pizzas when you switch between MyOrders and Index, we can fix this by storing the state at a higher level
- Create the OrderState class
- Add to DI in Startup (Scoped)
- Move most of our properties / methods in Index and ConfigurePizza to the OrderState
- Add a StateChanged event to OrderState
- Subscribe to StateChanged from Index in OnInit
- Add an implementation of IDisposable to unsubscribe
1. JS interop
- Add order status
- Real status (map location, time to delivery) via polling
......
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BlazingPizza.Client
{
public class OrderState
{
public event EventHandler StateChanged;
public bool ShowingConfigureDialog { get; private set; }
public Pizza ConfiguringPizza { get; private set; }
public Order Order { get; private set; } = new Order();
public void ShowConfigurePizzaDialog(PizzaSpecial special)
{
ConfiguringPizza = new Pizza()
{
Special = special,
SpecialId = special.Id,
Size = Pizza.DefaultSize,
Toppings = new List<PizzaTopping>(),
};
ShowingConfigureDialog = true;
}
public void CancelConfigurePizzaDialog()
{
ConfiguringPizza = null;
ShowingConfigureDialog = false;
StateHasChanged();
}
public void ConfirmConfigurePizzaDialog()
{
Order.Pizzas.Add(ConfiguringPizza);
ConfiguringPizza = null;
ShowingConfigureDialog = false;
StateHasChanged();
}
public void AddTopping(Topping topping)
{
if (ConfiguringPizza.Toppings.Find(pt => pt.Topping == topping) == null)
{
ConfiguringPizza.Toppings.Add(new PizzaTopping() { Topping = topping });
}
StateHasChanged();
}
public void RemoveTopping(Topping topping)
{
ConfiguringPizza.Toppings.RemoveAll(pt => pt.Topping == topping);
StateHasChanged();
}
public void RemoveConfiguredPizza(Pizza pizza)
{
Order.Pizzas.Remove(pizza);
StateHasChanged();
}
public void ResetOrder()
{
Order = new Order();
}
private void StateHasChanged()
{
StateChanged?.Invoke(this, EventArgs.Empty);
}
}
}
@page "/"
@inject HttpClient HttpClient
@inject OrderState OrderState
@inject IUriHelper UriHelper
@implements IDisposable
<div class="main">
<ul class="pizza-cards">
......@@ -8,7 +10,7 @@
{
@foreach (var special in specials)
{
<li onclick="@(() => ShowConfigurePizzaDialog(special))" style="background-image: url('@special.ImageUrl')">
<li onclick="@(() => OrderState.ShowConfigurePizzaDialog(special))" style="background-image: url('@special.ImageUrl')">
<div class="pizza-info">
<span class="title">@special.Name</span>
@special.Description
......@@ -21,14 +23,14 @@
</div>
<div class="sidebar">
@if (order.Pizzas.Any())
@if (OrderState.Order.Pizzas.Any())
{
<div class="order-contents">
<h2>Your order</h2>
@foreach (var configuredPizza in order.Pizzas)
@foreach (var configuredPizza in OrderState.Order.Pizzas)
{
<ConfiguredPizzaItem Pizza="configuredPizza" OnRemoved="() => RemoveConfiguredPizza(configuredPizza)" />
<ConfiguredPizzaItem Pizza="configuredPizza" OnRemoved="() => OrderState.RemoveConfiguredPizza(configuredPizza)" />
}
</div>
}
......@@ -37,74 +39,40 @@
<div class="empty-cart">Choose a pizza<br>to get started</div>
}
<div class="order-total @(order.Pizzas.Any() ? "" : "hidden")">
<div class="order-total @(OrderState.Order.Pizzas.Any() ? "" : "hidden")">
Total:
<span class="total-price">@order.GetFormattedTotalPrice()</span>
<button class="btn btn-warning" disabled="@(order.Pizzas.Count == 0)" onclick="@PlaceOrder">
<span class="total-price">@OrderState.Order.GetFormattedTotalPrice()</span>
<button class="btn btn-warning" disabled="@(OrderState.Order.Pizzas.Count == 0)" onclick="@PlaceOrder">
Order >
</button>
</div>
</div>
@if (showingConfigureDialog)
@if (OrderState.ShowingConfigureDialog)
{
<ConfigurePizzaDialog
Pizza="configuringPizza"
OnCancel="CancelConfigurePizzaDialog"
OnConfirm="ConfirmConfigurePizzaDialog"/>
<ConfigurePizzaDialog />
}
@functions {
List<PizzaSpecial> specials;
Pizza configuringPizza;
bool showingConfigureDialog;
Order order = new Order();
protected async override Task OnInitAsync()
{
OrderState.StateChanged += OnOrderStateChanged;
specials = await HttpClient.GetJsonAsync<List<PizzaSpecial>>("/specials");
}
void ShowConfigurePizzaDialog(PizzaSpecial special)
void IDisposable.Dispose()
{
configuringPizza = new Pizza()
{
Special = special,
SpecialId = special.Id,
Size = Pizza.DefaultSize,
Toppings = new List<PizzaTopping>(),
};
showingConfigureDialog = true;
}
void CancelConfigurePizzaDialog()
{
configuringPizza = null;
showingConfigureDialog = false;
StateHasChanged();
OrderState.StateChanged -= OnOrderStateChanged;
}
void ConfirmConfigurePizzaDialog()
{
order.Pizzas.Add(configuringPizza);
configuringPizza = null;
showingConfigureDialog = false;
StateHasChanged();
}
void RemoveConfiguredPizza(Pizza pizza)
{
order.Pizzas.Remove(pizza);
StateHasChanged();
}
void OnOrderStateChanged(object sender, EventArgs e) => StateHasChanged();
async Task PlaceOrder()
{
await HttpClient.PostJsonAsync("/orders", order);
order = new Order();
await HttpClient.PostJsonAsync("/orders", OrderState.Order);
OrderState.ResetOrder();
UriHelper.NavigateTo("myorders");
}
}
@inject HttpClient HttpClient
@inject OrderState OrderState
<div class="dialog-container">
<div class="dialog">
......@@ -44,28 +45,25 @@
<div class="topping">
@topping.Topping.Name
<span class="topping-price">@topping.Topping.GetFormattedPrice()</span>
<button type="button" class="delete-topping" onclick="@(() => RemoveTopping(topping.Topping))">x</button>
<button type="button" class="delete-topping" onclick="@(() => OrderState.RemoveTopping(topping.Topping))">x</button>
</div>
}
</div>
</form>
<div class="dialog-buttons">
<button class="btn btn-secondary mr-auto" onclick="@OnCancel">Cancel</button>
<button class="btn btn-secondary mr-auto" onclick="@OrderState.CancelConfigurePizzaDialog">Cancel</button>
<span class="mr-center">
Price: <span class="price">@(Pizza.GetFormattedTotalPrice())</span>
</span>
<button class="btn btn-success ml-auto" onclick="@OnConfirm">Order ></button>
<button class="btn btn-success ml-auto" onclick="@OrderState.ConfirmConfigurePizzaDialog">Order ></button>
</div>
</div>
</div>
@functions {
List<Topping> toppings { get; set; }
[Parameter] Pizza Pizza { get; set; }
[Parameter] Action OnCancel { get; set; }
[Parameter] Action OnConfirm { get; set; }
Pizza Pizza => OrderState.ConfiguringPizza;
protected async override Task OnInitAsync()
{
......@@ -76,21 +74,7 @@
{
if (int.TryParse((string)e.Value, out var index) && index >= 0)
{
AddTopping(toppings[index]);
OrderState.AddTopping(toppings[index]);
}
}
void AddTopping(Topping topping)
{
if (Pizza.Toppings.Find(pt => pt.Topping == topping) == null)
{
Pizza.Toppings.Add(new PizzaTopping() { Topping = topping });
}
}
void RemoveTopping(Topping topping)
{
Pizza.Toppings.RemoveAll(pt => pt.Topping == topping);
StateHasChanged();
}
}
......@@ -7,7 +7,7 @@ namespace BlazingPizza.Client
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<OrderState>();
}
public void Configure(IBlazorApplicationBuilder app)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment