initial commit

This commit is contained in:
Niklas Kapelle 2020-12-17 16:52:43 +01:00
commit 2918fe6b12
102 changed files with 4465 additions and 0 deletions

435
.gitignore vendored Normal file
View File

@ -0,0 +1,435 @@
*.db
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# JetBrains Rider
.idea/
*.sln.iml
##
## Visual Studio Code
##
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="$(AspNetCoreVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
@if (Show)
{
<div class="dialog-container">
<div class="dialog">
@ChildContent
</div>
</div>
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public bool Show { get; set; }
}

View File

@ -0,0 +1,36 @@
@typeparam TItem
@if (items == null)
{
@Loading
}
else if (!items.Any())
{
@Empty
}
else
{
<div class="list-group @ListGroupClass">
@foreach (var item in items)
{
<div class="list-group-item">
@Item(item)
</div>
}
</div>
}
@code {
IEnumerable<TItem> items;
[Parameter] public Func<Task<IEnumerable<TItem>>> Loader { get; set; }
[Parameter] public RenderFragment Loading { get; set; }
[Parameter] public RenderFragment Empty { get; set; }
[Parameter] public RenderFragment<TItem> Item { get; set; }
[Parameter] public string ListGroupClass { get; set; }
protected override async Task OnParametersSetAsync()
{
items = await Loader();
}
}

View File

@ -0,0 +1 @@
@using Microsoft.AspNetCore.Components.Web

View File

@ -0,0 +1,19 @@
<CascadingAuthenticationState>
<Router AppAssembly="typeof(Program).Assembly" Context="routeData">
<Found>
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)">
<NotAuthorized>
<RedirectToLogin />
</NotAuthorized>
<Authorizing>
<div class="main">Bitte warten...</div>
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="typeof(MainLayout)">
<div class="main">Nichts gefunden</div>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>7.3</LangVersion>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="$(BlazorVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="$(BlazorVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="$(BlazorVersion)" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="$(AspNetCoreVersion)" />
<PackageReference Include="System.Net.Http.Json" Version="$(SystemNetHttpJsonVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BlazingComponents\BlazingComponents.csproj" />
<ProjectReference Include="..\BlazingPizza.ComponentsLibrary\BlazingPizza.ComponentsLibrary.csproj" />
<ProjectReference Include="..\BlazingPizza.Shared\BlazingPizza.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
using Microsoft.JSInterop;
using System.Threading.Tasks;
namespace BlazingPizza.Client
{
public static class JSRuntimeExtensions
{
public static ValueTask<bool> Confirm(this IJSRuntime jsRuntime, string message)
{
return jsRuntime.InvokeAsync<bool>("confirm", message);
}
}
}

View File

@ -0,0 +1,55 @@
using System.Collections.Generic;
namespace BlazingPizza.Client
{
public class OrderState
{
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;
}
public void ConfirmConfigurePizzaDialog()
{
Order.Pizzas.Add(ConfiguringPizza);
ConfiguringPizza = null;
ShowingConfigureDialog = false;
}
public void RemoveConfiguredPizza(Pizza pizza)
{
Order.Pizzas.Remove(pizza);
}
public void ResetOrder()
{
Order = new Order();
}
public void ReplaceOrder(Order order)
{
Order = order;
}
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
namespace BlazingPizza.Client
{
public class OrdersClient
{
private readonly HttpClient httpClient;
public OrdersClient(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public async Task<IEnumerable<OrderWithStatus>> GetOrders() =>
await httpClient.GetFromJsonAsync<IEnumerable<OrderWithStatus>>("orders");
public async Task<OrderWithStatus> GetOrder(int orderId) =>
await httpClient.GetFromJsonAsync<OrderWithStatus>($"orders/{orderId}");
public async Task<int> PlaceOrder(Order order)
{
var response = await httpClient.PostAsJsonAsync("orders", order);
response.EnsureSuccessStatusCode();
var orderId = await response.Content.ReadFromJsonAsync<int>();
return orderId;
}
public async Task SubscribeToNotifications(NotificationSubscription subscription)
{
var response = await httpClient.PutAsJsonAsync("notifications/subscribe", subscription);
response.EnsureSuccessStatusCode();
}
}
}

View File

@ -0,0 +1,32 @@
@page "/authentication/{action}"
@inject OrderState OrderState
@inject NavigationManager NavigationManager
<RemoteAuthenticatorViewCore
TAuthenticationState="PizzaAuthenticationState"
AuthenticationState="RemoteAuthenticationState"
OnLogInSucceeded="RestorePizza"
Action="@Action" />
@code{
[Parameter] public string Action { get; set; }
public PizzaAuthenticationState RemoteAuthenticationState { get; set; } = new PizzaAuthenticationState();
protected override void OnInitialized()
{
if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn, Action))
{
// Preserve the current order so that we don't loose it
RemoteAuthenticationState.Order = OrderState.Order;
}
}
private void RestorePizza(PizzaAuthenticationState pizzaState)
{
if (pizzaState.Order != null)
{
OrderState.ReplaceOrder(pizzaState.Order);
}
}
}

View File

@ -0,0 +1,70 @@
@page "/checkout"
@attribute [Authorize]
@inject OrderState OrderState
@inject OrdersClient OrdersClient
@inject NavigationManager NavigationManager
@inject IJSRuntime JSRuntime
<div class="main">
<EditForm Model="OrderState.Order.DeliveryAddress" OnValidSubmit="PlaceOrder">
<div class="checkout-cols">
<div class="checkout-order-details">
<h4>Bestellung überprüfen</h4>
<OrderReview Order="OrderState.Order" />
</div>
<div class="checkout-delivery-address">
<h4>Lieferaddresse...</h4>
<AddressEditor Address="OrderState.Order.DeliveryAddress" />
</div>
</div>
<button type="submit" class="checkout-button btn btn-warning" disabled="@isSubmitting">
Bestellen
</button>
<DataAnnotationsValidator />
</EditForm>
</div>
@code {
bool isSubmitting;
protected override void OnInitialized()
{
// In the background, ask if they want to be notified about order updates
_ = RequestNotificationSubscriptionAsync();
}
async Task RequestNotificationSubscriptionAsync()
{
var subscription = await JSRuntime.InvokeAsync<NotificationSubscription>("blazorPushNotifications.requestSubscription");
if (subscription != null)
{
try
{
await OrdersClient.SubscribeToNotifications(subscription);
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
}
}
async Task PlaceOrder()
{
isSubmitting = true;
try
{
var newOrderId = await OrdersClient.PlaceOrder(OrderState.Order);
OrderState.ResetOrder();
NavigationManager.NavigateTo($"myorders/{newOrderId}");
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
}
}

View File

@ -0,0 +1,24 @@
@page "/impressum"
<div class="main">
<h1>Impressum</h1>
<p>Angaben gemäß § 5 TMG</p>
<p>Max Muster <br>
Musterweg<br>
12345 Musterstadt <br>
</p>
<p> <strong>Vertreten durch: </strong><br>
Max Muster<br>
</p>
<p><strong>Kontakt:</strong> <br>
Telefon: 01234-789456<br>
Fax: 1234-56789<br>
E-Mail: <a href='mailto:max@muster.de'>max@muster.de</a><br></p>
<p><strong>Umsatzsteuer-ID: </strong> <br>
Umsatzsteuer-Identifikationsnummer gemäß §27a Umsatzsteuergesetz: Musterustid.<br><br>
<strong>Wirtschafts-ID: </strong><br>
Musterwirtschaftsid<br>
</p>
<p><strong>Aufsichtsbehörde:</strong><br>
Musteraufsicht Musterstadt<br></p><br>
</div>

View File

@ -0,0 +1,74 @@
@page "/"
@inject HttpClient HttpClient
@inject OrderState OrderState
@inject NavigationManager NavigationManager
@inject IJSRuntime JS
<div class="main">
<ul class="pizza-cards">
@if (specials != null)
{
@foreach (var special in specials)
{
<li @onclick="@(() =>OrderState.ShowConfigurePizzaDialog(special))" style="background-image: url('@special.ImageUrl')">
<div class="pizza-info">
<span class="title">@special.Name</span>
@special.Description
<span class="price">@special.GetFormattedBasePrice()</span>
</div>
</li>
}
}
</ul>
</div>
<div class="sidebar">
@if (Order.Pizzas.Any())
{
<div class="order-contents">
<h2>Ihre Bestellung</h2>
@foreach (var configuredPizza in Order.Pizzas)
{
<ConfiguredPizzaItem Pizza="configuredPizza" OnRemoved="@(() => RemovePizza(configuredPizza))" />
}
</div>
}
else
{
<div class="empty-cart">Wählen sie eine Pizza<br>um zu beginnen</div>
}
<div class="order-total @(Order.Pizzas.Any() ? "" : "hidden")">
Summe:
<span class="total-price">@Order.GetFormattedTotalPrice()</span>
<a href="checkout" class="@(Order.Pizzas.Count == 0 ? "btn btn-warning disabled" : "btn btn-warning")">
Bestellen >
</a>
</div>
</div>
<TemplatedDialog Show="OrderState.ShowingConfigureDialog">
<ConfigurePizzaDialog
Pizza="OrderState.ConfiguringPizza"
OnCancel="OrderState.CancelConfigurePizzaDialog"
OnConfirm="OrderState.ConfirmConfigurePizzaDialog" />
</TemplatedDialog>
@code {
List<PizzaSpecial> specials;
Order Order => OrderState.Order;
protected override async Task OnInitializedAsync()
{
specials = await HttpClient.GetFromJsonAsync<List<PizzaSpecial>>("specials");
}
async Task RemovePizza(Pizza configuredPizza)
{
if (await JS.Confirm($"{configuredPizza.Special.Name} von der Bestellung entfernen?"))
{
OrderState.RemoveConfiguredPizza(configuredPizza);
}
}
}

View File

@ -0,0 +1,46 @@
@page "/myorders"
@attribute [Authorize]
@inject OrdersClient OrdersClient
<div class="main">
<TemplatedList Loader="@LoadOrders" ListGroupClass="orders-list">
<Loading>Lade...</Loading>
<Empty>
<h2>Noch keine Bestellungen</h2>
<a class="btn btn-success" href="">Bestell eine Pizza</a>
</Empty>
<Item Context="item">
<div class="col">
<h5>@item.Order.CreatedTime.ToLongDateString()</h5>
Produkte:
<strong>@item.Order.Pizzas.Count()</strong>;
Gesamtpreis:
<strong>€@item.Order.GetFormattedTotalPrice()</strong>
</div>
<div class="col">
Status: <strong>@item.StatusText</strong>
</div>
<div class="col flex-grow-0">
<a href="myorders/@item.Order.OrderId" class="btn btn-success">
Verfolgen &gt;
</a>
</div>
</Item>
</TemplatedList>
</div>
@code {
async Task<IEnumerable<OrderWithStatus>> LoadOrders()
{
var ordersWithStatus = Enumerable.Empty<OrderWithStatus>();
try
{
ordersWithStatus = await OrdersClient.GetOrders();
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
return ordersWithStatus;
}
}

View File

@ -0,0 +1,95 @@
@page "/myorders/{orderId:int}"
@attribute [Authorize]
@using System.Threading
@inject OrdersClient OrdersClient
@implements IDisposable
<div class="main">
@if (invalidOrder)
{
<h2>NEIN</h2>
<p>Konnte keine Bestellungen laden</p>
}
else if (orderWithStatus == null)
{
<text>Lade...</text>
}
else
{
<div class="track-order">
<div class="track-order-title">
<h2>
Bestellt am @orderWithStatus.Order.CreatedTime.ToLongDateString()
</h2>
<p class="ml-auto mb-0">
Status: <strong>@orderWithStatus.StatusText</strong>
</p>
</div>
<div class="track-order-body">
<div class="track-order-details">
<OrderReview Order="orderWithStatus.Order" />
</div>
<div class="track-order-map">
<Map Zoom="13" Markers="orderWithStatus.MapMarkers" />
</div>
</div>
</div>
}
</div>
@code {
[Parameter] public int OrderId { get; set; }
OrderWithStatus orderWithStatus;
bool invalidOrder;
CancellationTokenSource pollingCancellationToken;
protected override void OnParametersSet()
{
// If we were already polling for a different order, stop doing so
pollingCancellationToken?.Cancel();
// Start a new poll loop
PollForUpdates();
}
private async void PollForUpdates()
{
invalidOrder = false;
pollingCancellationToken = new CancellationTokenSource();
while (!pollingCancellationToken.IsCancellationRequested)
{
try
{
orderWithStatus = await OrdersClient.GetOrder(OrderId);
StateHasChanged();
if (orderWithStatus.IsDelivered)
{
pollingCancellationToken.Cancel();
}
else
{
await Task.Delay(4000);
}
}
catch (AccessTokenNotAvailableException ex)
{
pollingCancellationToken.Cancel();
ex.Redirect();
}
catch (Exception ex)
{
invalidOrder = true;
pollingCancellationToken.Cancel();
Console.Error.WriteLine(ex);
StateHasChanged();
}
}
}
void IDisposable.Dispose()
{
pollingCancellationToken?.Cancel();
}
}

View File

@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
namespace BlazingPizza.Client
{
public class PizzaAuthenticationState : RemoteAuthenticationState
{
public Order Order { get; set; }
}
}

View File

@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace BlazingPizza.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) });
builder.Services.AddHttpClient<OrdersClient>(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped<OrderState>();
// Add auth services
builder.Services.AddApiAuthorization<PizzaAuthenticationState>(options =>
{
options.AuthenticationPaths.LogOutSucceededPath = "";
});
await builder.Build().RunAsync();
}
}
}

