Refactor New/Edit Store modals/pages to Razor components
This commit is contained in:
parent
595a691da2
commit
74cb6109c9
13
Groceries/Components/Modal.razor
Normal file
13
Groceries/Components/Modal.razor
Normal file
@ -0,0 +1,13 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<turbo-frame id="modal" data-modal-target="frame">
|
||||
<article class="card">
|
||||
<header class="card__header row">
|
||||
<h2 class="row__fill">
|
||||
<SectionOutlet SectionName="modalTitle" />
|
||||
</h2>
|
||||
<button class="button modal__close-button" data-action="modal#close">🗙</button>
|
||||
</header>
|
||||
@Body
|
||||
</article>
|
||||
</turbo-frame>
|
@ -2,6 +2,42 @@ namespace Groceries;
|
||||
|
||||
public static class HttpRequestExtensions
|
||||
{
|
||||
public static Uri GetUri(this HttpRequest request)
|
||||
{
|
||||
var builder = new UriBuilder
|
||||
{
|
||||
Scheme = request.Scheme,
|
||||
Host = request.Host.Host,
|
||||
Port = request.Host.Port.GetValueOrDefault(-1),
|
||||
Path = request.Path.ToUriComponent(),
|
||||
Query = request.QueryString.ToUriComponent(),
|
||||
};
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
private static Uri GetOrigin(this HttpRequest request)
|
||||
{
|
||||
var builder = new UriBuilder
|
||||
{
|
||||
Scheme = request.Scheme,
|
||||
Host = request.Host.Host,
|
||||
Port = request.Host.Port.GetValueOrDefault(-1),
|
||||
};
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
private static bool IsSameOrigin(this HttpRequest request, Uri uri)
|
||||
{
|
||||
var origin = request.GetOrigin();
|
||||
return origin.IsBaseOf(uri);
|
||||
}
|
||||
|
||||
public static Uri? GetRefererIfSameOrigin(this HttpRequest request)
|
||||
{
|
||||
var referer = request.GetTypedHeaders().Referer;
|
||||
return referer != null && request.IsSameOrigin(referer) ? referer : null;
|
||||
}
|
||||
|
||||
public static bool IsTurboFrameRequest(this HttpRequest request, string frameId)
|
||||
{
|
||||
return request.Headers.TryGetValue("Turbo-Frame", out var values) && values.Contains(frameId);
|
||||
|
@ -1,36 +0,0 @@
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@using Microsoft.AspNetCore.Http.Extensions;
|
||||
|
||||
@model Groceries.Data.Store
|
||||
@{
|
||||
ViewBag.Title = "Edit Store";
|
||||
|
||||
var returnUrl = Url.Action("Index", new { page = 1 });
|
||||
if (Context.Request.GetTypedHeaders().Referer is Uri referer && referer.Host == Context.Request.Host.Host)
|
||||
{
|
||||
var requestUrl = new UriBuilder
|
||||
{
|
||||
Scheme = Context.Request.Scheme,
|
||||
Host = Context.Request.Host.Host,
|
||||
Port = Context.Request.Host.Port.GetValueOrDefault(-1),
|
||||
Path = Context.Request.Path.ToString(),
|
||||
Query = Context.Request.QueryString.ToString(),
|
||||
}.Uri;
|
||||
|
||||
if (referer != requestUrl)
|
||||
{
|
||||
returnUrl = referer.PathAndQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<h1>Edit Store</h1>
|
||||
|
||||
<form method="post" asp-action="EditStore">
|
||||
<partial name="_StoreForm" />
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit">Save</button>
|
||||
<a class="button" href="@returnUrl">Cancel</a>
|
||||
</div>
|
||||
</form>
|
17
Groceries/Stores/EditStoreModal.razor
Normal file
17
Groceries/Stores/EditStoreModal.razor
Normal file
@ -0,0 +1,17 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@layout Modal
|
||||
|
||||
<SectionContent SectionName="modalTitle">Edit Store</SectionContent>
|
||||
|
||||
<StoreForm Store="Store" class="card__content" id="editStore" data-action="turbo:submit-end->modal#close" />
|
||||
|
||||
<footer class="card__footer card__footer--shaded row">
|
||||
<button class="button button--primary" type="submit" form="editStore">Save</button>
|
||||
<button class="button" data-action="modal#close">Cancel</button>
|
||||
</footer>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Store Store { get; set; }
|
||||
}
|
32
Groceries/Stores/EditStorePage.razor
Normal file
32
Groceries/Stores/EditStorePage.razor
Normal file
@ -0,0 +1,32 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@layout Layout
|
||||
|
||||
@inject IHttpContextAccessor HttpContextAccessor
|
||||
|
||||
<PageTitle>Groceries – Edit Store</PageTitle>
|
||||
|
||||
<h1>Edit Store</h1>
|
||||
|
||||
<StoreForm Store="Store">
|
||||
<div class="row">
|
||||
<button class="button button--primary">Save</button>
|
||||
<a class="button" href="@returnUrl">Cancel</a>
|
||||
</div>
|
||||
</StoreForm>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Store Store { get; set; }
|
||||
|
||||
private string returnUrl = "/stores?page=1";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var request = HttpContextAccessor.HttpContext!.Request;
|
||||
if (request.GetRefererIfSameOrigin() is Uri referer && referer != request.GetUri())
|
||||
{
|
||||
returnUrl = referer.PathAndQuery;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
@model Groceries.Data.Store
|
||||
@{
|
||||
Layout = "_Modal";
|
||||
ViewBag.Title = "Edit Store";
|
||||
}
|
||||
|
||||
<form class="card__content" id="editStore" method="post" asp-action="EditStore" data-action="turbo:submit-end->modal#close">
|
||||
<partial name="_StoreForm" />
|
||||
</form>
|
||||
|
||||
<footer class="card__footer card__footer--shaded row">
|
||||
<button class="button button--primary" type="submit" form="editStore">Save</button>
|
||||
<button class="button" data-action="modal#close">Cancel</button>
|
||||
</footer>
|
@ -1,35 +0,0 @@
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@using Microsoft.AspNetCore.Http.Extensions;
|
||||
|
||||
@{
|
||||
ViewBag.Title = "New Store";
|
||||
|
||||
var returnUrl = Url.Action("Index", new { page = 1 });
|
||||
if (Context.Request.GetTypedHeaders().Referer is Uri referer && referer.Host == Context.Request.Host.Host)
|
||||
{
|
||||
var requestUrl = new UriBuilder
|
||||
{
|
||||
Scheme = Context.Request.Scheme,
|
||||
Host = Context.Request.Host.Host,
|
||||
Port = Context.Request.Host.Port.GetValueOrDefault(-1),
|
||||
Path = Context.Request.Path.ToString(),
|
||||
Query = Context.Request.QueryString.ToString(),
|
||||
}.Uri;
|
||||
|
||||
if (referer != requestUrl)
|
||||
{
|
||||
returnUrl = referer.PathAndQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<h1>New Store</h1>
|
||||
|
||||
<form method="post" asp-action="NewStore">
|
||||
<partial name="_StoreForm" />
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary">Save</button>
|
||||
<a class="button" href="@returnUrl">Cancel</a>
|
||||
</div>
|
||||
</form>
|
10
Groceries/Stores/NewStoreModal.razor
Normal file
10
Groceries/Stores/NewStoreModal.razor
Normal file
@ -0,0 +1,10 @@
|
||||
@layout Modal
|
||||
|
||||
<SectionContent SectionName="modalTitle">New Store</SectionContent>
|
||||
|
||||
<StoreForm class="card__content" id="newStore" data-action="turbo:submit-end->modal#close" />
|
||||
|
||||
<footer class="card__footer card__footer--shaded row">
|
||||
<button class="button button--primary" type="submit" form="newStore">Save</button>
|
||||
<button class="button" data-action="modal#close">Cancel</button>
|
||||
</footer>
|
27
Groceries/Stores/NewStorePage.razor
Normal file
27
Groceries/Stores/NewStorePage.razor
Normal file
@ -0,0 +1,27 @@
|
||||
@layout Layout
|
||||
|
||||
@inject IHttpContextAccessor HttpContextAccessor
|
||||
|
||||
<PageTitle>Groceries – New Store</PageTitle>
|
||||
|
||||
<h1>New Store</h1>
|
||||
|
||||
<StoreForm>
|
||||
<div class="row">
|
||||
<button class="button button--primary">Save</button>
|
||||
<a class="button" href="@returnUrl">Cancel</a>
|
||||
</div>
|
||||
</StoreForm>
|
||||
|
||||
@code {
|
||||
private string returnUrl = "/stores?page=1";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var request = HttpContextAccessor.HttpContext!.Request;
|
||||
if (request.GetRefererIfSameOrigin() is Uri referer && referer != request.GetUri())
|
||||
{
|
||||
returnUrl = referer.PathAndQuery;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
@{
|
||||
Layout = "_Modal";
|
||||
ViewBag.Title = "New Store";
|
||||
}
|
||||
|
||||
<form class="card__content" id="newStore" method="post" asp-action="NewStore" data-action="turbo:submit-end->modal#close">
|
||||
<partial name="_StoreForm" />
|
||||
</form>
|
||||
|
||||
<footer class="card__footer card__footer--shaded row">
|
||||
<button class="button button--primary" type="submit" form="newStore">Save</button>
|
||||
<button class="button" data-action="modal#close">Cancel</button>
|
||||
</footer>
|
52
Groceries/Stores/StoreForm.razor
Normal file
52
Groceries/Stores/StoreForm.razor
Normal file
@ -0,0 +1,52 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@inject AppDbContext DbContext
|
||||
|
||||
<form method="post" @attributes="AdditionalAttributes">
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="storeRetailerId">Retailer</label>
|
||||
<select class="form-field__control select" id="storeRetailerId" name="retailerId" required autofocus>
|
||||
@foreach (var retailer in retailers)
|
||||
{
|
||||
<option value="@retailer.Id" selected="@(retailer.Id == Store?.RetailerId)">@retailer.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="storeName">Name</label>
|
||||
<div class="form-field__control input">
|
||||
<input class="input__control" id="storeName" name="name" value="@Store?.Name" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="storeAddress">
|
||||
Address <span class="form-field__corner-hint">Optional</span>
|
||||
</label>
|
||||
<textarea class="form-field__control textarea" id="storeAddress" name="address" rows="4">@Store?.Address</textarea>
|
||||
</div>
|
||||
|
||||
@ChildContent
|
||||
</form>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public Store? Store { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
[Parameter(CaptureUnmatchedValues = true)]
|
||||
public Dictionary<string, object>? AdditionalAttributes { get; set; }
|
||||
|
||||
private Retailer[] retailers = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
retailers = await DbContext.Retailers
|
||||
.OrderBy(retailer => retailer.Name)
|
||||
.ToArrayAsync();
|
||||
}
|
||||
}
|
@ -22,15 +22,15 @@ public class StoresController : Controller
|
||||
}
|
||||
|
||||
[HttpGet("new")]
|
||||
public IActionResult NewStore()
|
||||
public IResult NewStore()
|
||||
{
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? View($"{nameof(NewStore)}_Modal")
|
||||
: View();
|
||||
? new RazorComponentResult<NewStoreModal>()
|
||||
: new RazorComponentResult<NewStorePage>();
|
||||
}
|
||||
|
||||
[HttpPost("new")]
|
||||
public async Task<IActionResult> NewStore(Guid retailerId, string name, string? address)
|
||||
public async Task<IResult> NewStore(Guid retailerId, string name, string? address)
|
||||
{
|
||||
var store = new Store(retailerId, name, address);
|
||||
dbContext.Stores.Add(store);
|
||||
@ -38,28 +38,28 @@ public class StoresController : Controller
|
||||
await dbContext.SaveChangesAsync(HttpContext.RequestAborted);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(Index), new { page = 1 });
|
||||
? Results.NoContent()
|
||||
: Results.LocalRedirect("/stores?page=1");
|
||||
}
|
||||
|
||||
[HttpGet("edit/{id}")]
|
||||
public async Task<IActionResult> EditStore(Guid id)
|
||||
public async Task<IResult> EditStore(Guid id)
|
||||
{
|
||||
var store = await dbContext.Stores
|
||||
.SingleOrDefaultAsync(store => store.Id == id, HttpContext.RequestAborted);
|
||||
|
||||
if (store == null)
|
||||
{
|
||||
return NotFound();
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? View($"{nameof(EditStore)}_Modal", store)
|
||||
: View(store);
|
||||
? new RazorComponentResult<EditStoreModal>(new { Store = store })
|
||||
: new RazorComponentResult<EditStorePage>(new { Store = store });
|
||||
}
|
||||
|
||||
[HttpPost("edit/{id}")]
|
||||
public async Task<IActionResult> EditStore(Guid id, Guid retailerId, string name, string? address)
|
||||
public async Task<IResult> EditStore(Guid id, Guid retailerId, string name, string? address, string? returnUrl)
|
||||
{
|
||||
var store = new Store(id, retailerId, name, address);
|
||||
dbContext.Stores.Update(store);
|
||||
@ -67,7 +67,7 @@ public class StoresController : Controller
|
||||
await dbContext.SaveChangesAsync(HttpContext.RequestAborted);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(EditStore));
|
||||
? Results.NoContent()
|
||||
: Results.LocalRedirect($"/stores/edit/{id}");
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@model Store?
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
var retailers = await dbContext.Retailers
|
||||
.OrderBy(retailer => retailer.Name)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="storeRetailerId">Retailer</label>
|
||||
<select class="form-field__control select" id="storeRetailerId" name="retailerId" required autofocus>
|
||||
@foreach (var retailer in retailers)
|
||||
{
|
||||
<option value="@retailer.Id" selected="@(retailer.Id == Model?.RetailerId)">@retailer.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="storeName">Name</label>
|
||||
<div class="form-field__control input">
|
||||
<input class="input__control" id="storeName" name="name" value="@Model?.Name" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="storeAddress">
|
||||
Address
|
||||
<span class="form-field__corner-hint">Optional</span>
|
||||
</label>
|
||||
<textarea class="form-field__control textarea" id="storeAddress" name="address" rows="4">@Model?.Address</textarea>
|
||||
</div>
|
@ -1,3 +1,4 @@
|
||||
@using Groceries.Components
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Sections
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
|
Loading…
x
Reference in New Issue
Block a user