initial commit
435
.gitignore
vendored
Normal 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
|
13
BlazingComponents/BlazingComponents.csproj
Normal 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>
|
13
BlazingComponents/TemplatedDialog.razor
Normal 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; }
|
||||||
|
}
|
36
BlazingComponents/TemplatedList.razor
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
1
BlazingComponents/_Imports.razor
Normal file
@ -0,0 +1 @@
|
|||||||
|
@using Microsoft.AspNetCore.Components.Web
|
19
BlazingPizza.Client/App.razor
Normal 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>
|
23
BlazingPizza.Client/BlazingPizza.Client.csproj
Normal 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>
|
13
BlazingPizza.Client/JSRuntimeExtensions.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
BlazingPizza.Client/OrderState.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
BlazingPizza.Client/OrdersClient.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
BlazingPizza.Client/Pages/Authentication.razor
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
BlazingPizza.Client/Pages/Checkout.razor
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
BlazingPizza.Client/Pages/Impressum.razor
Normal 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>
|
74
BlazingPizza.Client/Pages/Index.razor
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
BlazingPizza.Client/Pages/MyOrders.razor
Normal 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 >
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
}
|
95
BlazingPizza.Client/Pages/OrderDetails.razor
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
9
BlazingPizza.Client/PizzaAuthenticationState.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
|
||||||
|
|
||||||
|
namespace BlazingPizza.Client
|
||||||
|
{
|
||||||
|
public class PizzaAuthenticationState : RemoteAuthenticationState
|
||||||
|
{
|
||||||
|
public Order Order { get; set; }
|
||||||
|
}
|
||||||
|
}
|
31
BlazingPizza.Client/Program.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
BlazingPizza.Client/Shared/AddressEditor.razor
Normal 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; }
|
||||||
|
}
|
91
BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
18
BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor
Normal 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; }
|
||||||
|
}
|
29
BlazingPizza.Client/Shared/LoginDisplay.razor
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
30
BlazingPizza.Client/Shared/MainLayout.razor
Normal 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>
|
28
BlazingPizza.Client/Shared/OrderReview.razor
Normal 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; }
|
||||||
|
}
|
7
BlazingPizza.Client/Shared/RedirectToLogin.razor
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
@inject NavigationManager Navigation
|
||||||
|
@code {
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}");
|
||||||
|
}
|
||||||
|
}
|
15
BlazingPizza.Client/_Imports.razor
Normal 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
|
7
BlazingPizza.Client/wwwroot/css/bootstrap/bootstrap.min.css
vendored
Normal file
BIN
BlazingPizza.Client/wwwroot/css/font/quicksand-v8-latin-300.woff
Normal file
BIN
BlazingPizza.Client/wwwroot/css/font/quicksand-v8-latin-500.woff
Normal file
BIN
BlazingPizza.Client/wwwroot/css/font/quicksand-v8-latin-700.woff
Normal file
40
BlazingPizza.Client/wwwroot/css/font/quicksand.css
Normal 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+ */
|
||||||
|
}
|
||||||
|
|
758
BlazingPizza.Client/wwwroot/css/site.css
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
77
BlazingPizza.Client/wwwroot/img/bike.svg
Normal 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 |
BIN
BlazingPizza.Client/wwwroot/img/icon-512.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
BlazingPizza.Client/wwwroot/img/logo.png
Normal file
After Width: | Height: | Size: 12 KiB |
117
BlazingPizza.Client/wwwroot/img/logo.svg
Normal file
After Width: | Height: | Size: 43 KiB |
106
BlazingPizza.Client/wwwroot/img/pizza-slice.svg
Normal 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 |
BIN
BlazingPizza.Client/wwwroot/img/pizzas/bacon.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
BlazingPizza.Client/wwwroot/img/pizzas/cheese.jpg
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
BlazingPizza.Client/wwwroot/img/pizzas/margherita.jpg
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
BlazingPizza.Client/wwwroot/img/pizzas/meaty.jpg
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
BlazingPizza.Client/wwwroot/img/pizzas/mushroom.jpg
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
BlazingPizza.Client/wwwroot/img/pizzas/pepperoni.jpg
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
BlazingPizza.Client/wwwroot/img/pizzas/salad.jpg
Normal file
After Width: | Height: | Size: 64 KiB |
72
BlazingPizza.Client/wwwroot/img/user.svg
Normal 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 |
34
BlazingPizza.Client/wwwroot/index.html
Normal 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>
|
16
BlazingPizza.Client/wwwroot/manifest.json
Normal 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"
|
||||||
|
}
|
27
BlazingPizza.Client/wwwroot/service-worker.js
Normal 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));
|
||||||
|
});
|
@ -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>
|
17
BlazingPizza.ComponentsLibrary/LocalStorage.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
19
BlazingPizza.ComponentsLibrary/Map/Map.razor
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
13
BlazingPizza.ComponentsLibrary/Map/Marker.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
9
BlazingPizza.ComponentsLibrary/Map/Point.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace BlazingPizza.ComponentsLibrary.Map
|
||||||
|
{
|
||||||
|
public class Point
|
||||||
|
{
|
||||||
|
public double X { get; set; }
|
||||||
|
|
||||||
|
public double Y { get; set; }
|
||||||
|
}
|
||||||
|
}
|
79
BlazingPizza.ComponentsLibrary/wwwroot/deliveryMap.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
(function () {
|
||||||
|
var tileUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||||
|
var tileAttribution = 'Map data © <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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
After Width: | Height: | Size: 1.2 KiB |
BIN
BlazingPizza.ComponentsLibrary/wwwroot/leaflet/images/layers.png
Normal file
After Width: | Height: | Size: 696 B |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 618 B |
635
BlazingPizza.ComponentsLibrary/wwwroot/leaflet/leaflet.css
Normal 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;
|
||||||
|
}
|
7
BlazingPizza.ComponentsLibrary/wwwroot/localStorage.js
Normal 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]; }
|
||||||
|
};
|
||||||
|
})();
|
46
BlazingPizza.ComponentsLibrary/wwwroot/pushNotifications.js
Normal 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);
|
||||||
|
}
|
||||||
|
})();
|
@ -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>
|
38
BlazingPizza.Server/Areas/Identity/Pages/_Layout.cshtml
Normal 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>
|
25
BlazingPizza.Server/BlazingPizza.Server.csproj
Normal 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>
|
43
BlazingPizza.Server/NotificationsController.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
BlazingPizza.Server/OidcConfigurationController.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
135
BlazingPizza.Server/OrdersController.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
BlazingPizza.Server/PizzaStoreContext.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
BlazingPizza.Server/PizzaStoreUser.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace BlazingPizza.Server
|
||||||
|
{
|
||||||
|
public class PizzaStoreUser : IdentityUser
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
16
BlazingPizza.Server/PizzasController.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
BlazingPizza.Server/Program.cs
Normal 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>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
29
BlazingPizza.Server/Properties/launchSettings.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
BlazingPizza.Server/SeedData.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
BlazingPizza.Server/SpecialsController.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
BlazingPizza.Server/Startup.cs
Normal 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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
BlazingPizza.Server/ToppingsController.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
BlazingPizza.Server/appsettings.Development.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Debug",
|
||||||
|
"System": "Information",
|
||||||
|
"Microsoft": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IdentityServer": {
|
||||||
|
"Key": {
|
||||||
|
"Type": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
BlazingPizza.Server/appsettings.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Debug",
|
||||||
|
"System": "Information",
|
||||||
|
"Microsoft": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IdentityServer": {
|
||||||
|
"Clients": {
|
||||||
|
"BlazingPizza.Client": {
|
||||||
|
"Profile": "IdentityServerSPA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Key": {
|
||||||
|
"Type":"Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
BlazingPizza.Shared/Address.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
13
BlazingPizza.Shared/BlazingPizza.Shared.csproj
Normal 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>
|
27
BlazingPizza.Shared/LatLong.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
BlazingPizza.Shared/NotificationSubscription.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
25
BlazingPizza.Shared/Order.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
83
BlazingPizza.Shared/OrderWithStatus.cs
Normal 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 };
|
||||||
|
}
|
||||||
|
}
|
42
BlazingPizza.Shared/Pizza.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
BlazingPizza.Shared/PizzaSpecial.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
11
BlazingPizza.Shared/PizzaTopping.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
14
BlazingPizza.Shared/Topping.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
9
BlazingPizza.Shared/UserInfo.cs
Normal 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
@ -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
@ -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
@ -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"]
|