View File

@ -0,0 +1,51 @@
<div class="form-field">
<label>Name:</label>
<div>
<InputText @bind-Value="Address.Name" />
<ValidationMessage For="@(() => Address.Name)" />
</div>
</div>
<div class="form-field">
<label>Adresse 1:</label>
<div>
<InputText @bind-Value="Address.Line1" />
<ValidationMessage For="@(() => Address.Line1)" />
</div>
</div>
<div class="form-field">
<label>Adresse 2:</label>
<div>
<InputText @bind-Value="Address.Line2" />
<ValidationMessage For="@(() => Address.Line2)" />
</div>
</div>
<div class="form-field">
<label>Stadt:</label>
<div>
<InputText @bind-Value="Address.City" />
<ValidationMessage For="@(() => Address.City)" />
</div>
</div>
<div class="form-field">
<label>Region:</label>
<div>
<InputText @bind-Value="Address.Region" />
<ValidationMessage For="@(() => Address.Region)" />
</div>
</div>
<div class="form-field">
<label>PLZ:</label>
<div>
<InputText @bind-Value="Address.PostalCode" />
<ValidationMessage For="@(() => Address.PostalCode)" />
</div>
</div>
@code {
[Parameter] public Address Address { get; set; }
}

View File

@ -0,0 +1,91 @@
@inject HttpClient HttpClient
<div class="dialog-title">
<h2>@Pizza.Special.Name</h2>
@Pizza.Special.Description
</div>
<form class="dialog-body">
<div>
<label>Größe:</label>
<input type="range" min="@Pizza.MinimumSize" max="@Pizza.MaximumSize" step="1" @bind="Pizza.Size" @bind:event="oninput" />
<span class="size-label">
@(Pizza.Size)" (€@(Pizza.GetFormattedTotalPrice()))
</span>
</div>
<div>
<label>Extra Beläge:</label>
@if (toppings == null)
{
<select class="custom-select" disabled>
<option>(Lade...)</option>
</select>
}
else if (Pizza.Toppings.Count >= 6)
{
<div>(maximale Anzahl erreicht)</div>
}
else
{
<select class="custom-select" @onchange="ToppingSelected">
<option value="-1" disabled selected>(select)</option>
@for (var i = 0; i < toppings.Count; i++)
{
<option value="@i">@toppings[i].Name - (€@(toppings[i].GetFormattedPrice()))</option>
}
</select>
}
</div>
<div class="toppings">
@foreach (var topping in Pizza.Toppings)
{
<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>
</div>
}
</div>
</form>
<div class="dialog-buttons">
<button class="btn btn-secondary mr-auto" @onclick="OnCancel">Abbrechen</button>
<span class="mr-center">
Kosten: <span class="price">@(Pizza.GetFormattedTotalPrice())</span>
</span>
<button class="btn btn-success ml-auto" @onclick="OnConfirm">Bestellen ></button>
</div>
@code {
List<Topping> toppings;
[Parameter] public Pizza Pizza { get; set; }
[Parameter] public EventCallback OnCancel { get; set; }
[Parameter] public EventCallback OnConfirm { get; set; }
protected async override Task OnInitializedAsync()
{
toppings = await HttpClient.GetFromJsonAsync<List<Topping>>("toppings");
}
void ToppingSelected(ChangeEventArgs e)
{
if (int.TryParse((string)e.Value, out var index) && index >= 0)
{
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);
}
}

View File

@ -0,0 +1,18 @@
<div class="cart-item">
<a @onclick="OnRemoved" class="delete-item">x</a>
<div class="title">@(Pizza.Size)" @Pizza.Special.Name</div>
<ul>
@foreach (var topping in Pizza.Toppings)
{
<li>+ @topping.Topping.Name</li>
}
</ul>
<div class="item-price">
@Pizza.GetFormattedTotalPrice()
</div>
</div>
@code {
[Parameter] public Pizza Pizza { get; set; }
[Parameter] public EventCallback OnRemoved { get; set; }
}

View File

@ -0,0 +1,29 @@
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
<div class="user-info">
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<img src="img/user.svg" />
<div>
<a href="authentication/profile" class="username">@context.User.Identity.Name</a>
<button class="btn btn-link sign-out" @onclick="BeginSignOut">Ausloggen</button>
</div>
</Authorized>
<NotAuthorized>
<a class="sign-in" href="authentication/register">Registrieren</a>
<a class="sign-in" href="authentication/login">Einloggen</a>
</NotAuthorized>
</AuthorizeView>
</div>
@code{
async Task BeginSignOut()
{
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
}
}

View File

@ -0,0 +1,30 @@
@inherits LayoutComponentBase
<div class="top-bar">
<a class="logo" href="">
<img src="img/logo.png" />
</a>
<NavLink href="" class="nav-tab" Match="NavLinkMatch.All">
<img src="img/pizza-slice.svg" />
<div>Pizza</div>
</NavLink>
<AuthorizeView>
<NavLink href="myorders" class="nav-tab">
<img src="img/bike.svg" />
<div>Meine Bestellungen</div>
</NavLink>
</AuthorizeView>
<NavLink href="/impressum" class="nav-tab" Match="NavLinkMatch.All">
<img src="img/pizza-slice.svg" />
<div>Impressum</div>
</NavLink>
<LoginDisplay />
</div>
<div class="content">
@Body
</div>

View File

@ -0,0 +1,28 @@
@foreach (var pizza in Order.Pizzas)
{
<p>
<strong>
@(pizza.Size)"
@pizza.Special.Name
(€@pizza.GetFormattedTotalPrice())
</strong>
</p>
<ul>
@foreach (var topping in pizza.Toppings)
{
<li>+ @topping.Topping.Name</li>
}
</ul>
}
<p>
<strong>
Gesamt:
€@Order.GetFormattedTotalPrice()
</strong>
</p>
@code {
[Parameter] public Order Order { get; set; }
}

View File

@ -0,0 +1,7 @@
@inject NavigationManager Navigation
@code {
protected override void OnInitialized()
{
Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}");
}
}

View File

@ -0,0 +1,15 @@
@using System.Net.Http
@using System.Net.Http.Headers
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.JSInterop
@using BlazingPizza.Client
@using BlazingPizza.Client.Shared
@using BlazingPizza.ComponentsLibrary
@using BlazingPizza.ComponentsLibrary.Map
@using BlazingComponents

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,40 @@
/* quicksand-300 - latin */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 300;
src: local('Quicksand Light'), local('Quicksand-Light'),
url('quicksand-v8-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('quicksand-v8-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* quicksand-regular - latin */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 400;
src: local('Quicksand Regular'), local('Quicksand-Regular'),
url('quicksand-v8-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('quicksand-v8-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* quicksand-500 - latin */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 500;
src: local('Quicksand Medium'), local('Quicksand-Medium'),
url('quicksand-v8-latin-500.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('quicksand-v8-latin-500.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* quicksand-700 - latin */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 700;
src: local('Quicksand Bold'), local('Quicksand-Bold'),
url('quicksand-v8-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('quicksand-v8-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}

View File

@ -0,0 +1,758 @@
@import url('font/quicksand.css');
body, html {
height: 100%;
}
body {
padding-top: 5rem;
flex-direction: column;
font-family: 'quicksand';
overflow-y: hidden;
}
form {
width: 100%;
}
.form-group.row > .col-form-label {
text-align: right;
}
.top-bar {
height: 5rem;
background-color: rgb(192,0,0);
background-image: linear-gradient(rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.25) 25%, rgba(0,0,0,0) 70%);
display: flex;
align-items: stretch;
color: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
padding: 0 3rem;
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 1030;
}
.logo {
display: flex;
}
.logo > img {
margin-right: 3rem;
width: 9rem;
}
.content {
display: flex;
height: 100%;
z-index: 1;
background-color: white;
}
.main {
flex-grow: 1;
overflow-y: auto;
background: linear-gradient(rgba(0,0,0,0) 40%, rgba(0,0,0,0.4) 80%);
padding: 1.5rem !important;
}
.nav-tab {
margin: 0;
padding: 0.3rem 1.8rem;
display: inline-block;
background-color: rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 0.9rem;
color: white;
position: relative;
text-transform: uppercase;
transition: 0.2s ease-out;
}
.nav-tab:hover {
color: white;
text-decoration: none;
background-color: rgba(255,255,255,0.3);
}
.nav-tab.active {
background-color: rgba(255,255,255,0.2);
color: #fff2cc;
}
.nav-tab img {
height: 2rem;
margin-bottom: 0.25rem;
}
.nav-tab.active img {
filter: brightness(0) saturate(100%) invert(93%) sepia(18%) saturate(797%) hue-rotate(316deg) brightness(109%) contrast(101%);
}
.nav-tab.active:after {
content: "";
position: absolute;
bottom: -1rem;
z-index: 1;
width: 0px;
height: 0px;
border-left: 0.6rem solid transparent;
border-right: 0.6rem solid transparent;
border-top: 1rem solid rgb(205,51,51);
}
.user-info {
margin-left: auto;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.user-info img {
margin-right: 0.7rem;
width: 2.6rem;
}
.user-info .username {
display: block;
font-weight: 700;
line-height: 0.7rem;
margin-top: 0.5rem;
color: white;
font-size: 1rem;
}
.user-info a {
color: #fff2cc;
font-size: 0.8rem;
}
.user-info button.sign-out {
color: #fff2cc;
font-size: 0.8rem;
padding: 0;
}
.pizza-cards {
display: grid;
grid-template-columns: repeat(auto-fill, 20rem);
grid-gap: 2rem;
justify-content: center;
padding-left: 0;
}
.pizza-cards > li {
height: 10rem;
position: relative;
background-size: cover;
border-radius: 0.5rem;
list-style-type: none;
box-shadow: 0 3px 4px rgba(0,0,0,0.4);
transition: 0.1s ease-out;
}
.pizza-cards > li:hover {
transform: scale(1.02);
}
.pizza-info {
border-radius: 0.5rem;
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
background: linear-gradient(rgba(0,0,0,0.7) 30%, rgba(0,0,0,0) 80%);
padding: 1rem 1rem;
color: #fff2cc;
cursor: pointer;
text-shadow: 0 2px 2px rgba(0,0,0,0.5);
line-height: 1.25rem;
}
.pizza-info .title {
color: white;
font-size: 1.4rem;
display: block;
margin: 0.2rem 0 0.4rem 0;
font-family: 'Bahnschrift', Arial, Helvetica, sans-serif;
text-transform: uppercase;
}
.pizza-info .price {
position: absolute;
bottom: 0.5rem;
right: 1rem;
font-size: 1.5rem;
font-weight: 700;
padding: 0rem 0.7rem;
border-radius: 4px;
background-color: #08af08;
color: white;
line-height: 2rem;
}
.price::before {
content: '€';
font-weight: 300;
font-size: 1.2rem;
margin-right: 0.2rem;
}
.sidebar {
background-color: #2b2b2b;
width: 20rem;
display: flex;
flex-direction: column;
color: white;
}
.order-contents {
overflow-y: auto;
padding: 2rem 1.5rem 1.5rem 1.5rem;
flex-grow: 1;
}
.order-contents h2 {
color: #fff2cc;
font-size: 1.3rem;
font-weight: 300;
margin-bottom: 1rem;
font-family: 'Bahnschrift', Arial, Helvetica, sans-serif;
text-transform: uppercase;
}
.order-total {
background-color: rgb(191, 52, 52);
height: 4rem;
flex-shrink: 0;
display: flex;
flex-direction: row;
align-items: center;
color: white;
font-size: 1.2rem;
transition: all 600ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
padding: 0 1.5rem;
}
.order-total.hidden {
transform: translate3d(0, 4rem, 0);
}
.order-total .total-price {
font-weight: 700;
font-size: 1.5rem;
}
.order-total .total-price::before {
content: '€';
font-weight: 300;
margin: 0 0.1rem 0 0.4rem;
}
.order-total .btn {
margin-left: auto;
font-weight: 700;
border-radius: 20px;
padding: 0.4rem 1.2rem;
}
.checkout-button {
margin: auto;
display: block;
font-weight: 700;
border-radius: 20px;
padding: 0.4rem 1.2rem;
}
.cart-item {
background-color: #333333;
padding: 0.8rem 1.2rem;
border-radius: 6px;
font-weight: 100;
margin-top: 1rem;
position: relative;
}
.cart-item .title {
font-weight: 700;
}
.cart-item ul {
padding: 0;
margin: 0.4rem 0.6rem;
}
.cart-item li {
list-style-type: none;
margin-left: 0rem;
font-size: 0.8rem;
}
.empty-cart {
text-align: center;
margin: auto;
font-size: 1.5rem;
font-weight: 100;
color: #676767;
}
.item-price {
font-weight: 500;
}
.item-price::before {
content: '€';
font-weight: 100;
margin-right: 0.3rem;
}
.delete-item {
position: absolute;
top: 0;
right: 0;
content: 'X';
cursor: pointer;
color: #fff2cc;
width: 2rem;
height: 2rem;
text-align: center;
}
.delete-item:hover {
text-decoration: none;
color: #fff2cc;
background-color: rgba(255,255,255,0.1);
}
.configured-pizza-item {
display: flex;
flex-direction: row;
}
.dialog-container {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0,0,0,0.5);
z-index: 2000;
display: flex;
animation: dialog-container-entry 0.2s;
}
@keyframes dialog-container-entry {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.dialog {
background-color: white;
box-shadow: 0 0 12px rgba(0,0,0,0.6);
display: flex;
flex-direction: column;
z-index: 2000;
align-self: center;
margin: auto;
width: 700px;
max-height: calc(100% - 3rem);
animation: dialog-entry 0.4s;
animation-timing-function: cubic-bezier(0.075, 0.820, 0.165, 1.000);
}
@keyframes dialog-entry {
0% {
transform: translateY(30px) scale(0.95);
}
100% {
transform: translateX(0px) scale(1.0);
}
}
.dialog-title {
background-color: #444;
color: #fff2cc;
padding: 1.3rem 2rem;
}
.dialog-title h2 {
color: white;
font-size: 1.4rem;
margin: 0;
font-family: 'Bahnschrift', Arial, Helvetica, sans-serif;
text-transform: uppercase;
line-height: 1.3rem;
}
.dialog-body {
flex-grow: 1;
padding: 0.5rem 3rem 1rem 0;
}
.dialog-buttons {
height: 4rem;
flex-shrink: 0;
display: flex;
align-items: center;
background-color: #eee;
padding: 0 1rem;
}
.dialog-body > div {
display: flex;
margin-top: 1rem;
align-items: center;
}
.dialog-body label {
text-align: right;
width: 200px;
margin: 0 1.5rem;
}
.dialog-body input, .dialog-body select {
flex-grow: 1;
width: unset;
}
.dialog-body .size-label {
min-width: 110px;
text-align: right;
}
.dialog .toppings {
text-align: center;
display: block;
padding-left: 4rem;
}
.dialog .topping {
display: inline-block;
background-color: #a04343;
color: white;
padding: 0.2rem 1rem;
border-radius: 2rem;
margin: 0.4rem 0.3rem;
font-weight: 700;
}
.dialog .topping-price {
font-weight: 100;
font-size: 0.8rem;
}
.dialog .topping-price::before {
content: '€';
}
.delete-topping {
background: none;
border: none;
color: white;
padding: 0.2rem 0.2rem 0.3rem 0.2rem;
cursor: pointer;
}
.delete-topping:hover {
color: yellow;
}
.form-message {
padding: 0.5rem;
font-weight: 700;
}
.dialog .price {
font-weight: 700;
font-size: 1.5rem;
}
.orders-list .list-group-item {
display: flex;
}
.orders-list .col {
margin: auto;
}
.orders-list h5 {
color: #c03939;
font-size: 1.3rem;
font-weight: 300;
margin: 0.2rem 0 0 0;
font-family: 'Bahnschrift', Arial, Helvetica, sans-serif;
text-transform: uppercase;
}
.track-order {
background-color: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.4);
height: 100%;
display: flex;
flex-direction: column;
}
.track-order > div {
overflow-y: hidden;
}
.track-order-title {
background-color: #eee;
display: flex;
align-items: center;
padding: 1rem 3rem;
}
.track-order-title h2 {
color: #c03939;
font-size: 1.3rem;
font-weight: 300;
margin: 0rem;
font-family: 'Bahnschrift', Arial, Helvetica, sans-serif;
text-transform: uppercase;
}
.track-order-body {
flex-grow: 1;
display: flex;
}
.track-order-details {
overflow-y: auto;
padding: 1.5rem 3rem;
flex-grow: 1;
}
.track-order-map {
width: 350px;
flex-shrink: 0;
}
a.sign-in {
background: none;
border: 1.5px solid white;
border-radius: 0.7em;
color: white;
text-transform: uppercase;
padding: 0.2rem 0.8rem 0.1rem 0.8rem;
font-family: 'Bahnschrift', Arial, Helvetica, sans-serif;
font-weight: 100;
cursor: pointer;
transition: 0.2s ease-out;
margin-left: 3px;
}
a.sign-in:hover {
background-color: rgba(255,255,255,0.3);
color: #fff2cc;
border-color: #fff2cc;
}
input[type=range] {
-webkit-appearance: none;
margin: 7.1px 0;
height: 21px;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 5.8px;
cursor: pointer;
box-shadow: 0px 0px 1px #000000, 0px 0px 0px #0d0d0d;
background: #dcdcdc;
border-radius: 1.3px;
border: 0px solid #010101;
}
input[type=range]::-webkit-slider-thumb {
box-shadow: 0.9px 0.9px 1px rgba(0, 0, 49, 0.43), 0px 0px 0.9px rgba(0, 0, 75, 0.43);
border: 0px solid #00001e;
height: 20px;
width: 20px;
border-radius: 10px;
background: #d45352;
cursor: pointer;
-webkit-appearance: none;
margin-top: -7.1px;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #e1e1e1;
}
input[type=range]::-moz-range-track {
width: 100%;
height: 5.8px;
cursor: pointer;
box-shadow: 0px 0px 1px #000000, 0px 0px 0px #0d0d0d;
background: #dcdcdc;
border-radius: 1.3px;
border: 0px solid #010101;
}
input[type=range]::-moz-range-thumb {
box-shadow: 0.9px 0.9px 1px rgba(0, 0, 49, 0.43), 0px 0px 0.9px rgba(0, 0, 75, 0.43);
border: 0px solid #00001e;
height: 20px;
width: 20px;
border-radius: 10px;
background: #d45352;
cursor: pointer;
}
input[type=range]::-ms-track {
width: 100%;
height: 5.8px;
cursor: pointer;
background: transparent;
border-color: transparent;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: #d7d7d7;
border: 0px solid #010101;
border-radius: 2.6px;
box-shadow: 0px 0px 1px #000000, 0px 0px 0px #0d0d0d;
}
input[type=range]::-ms-fill-upper {
background: #dcdcdc;
border: 0px solid #010101;
border-radius: 2.6px;
box-shadow: 0px 0px 1px #000000, 0px 0px 0px #0d0d0d;
}
input[type=range]::-ms-thumb {
box-shadow: 0.9px 0.9px 1px rgba(0, 0, 49, 0.43), 0px 0px 0.9px rgba(0, 0, 75, 0.43);
border: 0px solid #00001e;
height: 20px;
width: 20px;
border-radius: 10px;
background: #d45352;
cursor: pointer;
margin-top: 0;
}
.checkout-cols {
display: flex;
}
.checkout-cols h4 {
margin-bottom: 1.5rem;
}
.checkout-cols > div {
flex: 1;
margin: 1rem;
border: 1px solid #ddd;
background: rgba(255,255,255,0.3);
padding: 1.25rem 1rem;
}
.checkout-cols > div:first-child {
margin-left: 0;
}
.checkout-cols > div:last-child {
margin-right: 0;
}
.loading-bar {
position: absolute;
top: calc(50% - 3px);
left: calc(50% - 250px);
width: 500px;
height: 6px;
background-color: white;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.2)
}
.loading-bar::after {
content: '';
display: block;
width: 200px;
height: 100%;
background-color: #dc105a;
animation: progressbar-slide 1s infinite;
animation-timing-function: ease-in-out;
}
.form-field {
display: flex;
margin: 0.5rem;
}
.form-field > label {
width: 8rem;
}
.form-field > div {
flex-grow: 1;
}
.form-field input {
width: 100%;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}
.validation-message {
color: red;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
@keyframes progressbar-slide {
0% {
transform: translateX(-200px);
}
70% {
transform: translateX(500px);
}
100% {
transform: translateX(500px);
}
}

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="14.566216mm"
height="12.915195mm"
viewBox="0 0 14.566216 12.915195"
version="1.1"
id="svg6773"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="bike.svg">
<defs
id="defs6767" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-495.3304"
inkscape:cy="-464.16476"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="3018"
inkscape:window-height="1744"
inkscape:window-x="665"
inkscape:window-y="3070"
inkscape:window-maximized="1" />
<metadata
id="metadata6770">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-14.639502,-13.107878)">
<path
d="m 24.422045,14.741241 c 0,0.80786 -0.65263,1.4605 -1.4605,1.4605 -0.80786,0 -1.46402,-0.65264 -1.46402,-1.4605 0,-0.80786 0.65616,-1.46403 1.46402,-1.46403 0.80787,0 1.4605,0.65617 1.4605,1.46403 z"
style="fill:none;stroke:#ffffff;stroke-width:0.33866665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path4889"
inkscape:connector-curvature="0" />
<path
d="m 19.426715,23.546573 c 0,1.273528 -1.03364,2.307167 -2.30717,2.307167 -1.27352,0 -2.30716,-1.033639 -2.30716,-2.307167 0,-1.273528 1.03364,-2.307166 2.30716,-2.307166 1.27353,0 2.30717,1.033638 2.30717,2.307166 z"
style="fill:none;stroke:#ffffff;stroke-width:0.33866665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path4891"
inkscape:connector-curvature="0" />
<path
d="m 29.036385,23.546573 c 0,1.273528 -1.03364,2.307167 -2.30717,2.307167 -1.27353,0 -2.30717,-1.033639 -2.30717,-2.307167 0,-1.273528 1.03364,-2.307166 2.30717,-2.307166 1.27353,0 2.30717,1.033638 2.30717,2.307166 z"
style="fill:none;stroke:#ffffff;stroke-width:0.33866665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path4893"
inkscape:connector-curvature="0" />
<path
d="m 22.587605,19.711881 -1.04422,-0.10231 0.98072,-1.70039 2.23661,1.21709 c -0.0212,0.0212 -0.0212,0.0423 -0.0423,0.0635 l -1.7533,0.98425 v -0.0423 c 0,-0.20814 -0.16934,-0.39864 -0.37748,-0.4198 z m 4.13808,0.88194 c 0.39864,0 0.77611,0.0847 1.10772,0.22931 0.21167,-0.37747 0.35631,-0.81845 0.35631,-1.27706 0,-1.38289 -1.12889,-2.5153 -2.50825,-2.5153 -0.2928,0 -0.56444,0.0635 -0.83608,0.14816 0.10583,0.20814 0.1658,0.43745 0.18697,0.69145 l -2.82222,-1.55222 c -0.0423,-0.0212 -0.0811,-0.0423 -0.12347,-0.0423 -0.48331,-0.18697 -1.02659,-0.0212 -1.29823,0.44097 l -1.46402,2.51531 c -0.0176,0.0423 -0.0388,0.10231 -0.06,0.14464 l -4.45558,-0.45861 v 0.83608 l 3.13619,0.96308 c 1.21356,0.35631 2.09198,1.488727 2.09198,2.808115 0,0.296334 -0.0423,0.567973 -0.127,0.839612 h 4.01461 c -0.0811,-0.271639 -0.12347,-0.543278 -0.12347,-0.839612 0,-1.612194 1.31586,-2.931585 2.92452,-2.931585 z"
style="fill:none;stroke:#ffffff;stroke-width:0.33866665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path4895"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="12.864264mm"
height="12.890507mm"
viewBox="0 0 12.864264 12.890507"
version="1.1"
id="svg7666"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="pizza-slice.svg">
<defs
id="defs7660">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4885">
<path
d="M -3.9725e-5,540 H 960 V 1.2207e-4 L -4.3448e-5,6.1035e-5"
id="path4883"
inkscape:connector-curvature="0"
style="clip-rule:evenodd" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4875">
<path
d="M -1.9395e-5,540 H 960 V 1.2207e-4 L -2.3118e-5,6.1035e-5"
id="path4873"
inkscape:connector-curvature="0"
style="clip-rule:evenodd" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-538.54671"
inkscape:cy="-372.78284"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="3018"
inkscape:window-height="1744"
inkscape:window-x="665"
inkscape:window-y="3070"
inkscape:window-maximized="1" />
<metadata
id="metadata7663">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-26.073816,-37.310701)">
<g
id="g4869"
transform="matrix(0.35277777,0,0,-0.35277777,-82.464891,223.70045)"
style="stroke:#ffffff;stroke-opacity:1">
<g
id="g4871"
clip-path="url(#clipPath4875)"
style="stroke:#ffffff;stroke-opacity:1">
<path
d="m 331.92,516.06 c 0,0 -0.03,0.03 0,0 -0.03,0.03 -0.03,0.03 -0.03,0.03 -3.14,3.13 -6.77,5.57 -10.84,7.23 0,0 0,0 0,0 l -12.47,-30.6 30.61,12.47 c 0,0 0,0 0,0 -1.7,4.1 -4.13,7.73 -7.27,10.87 z m -10.3,-1.7 c -1.13,1.13 -1.13,2.93 -0.03,4.03 1.1,1.1 2.9,1.1 4.03,-0.03 1.13,-1.14 1.13,-2.94 0.03,-4.04 -1.1,-1.1 -2.9,-1.1 -4.03,0.04 z m -8.27,-14.94 2.3,5.63 c 0.5,-0.16 0.97,-0.36 1.37,-0.76 1.2,-1.2 1.2,-3.14 0,-4.34 -1,-1 -2.5,-1.16 -3.67,-0.53 z m 12.14,5.07 c -1.14,1.13 -1.14,2.93 -0.04,4.03 1.1,1.1 2.9,1.1 4.04,-0.03 1.13,-1.14 1.13,-2.94 0.03,-4.04 -1.1,-1.1 -2.9,-1.1 -4.03,0.04 z"
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path4877"
inkscape:connector-curvature="0" />
</g>
</g>
<g
id="g4879"
transform="matrix(0.35277777,0,0,-0.35277777,-82.464891,223.70045)"
style="stroke:#ffffff;stroke-opacity:1">
<g
id="g4881"
clip-path="url(#clipPath4885)"
style="stroke:#ffffff;stroke-opacity:1">
<path
d="m 343.52,506.99 c -1.93,4.67 -4.73,8.87 -8.3,12.43 -3.6,3.6 -7.77,6.37 -12.37,8.31 -0.73,0.33 -1.56,-0.04 -1.83,-0.77 -0.27,-0.73 0.07,-1.53 0.8,-1.87 4.23,-1.76 8.07,-4.33 11.4,-7.67 3.34,-3.33 5.9,-7.16 7.7,-11.5 0.07,-0.2 0.2,-0.33 0.34,-0.47 0.4,-0.4 1,-0.53 1.53,-0.33 0.7,0.3 1.03,1.17 0.73,1.87 z"
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path4887"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="15.113004mm"
height="15.113004mm"
viewBox="0 0 15.113004 15.113004"
version="1.1"
id="svg7709"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="user.svg">
<defs
id="defs7703" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-578.58286"
inkscape:cy="28.560012"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="3018"
inkscape:window-height="1744"
inkscape:window-x="665"
inkscape:window-y="3070"
inkscape:window-maximized="1" />
<metadata
id="metadata7706">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-98.276833,-141.27683)">
<path
d="m 98.806,148.83333 c 0,-3.88055 3.14678,-7.02733 7.02733,-7.02733 3.88056,0 7.02734,3.14678 7.02734,7.02733 0,3.88056 -3.14678,7.02734 -7.02734,7.02734 -3.88055,0 -7.02733,-3.14678 -7.02733,-7.02734 z"
style="fill:none;stroke:#ffffff;stroke-width:1.05833328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path4913"
inkscape:connector-curvature="0" />
<path
d="m 108.23222,147.28111 c 0,1.2947 -1.04775,2.34245 -2.34244,2.34245 -1.29117,0 -2.33892,-1.04775 -2.33892,-2.34245 0,-1.29117 1.04775,-2.33892 2.33892,-2.33892 1.29469,0 2.34244,1.04775 2.34244,2.33892 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.35277775"
id="path4915"
inkscape:connector-curvature="0" />
<path
d="m 110.51117,153.92392 0.0847,-1.66864 c 0,-0.35278 -0.17639,-0.70203 -0.46919,-0.93839 -0.64206,-0.52564 -1.4605,-0.87842 -2.27895,-1.11125 -0.58561,-0.17639 -1.22766,-0.29281 -1.92969,-0.29281 -0.64206,0 -1.28764,0.11642 -1.92969,0.29281 -0.81845,0.23283 -1.63689,0.64558 -2.27895,1.11125 -0.2928,0.23636 -0.46919,0.58561 -0.46919,0.93839 v 1.73214 c 1.46755,0.40569 0.98777,1.64747 4.70605,1.54869 4.36034,-0.25753 2.94217,-1.18533 4.56495,-1.61219 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.35277775"
id="path4917"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<base href="/" />
<link rel="icon" href="img/icon-512.png" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/site.css" rel="stylesheet" />
<link href="_content/BlazingPizza.ComponentsLibrary/leaflet/leaflet.css" rel="stylesheet" />
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<link rel="manifest" href="manifest.json" />
<title>Lodernd Pizza</title>
</head>
<body>
<app>
<div class="loading-bar"></div>
</app>
<div id="blazor-error-ui">
Ein fehler ist aufgetreten
<a href="" class="reload">Neu laden</a>
<a class="dismiss">🗙</a>
</div>
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
<script src="_content/BlazingPizza.ComponentsLibrary/localStorage.js"></script>
<script src="_content/BlazingPizza.ComponentsLibrary/pushNotifications.js"></script>
<script src="_content/BlazingPizza.ComponentsLibrary/deliveryMap.js"></script>
<script src="_content/BlazingPizza.ComponentsLibrary/leaflet/leaflet.js"></script>
<script>navigator.serviceWorker.register('service-worker.js');</script>
</body>
</html>

View File

@ -0,0 +1,16 @@
{
"short_name": "Blazing Pizza",
"name": "Blazing Pizza",
"icons": [
{
"src": "img/icon-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/",
"background_color": "#860000",
"display": "standalone",
"scope": "/",
"theme_color": "#860000"
}

View File

@ -0,0 +1,27 @@
self.addEventListener('install', async event => {
console.log('Installing service worker...');
self.skipWaiting();
});
self.addEventListener('fetch', event => {
// You can add custom logic here for controlling whether to use cached data if offline, etc.
// The following line opts out, so requests go directly to the network as usual.
return null;
});
self.addEventListener('push', event => {
const payload = event.data.json();
event.waitUntil(
self.registration.showNotification('Blazing Pizza', {
body: payload.message,
icon: 'img/icon-512.png',
vibrate: [100, 50, 100],
data: { url: payload.url }
})
);
});
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification.data.url));
});

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="$(AspNetCoreVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
using Microsoft.JSInterop;
using System.Threading.Tasks;
namespace BlazingPizza.ComponentsLibrary
{
public static class LocalStorage
{
public static ValueTask<T> GetAsync<T>(IJSRuntime jsRuntime, string key)
=> jsRuntime.InvokeAsync<T>("blazorLocalStorage.get", key);
public static ValueTask SetAsync(IJSRuntime jsRuntime, string key, object value)
=> jsRuntime.InvokeVoidAsync("blazorLocalStorage.set", key, value);
public static ValueTask DeleteAsync(IJSRuntime jsRuntime, string key)
=> jsRuntime.InvokeVoidAsync("blazorLocalStorage.delete", key);
}
}

View File

@ -0,0 +1,19 @@
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
<div id="@elementId" style="height: 100%; width: 100%;"></div>
@code {
string elementId = $"map-{Guid.NewGuid().ToString("D")}";
[Parameter] public double Zoom { get; set; }
[Parameter] public List<Marker> Markers { get; set; }
protected async override Task OnAfterRenderAsync(bool firstRender)
{
await JSRuntime.InvokeVoidAsync(
"deliveryMap.showOrUpdate",
elementId,
Markers);
}
}

View File

@ -0,0 +1,13 @@
namespace BlazingPizza.ComponentsLibrary.Map
{
public class Marker
{
public string Description { get; set; }
public double X { get; set; }
public double Y { get; set; }
public bool ShowPopup { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace BlazingPizza.ComponentsLibrary.Map
{
public class Point
{
public double X { get; set; }
public double Y { get; set; }
}
}

View File

@ -0,0 +1,79 @@
(function () {
var tileUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var tileAttribution = 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>';
// Global export
window.deliveryMap = {
showOrUpdate: function (elementId, markers) {
var elem = document.getElementById(elementId);
if (!elem) {
throw new Error('No element with ID ' + elementId);
}
// Initialize map if needed
if (!elem.map) {
elem.map = L.map(elementId);
elem.map.addedMarkers = [];
L.tileLayer(tileUrl, { attribution: tileAttribution }).addTo(elem.map);
}
var map = elem.map;
if (map.addedMarkers.length !== markers.length) {
// Markers have changed, so reset
map.addedMarkers.forEach(marker => marker.removeFrom(map));
map.addedMarkers = markers.map(m => {
return L.marker([m.y, m.x]).bindPopup(m.description).addTo(map);
});
// Auto-fit the view
var markersGroup = new L.featureGroup(map.addedMarkers);
map.fitBounds(markersGroup.getBounds().pad(0.3));
// Show applicable popups. Can't do this until after the view was auto-fitted.
markers.forEach((marker, index) => {
if (marker.showPopup) {
map.addedMarkers[index].openPopup();
}
});
} else {
// Same number of markers, so update positions/text without changing view bounds
markers.forEach((marker, index) => {
animateMarkerMove(
map.addedMarkers[index].setPopupContent(marker.description),
marker,
4000);
});
}
}
};
function animateMarkerMove(marker, coords, durationMs) {
if (marker.existingAnimation) {
cancelAnimationFrame(marker.existingAnimation.callbackHandle);
}
marker.existingAnimation = {
startTime: new Date(),
durationMs: durationMs,
startCoords: { x: marker.getLatLng().lng, y: marker.getLatLng().lat },
endCoords: coords,
callbackHandle: window.requestAnimationFrame(() => animateMarkerMoveFrame(marker))
};
}
function animateMarkerMoveFrame(marker) {
var anim = marker.existingAnimation;
var proportionCompleted = (new Date().valueOf() - anim.startTime.valueOf()) / anim.durationMs;
var coordsNow = {
x: anim.startCoords.x + (anim.endCoords.x - anim.startCoords.x) * proportionCompleted,
y: anim.startCoords.y + (anim.endCoords.y - anim.startCoords.y) * proportionCompleted
};
marker.setLatLng([coordsNow.y, coordsNow.x]);
if (proportionCompleted < 1) {
marker.existingAnimation.callbackHandle = window.requestAnimationFrame(
() => animateMarkerMoveFrame(marker));
}
}
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

View File

@ -0,0 +1,635 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg,
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile {
will-change: opacity;
}
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline: 0;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-container a.leaflet-active {
outline: 2px solid orange;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a,
.leaflet-bar a:hover {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path {
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.7);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover {
text-decoration: underline;
}
.leaflet-container .leaflet-control-attribution,
.leaflet-container .leaflet-control-scale {
font-size: 11px;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 19px;
line-height: 1.4;
}
.leaflet-popup-content p {
margin: 18px 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
padding: 4px 4px 0 0;
border: none;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
text-decoration: none;
font-weight: bold;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover {
color: #999;
}
.leaflet-popup-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-clickable {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
(function () {
window.blazorLocalStorage = {
get: key => key in localStorage ? JSON.parse(localStorage[key]) : null,
set: (key, value) => { localStorage[key] = JSON.stringify(value); },
delete: key => { delete localStorage[key]; }
};
})();

View File

@ -0,0 +1,46 @@
(function () {
// Note: Replace with your own key pair before deploying
const applicationServerPublicKey = 'BLC8GOevpcpjQiLkO7JmVClQjycvTCYWm6Cq_a7wJZlstGTVZvwGFFHMYfXt6Njyvgx_GlXJeo5cSiZ1y4JOx1o';
window.blazorPushNotifications = {
requestSubscription: async () => {
const worker = await navigator.serviceWorker.getRegistration();
const existingSubscription = await worker.pushManager.getSubscription();
if (!existingSubscription) {
const newSubscription = await subscribe(worker);
if (newSubscription) {
return {
url: newSubscription.endpoint,
p256dh: arrayBufferToBase64(newSubscription.getKey('p256dh')),
auth: arrayBufferToBase64(newSubscription.getKey('auth'))
};
}
}
}
};
async function subscribe(worker) {
try {
return await worker.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerPublicKey
});
} catch (error) {
if (error.name === 'NotAllowedError') {
return null;
}
throw error;
}
}
function arrayBufferToBase64(buffer) {
// https://stackoverflow.com/a/9458996
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
})();

View File

@ -0,0 +1,31 @@
@using Microsoft.AspNetCore.Identity
@using BlazingPizza.Server
@inject SignInManager<PizzaStoreUser> SignInManager
@inject UserManager<PizzaStoreUser> UserManager
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
var returnUrl = "/";
if (Context.Request.Query.TryGetValue("returnUrl", out var existingUrl))
{
returnUrl = existingUrl;
}
}
<div class="user-info">
@if (SignInManager.IsSignedIn(User))
{
<img src="~/img/user.svg" />
<div>
<a class="username" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">@User.Identity.Name</a>
<form asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/" method="post">
<button type="submit" class="btn btn-link sign-out">Ausloggen</button>
</form>
</div>
}
else
{
<a class="sign-in" asp-area="Identity" asp-page="/Account/Register" asp-route-returnUrl="@returnUrl">Registrieren</a>
<a class="sign-in" asp-area="Identity" asp-page="/Account/Login" asp-route-returnUrl="@returnUrl">Einloggen</a>
}
</div>

View File

@ -0,0 +1,38 @@
@using Microsoft.AspNetCore.Hosting
@using Microsoft.AspNetCore.Mvc.ViewEngines
@using BlazingPizza.Client.Shared
@inject IWebHostEnvironment Environment
@inject ICompositeViewEngine Engine
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="~/img/icon-512.png" rel="icon" />
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/site.css" rel="stylesheet" asp-append-version="true" />
<title>Lodern Pizza - @ViewData["Title"]</title>
</head>
<body>
<div class="top-bar">
<a class="logo" href="~/">
<img src="~/img/logo.png" />
</a>
<partial name="_LoginPartial" />
</div>
<div class="content">
<div class="main">
@RenderBody()
</div>
</div>
<script src="~/Identity/lib/jquery/dist/jquery.min.js"></script>
<script src="~/Identity/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/Identity/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="$(BlazorVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(EntityFrameworkVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="$(EntityFrameworkVersion)" />
<PackageReference Include="WebPush" Version="1.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BlazingPizza.Client\BlazingPizza.Client.csproj" />
<ProjectReference Include="..\BlazingPizza.Shared\BlazingPizza.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,43 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace BlazingPizza.Server
{
[Route("notifications")]
[ApiController]
[Authorize]
public class NotificationsController : Controller
{
private readonly PizzaStoreContext _db;
public NotificationsController(PizzaStoreContext db)
{
_db = db;
}
[HttpPut("subscribe")]
public async Task<NotificationSubscription> Subscribe(NotificationSubscription subscription)
{
// We're storing at most one subscription per user, so delete old ones.
// Alternatively, you could let the user register multiple subscriptions from different browsers/devices.
var userId = GetUserId();
var oldSubscriptions = _db.NotificationSubscriptions.Where(e => e.UserId == userId);
_db.NotificationSubscriptions.RemoveRange(oldSubscriptions);
// Store new subscription
subscription.UserId = userId;
_db.NotificationSubscriptions.Attach(subscription);
await _db.SaveChangesAsync();
return subscription;
}
private string GetUserId()
{
return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
}
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazingPizza.Server
{
public class OidcConfigurationController : Controller
{
public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider)
{
ClientRequestParametersProvider = clientRequestParametersProvider;
}
public IClientRequestParametersProvider ClientRequestParametersProvider { get; }
[HttpGet("_configuration/{clientId}")]
public IActionResult GetClientRequestParameters([FromRoute]string clientId)
{
var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);
return Ok(parameters);
}
}
}

View File

@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using WebPush;
namespace BlazingPizza.Server
{
[Route("orders")]
[ApiController]
[Authorize]
public class OrdersController : Controller
{
private readonly PizzaStoreContext _db;
public OrdersController(PizzaStoreContext db)
{
_db = db;
}
[HttpGet]
public async Task<ActionResult<List<OrderWithStatus>>> GetOrders()
{
var orders = await _db.Orders
.Where(o => o.UserId == GetUserId())
.Include(o => o.DeliveryLocation)
.Include(o => o.Pizzas).ThenInclude(p => p.Special)
.Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping)
.OrderByDescending(o => o.CreatedTime)
.ToListAsync();
return orders.Select(o => OrderWithStatus.FromOrder(o)).ToList();
}
[HttpGet("{orderId}")]
public async Task<ActionResult<OrderWithStatus>> GetOrderWithStatus(int orderId)
{
var order = await _db.Orders
.Where(o => o.OrderId == orderId)
.Where(o => o.UserId == GetUserId())
.Include(o => o.DeliveryLocation)
.Include(o => o.Pizzas).ThenInclude(p => p.Special)
.Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping)
.SingleOrDefaultAsync();
if (order == null)
{
return NotFound();
}
return OrderWithStatus.FromOrder(order);
}
[HttpPost]
public async Task<ActionResult<int>> PlaceOrder(Order order)
{
order.CreatedTime = DateTime.Now;
order.DeliveryLocation = new LatLong(49.1218574, 9.2117063);
order.UserId = GetUserId();
// Enforce existence of Pizza.SpecialId and Topping.ToppingId
// in the database - prevent the submitter from making up
// new specials and toppings
foreach (var pizza in order.Pizzas)
{
pizza.SpecialId = pizza.Special.Id;
pizza.Special = null;
foreach (var topping in pizza.Toppings)
{
topping.ToppingId = topping.Topping.Id;
topping.Topping = null;
}
}
_db.Orders.Attach(order);
await _db.SaveChangesAsync();
// In the background, send push notifications if possible
var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync();
if (subscription != null)
{
_ = TrackAndSendNotificationsAsync(order, subscription);
}
return order.OrderId;
}
private string GetUserId()
{
return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
}
private static async Task TrackAndSendNotificationsAsync(Order order, NotificationSubscription subscription)
{
// In a realistic case, some other backend process would track
// order delivery progress and send us notifications when it
// changes. Since we don't have any such process here, fake it.
await Task.Delay(OrderWithStatus.PreparationDuration);
await SendNotificationAsync(order, subscription, "Your order has been dispatched!");
await Task.Delay(OrderWithStatus.DeliveryDuration);
await SendNotificationAsync(order, subscription, "Your order is now delivered. Enjoy!");
}
private static async Task SendNotificationAsync(Order order, NotificationSubscription subscription, string message)
{
// For a real application, generate your own
var publicKey = "BLC8GOevpcpjQiLkO7JmVClQjycvTCYWm6Cq_a7wJZlstGTVZvwGFFHMYfXt6Njyvgx_GlXJeo5cSiZ1y4JOx1o";
var privateKey = "OrubzSz3yWACscZXjFQrrtDwCKg-TGFuWhluQ2wLXDo";
var pushSubscription = new PushSubscription(subscription.Url, subscription.P256dh, subscription.Auth);
var vapidDetails = new VapidDetails("mailto:<someone@example.com>", publicKey, privateKey);
var webPushClient = new WebPushClient();
try
{
var payload = JsonSerializer.Serialize(new
{
message,
url = $"myorders/{order.OrderId}",
});
await webPushClient.SendNotificationAsync(pushSubscription, payload, vapidDetails);
}
catch (Exception ex)
{
Console.Error.WriteLine("Error sending push notification: " + ex.Message);
}
}
}
}

View File

@ -0,0 +1,39 @@
using IdentityServer4.EntityFramework.Options;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace BlazingPizza.Server
{
public class PizzaStoreContext : ApiAuthorizationDbContext<PizzaStoreUser>
{
public PizzaStoreContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<Pizza> Pizzas { get; set; }
public DbSet<PizzaSpecial> Specials { get; set; }
public DbSet<Topping> Toppings { get; set; }
public DbSet<NotificationSubscription> NotificationSubscriptions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configuring a many-to-many special -> topping relationship that is friendly for serialization
modelBuilder.Entity<PizzaTopping>().HasKey(pst => new { pst.PizzaId, pst.ToppingId });
modelBuilder.Entity<PizzaTopping>().HasOne<Pizza>().WithMany(ps => ps.Toppings);
modelBuilder.Entity<PizzaTopping>().HasOne(pst => pst.Topping).WithMany();
// Inline the Lat-Long pairs in Order rather than having a FK to another table
modelBuilder.Entity<Order>().OwnsOne(o => o.DeliveryLocation);
}
}
}

View File

@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Identity;
namespace BlazingPizza.Server
{
public class PizzaStoreUser : IdentityUser
{
}
}

View File

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
namespace BlazingPizza.Server
{
[Route("pizzas")]
[ApiController]
public class PizzasController : Controller
{
private readonly PizzaStoreContext _db;
public PizzasController(PizzaStoreContext db)
{
_db = db;
}
}
}

View File

@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace BlazingPizza.Server
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
// Initialize the database
var scopeFactory = host.Services.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<PizzaStoreContext>();
if (db.Database.EnsureCreated())
{
SeedData.Initialize(db);
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@ -0,0 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:64588/",
"sslPort": 44381
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"BlazingPizza.Server": {
"commandName": "Project",
"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"
}
}
}
}

View File

@ -0,0 +1,70 @@
namespace BlazingPizza.Server
{
public static class SeedData
{
public static void Initialize(PizzaStoreContext db)
{
var toppings = new Topping[]
{
new Topping()
{
Name = "Ananas",
Price = 2.50m,
},
new Topping()
{
Name = "Artischocken",
Price = 1.50m,
},
new Topping()
{
Name = "Pilze",
Price = 1.00m,
}
};
var specials = new PizzaSpecial[]
{
new PizzaSpecial()
{
Name = "Pizza Margherita",
Description = "mit Edamer",
BasePrice = 9.99m,
ImageUrl = "img/pizzas/cheese.jpg",
},
new PizzaSpecial()
{
Name = "Pizza Salami",
Description = "mit Salami",
BasePrice = 8.00m,
ImageUrl = "img/pizzas/meaty.jpg",
},
new PizzaSpecial()
{
Name = "Pizza Prosciutto",
Description = "mit Schinken",
BasePrice = 8.00m,
ImageUrl = "img/pizzas/bacon.jpg",
},
new PizzaSpecial()
{
Name = "Pizza Funghi",
Description = "mit Pilzen",
BasePrice = 8.00m,
ImageUrl = "img/pizzas/mushroom.jpg",
},
new PizzaSpecial()
{
Name = "Pizza Gyros",
Description = "mit Gyros und frischem Blattspinat",
BasePrice = 8.50m,
ImageUrl = "img/pizzas/salad.jpg",
}
};
db.Toppings.AddRange(toppings);
db.Specials.AddRange(specials);
db.SaveChanges();
}
}
}

View File

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BlazingPizza.Server
{
[Route("specials")]
[ApiController]
public class SpecialsController : Controller
{
private readonly PizzaStoreContext _db;
public SpecialsController(PizzaStoreContext db)
{
_db = db;
}
[HttpGet]
public async Task<ActionResult<List<PizzaSpecial>>> GetSpecials()
{
return (await _db.Specials.ToListAsync()).OrderByDescending(s => s.BasePrice).ToList();
}
}
}

View File

@ -0,0 +1,70 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace BlazingPizza.Server
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddNewtonsoftJson();
services.AddDbContext<PizzaStoreContext>(options =>
options.UseSqlite("Data Source=pizza.db"));
services.AddDefaultIdentity<PizzaStoreUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<PizzaStoreContext>();
services.AddIdentityServer()
.AddApiAuthorization<PizzaStoreUser, PizzaStoreContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// 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.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapFallbackToFile("index.html");
});
}
}
}

View File

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BlazingPizza.Server
{
[Route("toppings")]
[ApiController]
public class ToppingsController : Controller
{
private readonly PizzaStoreContext _db;
public ToppingsController(PizzaStoreContext db)
{
_db = db;
}
[HttpGet]
public async Task<ActionResult<List<Topping>>> GetToppings()
{
return await _db.Toppings.OrderBy(t => t.Name).ToListAsync();
}
}
}

View File

@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"IdentityServer": {
"Key": {
"Type": "Development"
}
}
}

View File

@ -0,0 +1,19 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"IdentityServer": {
"Clients": {
"BlazingPizza.Client": {
"Profile": "IdentityServerSPA"
}
},
"Key": {
"Type":"Development"
}
}
}

View File

@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
namespace BlazingPizza
{
public class Address
{
public int Id { get; set; }
[Required, MaxLength(100)]
public string Name { get; set; }
[Required, MaxLength(100)]
public string Line1 { get; set; }
[MaxLength(100)]
public string Line2 { get; set; }
[Required, MaxLength(50)]
public string City { get; set; }
[Required, MaxLength(20)]
public string Region { get; set; }
[Required, MaxLength(20)]
public string PostalCode { get; set; }
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>7.3</LangVersion>
<RootNamespace>BlazingPizza</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BlazingPizza.ComponentsLibrary\BlazingPizza.ComponentsLibrary.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,27 @@
namespace BlazingPizza
{
public class LatLong
{
public LatLong()
{
}
public LatLong(double latitude, double longitude) : this()
{
Latitude = latitude;
Longitude = longitude;
}
public double Latitude { get; set; }
public double Longitude { get; set; }
public static LatLong Interpolate(LatLong start, LatLong end, double proportion)
{
// The Earth is flat, right? So no need for spherical interpolation.
return new LatLong(
start.Latitude + (end.Latitude - start.Latitude) * proportion,
start.Longitude + (end.Longitude - start.Longitude) * proportion);
}
}
}

View File

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
namespace BlazingPizza
{
public class NotificationSubscription
{
public int NotificationSubscriptionId { get; set; }
public string UserId { get; set; }
public string Url { get; set; }
public string P256dh { get; set; }
public string Auth { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace BlazingPizza
{
public class Order
{
public int OrderId { get; set; }
public string UserId { get; set; }
public DateTime CreatedTime { get; set; }
public Address DeliveryAddress { get; set; } = new Address();
public LatLong DeliveryLocation { get; set; }
public List<Pizza> Pizzas { get; set; } = new List<Pizza>();
public decimal GetTotalPrice() => Pizzas.Sum(p => p.GetTotalPrice());
public string GetFormattedTotalPrice() => GetTotalPrice().ToString("0.00");
}
}

View File

@ -0,0 +1,83 @@
using BlazingPizza.ComponentsLibrary.Map;
using System;
using System.Collections.Generic;
namespace BlazingPizza
{
public class OrderWithStatus
{
private static readonly string deliverdStatus = "Geliefert";
private static readonly string preparingStatus = "Vorbereiten";
private static readonly string deliveringStatus = "Wird geliefert";
public readonly static TimeSpan PreparationDuration = TimeSpan.FromSeconds(10);
public readonly static TimeSpan DeliveryDuration = TimeSpan.FromMinutes(1); // Unrealistic, but more interesting to watch
public Order Order { get; set; }
public string StatusText { get; set; }
public bool IsDelivered => StatusText == deliverdStatus;
public List<Marker> MapMarkers { get; set; }
public static OrderWithStatus FromOrder(Order order)
{
// To simulate a real backend process, we fake status updates based on the amount
// of time since the order was placed
string statusText;
List<Marker> mapMarkers;
var dispatchTime = order.CreatedTime.Add(PreparationDuration);
if (DateTime.Now < dispatchTime)
{
statusText = preparingStatus;
mapMarkers = new List<Marker>
{
ToMapMarker("Du", order.DeliveryLocation, showPopup: true)
};
}
else if (DateTime.Now < dispatchTime + DeliveryDuration)
{
statusText = deliveringStatus;
var startPosition = ComputeStartPosition(order);
var proportionOfDeliveryCompleted = Math.Min(1, (DateTime.Now - dispatchTime).TotalMilliseconds / DeliveryDuration.TotalMilliseconds);
var driverPosition = LatLong.Interpolate(startPosition, order.DeliveryLocation, proportionOfDeliveryCompleted);
mapMarkers = new List<Marker>
{
ToMapMarker("Du", order.DeliveryLocation),
ToMapMarker("Fahrer", driverPosition, showPopup: true),
};
}
else
{
statusText = deliverdStatus;
mapMarkers = new List<Marker>
{
ToMapMarker("Lieferaddresse", order.DeliveryLocation, showPopup: true),
};
}
return new OrderWithStatus
{
Order = order,
StatusText = statusText,
MapMarkers = mapMarkers,
};
}
private static LatLong ComputeStartPosition(Order order)
{
// Random but deterministic based on order ID
var rng = new Random(order.OrderId);
var distance = 0.01 + rng.NextDouble() * 0.02;
var angle = rng.NextDouble() * Math.PI * 2;
var offset = (distance * Math.Cos(angle), distance * Math.Sin(angle));
return new LatLong(order.DeliveryLocation.Latitude + offset.Item1, order.DeliveryLocation.Longitude + offset.Item2);
}
static Marker ToMapMarker(string description, LatLong coords, bool showPopup = false)
=> new Marker { Description = description, X = coords.Longitude, Y = coords.Latitude, ShowPopup = showPopup };
}
}

View File

@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Linq;
namespace BlazingPizza
{
/// <summary>
/// Represents a customized pizza as part of an order
/// </summary>
public class Pizza
{
public const int DefaultSize = 12;
public const int MinimumSize = 9;
public const int MaximumSize = 17;
public int Id { get; set; }
public int OrderId { get; set; }
public PizzaSpecial Special { get; set; }
public int SpecialId { get; set; }
public int Size { get; set; }
public List<PizzaTopping> Toppings { get; set; }
public decimal GetBasePrice()
{
return ((decimal)Size / (decimal)DefaultSize) * Special.BasePrice;
}
public decimal GetTotalPrice()
{
return GetBasePrice() + Toppings.Sum(t => t.Topping.Price);
}
public string GetFormattedTotalPrice()
{
return GetTotalPrice().ToString("0.00");
}
}
}

View File

@ -0,0 +1,20 @@
namespace BlazingPizza
{
/// <summary>
/// Represents a pre-configured template for a pizza a user can order
/// </summary>
public class PizzaSpecial
{
public int Id { get; set; }
public string Name { get; set; }
public decimal BasePrice { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public string GetFormattedBasePrice() => BasePrice.ToString("0.00");
}
}

View File

@ -0,0 +1,11 @@
namespace BlazingPizza
{
public class PizzaTopping
{
public Topping Topping { get; set; }
public int ToppingId { get; set; }
public int PizzaId { get; set; }
}
}

View File

@ -0,0 +1,14 @@
namespace BlazingPizza
{
public class Topping
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string GetFormattedPrice() => Price.ToString("0.00");
}
}

View File

@ -0,0 +1,9 @@
namespace BlazingPizza
{
public class UserInfo
{
public bool IsAuthenticated { get; set; }
public string Name { get; set; }
}
}

92
BlazingPizza.sln Normal file
View File

@ -0,0 +1,92 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28902.138
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.Server", "BlazingPizza.Server\BlazingPizza.Server.csproj", "{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.Client", "BlazingPizza.Client\BlazingPizza.Client.csproj", "{C33040F6-6F46-4AA2-8A8D-A97C934A0856}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.Shared", "BlazingPizza.Shared\BlazingPizza.Shared.csproj", "{42410116-06C1-4D19-B5FC-BD1F85F918B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.ComponentsLibrary", "BlazingPizza.ComponentsLibrary\BlazingPizza.ComponentsLibrary.csproj", "{0C421DC9-9F9A-4C97-9BC5-D959E8840864}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazingComponents", "BlazingComponents\BlazingComponents.csproj", "{4C4132E7-D096-42DA-9C3D-C444B42C3889}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x64.ActiveCfg = Debug|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x64.Build.0 = Debug|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x86.ActiveCfg = Debug|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x86.Build.0 = Debug|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|Any CPU.Build.0 = Release|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x64.ActiveCfg = Release|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x64.Build.0 = Release|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x86.ActiveCfg = Release|Any CPU
{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x86.Build.0 = Release|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x64.ActiveCfg = Debug|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x64.Build.0 = Debug|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x86.ActiveCfg = Debug|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x86.Build.0 = Debug|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|Any CPU.Build.0 = Release|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x64.ActiveCfg = Release|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x64.Build.0 = Release|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x86.ActiveCfg = Release|Any CPU
{C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x86.Build.0 = Release|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x64.ActiveCfg = Debug|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x64.Build.0 = Debug|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x86.ActiveCfg = Debug|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x86.Build.0 = Debug|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|Any CPU.Build.0 = Release|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x64.ActiveCfg = Release|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x64.Build.0 = Release|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x86.ActiveCfg = Release|Any CPU
{42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x86.Build.0 = Release|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x64.ActiveCfg = Debug|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x64.Build.0 = Debug|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x86.ActiveCfg = Debug|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x86.Build.0 = Debug|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|Any CPU.Build.0 = Release|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x64.ActiveCfg = Release|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x64.Build.0 = Release|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x86.ActiveCfg = Release|Any CPU
{0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x86.Build.0 = Release|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x64.ActiveCfg = Debug|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x64.Build.0 = Debug|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x86.ActiveCfg = Debug|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x86.Build.0 = Debug|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|Any CPU.Build.0 = Release|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x64.ActiveCfg = Release|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x64.Build.0 = Release|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x86.ActiveCfg = Release|Any CPU
{4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9574C5F7-86D1-4384-85AC-EA7D09B201AD}
EndGlobalSection
EndGlobal

8
Directory.Build.props Normal file
View File

@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<AspNetCoreVersion>3.1.4</AspNetCoreVersion>
<BlazorVersion>3.2.0</BlazorVersion>
<EntityFrameworkVersion>3.1.4</EntityFrameworkVersion>
<SystemNetHttpJsonVersion>3.2.0</SystemNetHttpJsonVersion>
</PropertyGroup>
</Project>

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /app
COPY . /app/
RUN dotnet publish -c Release -o out
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS runtime
WORKDIR /app
COPY --from=build /app/out ./
EXPOSE 80
ENTRYPOINT ["dotnet", "BlazingPizza.Server.dll"]

Some files were not shown because too many files have changed in this diff Show More