everything related to blazor!
This commit is contained in:
parent
700a06e7e0
commit
211d3fdc03
@ -93,7 +93,7 @@ namespace Api.Controllers
|
||||
public async Task<IActionResult> AddComment(Guid id, AddCommentViewModel command)
|
||||
{
|
||||
var commentId = await _bus.Send(new CommentLinkCommand(id, command.Content));
|
||||
return CreatedAtAction("", "", new { id = commentId }, null);
|
||||
return Created($"comments/{commentId}", null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -114,7 +114,12 @@ namespace Api
|
||||
app.UseSwaggerUi3();
|
||||
}
|
||||
|
||||
app.UseCors(o => o.AllowAnyOrigin());
|
||||
app.UseCors(o =>
|
||||
{
|
||||
o.AllowAnyOrigin();
|
||||
o.AllowAnyMethod();
|
||||
o.WithHeaders("content-type", "authorization");
|
||||
});
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
<Router AppAssembly="@typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
<CascadingAuthenticationState>
|
||||
<Router AppAssembly="@typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
</CascadingAuthenticationState>
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="5.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
|
||||
91
Apps/Client/CustomAuthStateProvider.cs
Normal file
91
Apps/Client/CustomAuthStateProvider.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
namespace Client
|
||||
{
|
||||
public sealed class CustomAuthStateProvider : AuthenticationStateProvider
|
||||
{
|
||||
private static string _token;
|
||||
|
||||
private readonly HttpClient _http;
|
||||
|
||||
public CustomAuthStateProvider(HttpClient http)
|
||||
{
|
||||
_http = http;
|
||||
}
|
||||
|
||||
public override Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||
{
|
||||
return Task.FromResult(AuthenticationStateFromCurrentToken());
|
||||
}
|
||||
|
||||
public void MarkUserAsAuthenticated(string token)
|
||||
{
|
||||
_token = token;
|
||||
NotifyAuthenticationStateChanged(Task.FromResult(AuthenticationStateFromCurrentToken()));
|
||||
}
|
||||
|
||||
private AuthenticationState AuthenticationStateFromCurrentToken()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_token))
|
||||
{
|
||||
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
|
||||
}
|
||||
|
||||
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
|
||||
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(_token), "jwt", "unique_name", null));
|
||||
|
||||
return new AuthenticationState(principal);
|
||||
}
|
||||
|
||||
private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
|
||||
{
|
||||
var claims = new List<Claim>();
|
||||
var payload = jwt.Split('.')[1];
|
||||
var jsonBytes = ParseBase64WithoutPadding(payload);
|
||||
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
|
||||
|
||||
keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);
|
||||
|
||||
if (roles != null)
|
||||
{
|
||||
if (roles.ToString().Trim().StartsWith("["))
|
||||
{
|
||||
var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());
|
||||
|
||||
foreach (var parsedRole in parsedRoles)
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, parsedRole));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
|
||||
}
|
||||
|
||||
keyValuePairs.Remove(ClaimTypes.Role);
|
||||
}
|
||||
|
||||
claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
|
||||
return claims;
|
||||
}
|
||||
|
||||
private byte[] ParseBase64WithoutPadding(string base64)
|
||||
{
|
||||
switch (base64.Length % 4)
|
||||
{
|
||||
case 2: base64 += "=="; break;
|
||||
case 3: base64 += "="; break;
|
||||
}
|
||||
return Convert.FromBase64String(base64);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Apps/Client/NotificationManager.cs
Normal file
36
Apps/Client/NotificationManager.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Client
|
||||
{
|
||||
public sealed class NotificationManager
|
||||
{
|
||||
private readonly ILogger<NotificationManager> _logger;
|
||||
private Queue<string> _messages = new Queue<string>();
|
||||
|
||||
public IReadOnlyList<string> Messages => _messages.ToArray();
|
||||
|
||||
public event Action OnChange;
|
||||
|
||||
public NotificationManager(ILogger<NotificationManager> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Add(string message)
|
||||
{
|
||||
_messages.Enqueue(message);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
_messages.Dequeue();
|
||||
OnChange?.Invoke();
|
||||
});
|
||||
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,10 @@
|
||||
@inject MyState State
|
||||
|
||||
@foreach (var name in State.Names)
|
||||
|
||||
{
|
||||
<p>@name</p>
|
||||
<p>@name</p>
|
||||
|
||||
}
|
||||
|
||||
<Names />
|
||||
@ -12,11 +14,18 @@
|
||||
<button @onclick="OnSend">Save</button>
|
||||
|
||||
@code {
|
||||
|
||||
private string CurrentValue;
|
||||
|
||||
|
||||
|
||||
private void OnSend()
|
||||
|
||||
{
|
||||
|
||||
State.AddName(CurrentValue);
|
||||
|
||||
CurrentValue = "";
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
@page "/"
|
||||
@inject LinksClient Links
|
||||
|
||||
<h1>Latest links</h1>
|
||||
<Title Value="Latest links" />
|
||||
|
||||
@if (_loading)
|
||||
@if (_links == null)
|
||||
{
|
||||
<p>Loading...</p>
|
||||
}
|
||||
@ -20,20 +20,10 @@ else
|
||||
}
|
||||
|
||||
@code {
|
||||
private LinkDto[] _links = new LinkDto[] { };
|
||||
private bool _loading = false;
|
||||
private LinkDto[] _links;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_loading = true;
|
||||
|
||||
try
|
||||
{
|
||||
_links = (await Links.GetLinksAsync()).ToArray();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
_links = (await Links.GetLinksAsync()).ToArray();
|
||||
}
|
||||
}
|
||||
36
Apps/Client/Pages/Login.razor
Normal file
36
Apps/Client/Pages/Login.razor
Normal file
@ -0,0 +1,36 @@
|
||||
@page "/login"
|
||||
@inject AccountsClient Accounts
|
||||
@inject NotificationManager Notifications
|
||||
@inject NavigationManager Navigation
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<Title Value="Sign in!" />
|
||||
|
||||
<EditForm Model="@_model" OnValidSubmit="TryLogin">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<InputText @bind-Value="_model.Username" />
|
||||
<InputText type="password" @bind-Value="_model.Password" />
|
||||
|
||||
<button type="submit">Log in!</button>
|
||||
</EditForm>
|
||||
|
||||
@code
|
||||
{
|
||||
private LoginViewModel _model = new LoginViewModel();
|
||||
|
||||
private async Task TryLogin()
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = await Accounts.LoginAsync(_model);
|
||||
((CustomAuthStateProvider)AuthStateProvider).MarkUserAsAuthenticated(token);
|
||||
Navigation.NavigateTo("/");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Notifications.Add("login failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
@page "/links/{id:guid}"
|
||||
@inject LinksClient Links
|
||||
@inject NotificationManager Notifications
|
||||
|
||||
@if (Item == null)
|
||||
{
|
||||
@ -7,7 +8,29 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1>Showing link @Item.Url with @Item.UpVotes / @Item.DownVotes</h1>
|
||||
<h1>@Item.Url</h1>
|
||||
<div>
|
||||
👍 @Item.UpVotes
|
||||
👎 @Item.DownVotes
|
||||
</div>
|
||||
|
||||
@if (Comments == null)
|
||||
{
|
||||
<p>Loading comments ...</p>
|
||||
} else if(Comments.Count == 0)
|
||||
{
|
||||
<p>No comment yet!</p>
|
||||
} else
|
||||
{
|
||||
foreach (var comment in Comments)
|
||||
{
|
||||
<Comment @key="comment.Id" Item="@comment" />
|
||||
}
|
||||
}
|
||||
|
||||
<AuthorizeView>
|
||||
<CommentForm OnSubmit="@SubmitComment" Username="@context.User.Identity.Name" />
|
||||
</AuthorizeView>
|
||||
}
|
||||
|
||||
@code {
|
||||
@ -16,9 +39,30 @@ else
|
||||
|
||||
private LinkDto Item;
|
||||
|
||||
private ICollection<CommentDto> Comments;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Item = await Links.GetLinkByIdAsync(Id);
|
||||
await FetchComments();
|
||||
}
|
||||
|
||||
protected async Task FetchComments()
|
||||
{
|
||||
Comments = await Links.CommentsAsync(Id);
|
||||
}
|
||||
|
||||
private async Task SubmitComment(AddCommentViewModel command)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Links.AddCommentAsync(Id, command);
|
||||
await FetchComments();
|
||||
Notifications.Add("comment added!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Notifications.Add("could not post a comment");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
namespace Client
|
||||
{
|
||||
@ -27,9 +28,13 @@ namespace Client
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
|
||||
builder.Services.AddAuthorizationCore();
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("http://localhost:8888/") });
|
||||
builder.Services.AddScoped<LinksClient>();
|
||||
builder.Services.AddScoped<AccountsClient>();
|
||||
builder.Services.AddSingleton<MyState>();
|
||||
builder.Services.AddSingleton<NotificationManager>();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
}
|
||||
|
||||
7
Apps/Client/Shared/Comment.razor
Normal file
7
Apps/Client/Shared/Comment.razor
Normal file
@ -0,0 +1,7 @@
|
||||
<p>@Item.Content</p>
|
||||
<p>- @Item.CreatedByName</p>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public CommentDto Item { get; set; }
|
||||
}
|
||||
35
Apps/Client/Shared/CommentForm.razor
Normal file
35
Apps/Client/Shared/CommentForm.razor
Normal file
@ -0,0 +1,35 @@
|
||||
<EditForm Model="@_model" OnValidSubmit="@OnValidSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
|
||||
<InputText @bind-Value="_model.Content" />
|
||||
<ValidationMessage For=@(() => _model.Content) />
|
||||
|
||||
<button type="submit">Comment as @_username</button>
|
||||
</EditForm>
|
||||
|
||||
@code
|
||||
{
|
||||
private AddCommentViewModel _model = new AddCommentViewModel();
|
||||
|
||||
[Parameter]
|
||||
public string Username { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||
|
||||
private string _username;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<AddCommentViewModel> OnSubmit { get; set; }
|
||||
|
||||
protected async override Task OnInitializedAsync()
|
||||
{
|
||||
_username = (await authenticationStateTask).User.Identity.Name;
|
||||
}
|
||||
|
||||
private async Task OnValidSubmit()
|
||||
{
|
||||
await OnSubmit.InvokeAsync(_model);
|
||||
_model = new AddCommentViewModel();
|
||||
}
|
||||
}
|
||||
5
Apps/Client/Shared/EmptyLayout.razor
Normal file
5
Apps/Client/Shared/EmptyLayout.razor
Normal file
@ -0,0 +1,5 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<main>
|
||||
@Body
|
||||
</main>
|
||||
7
Apps/Client/Shared/Inside.razor
Normal file
7
Apps/Client/Shared/Inside.razor
Normal file
@ -0,0 +1,7 @@
|
||||
<p>Title was @MyValue</p>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
private string MyValue { get; set; }
|
||||
}
|
||||
@ -1,17 +1,22 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<NavMenu />
|
||||
<div class="topbar">
|
||||
<NavLink href="/">Home</NavLink>
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
Sign in as @context.User.Identity.Name
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
You're not signed in <NavLink href="/login">Sign in</NavLink>.
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="top-row px-4">
|
||||
<a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
|
||||
</div>
|
||||
<main>
|
||||
@Body
|
||||
</main>
|
||||
|
||||
<div class="content px-4">
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
||||
<Toasts />
|
||||
</div>
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
.top-row a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row:not(.auth) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.top-row.auth {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row a, .top-row .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.main > div {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
@inject MyState State
|
||||
@implements IDisposable
|
||||
@inject IJSRuntime JS;
|
||||
|
||||
<p>Got @State.Names.Count</p>
|
||||
|
||||
@ -10,6 +12,7 @@
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
JS.InvokeVoidAsync("alert", "Disposed");
|
||||
State.OnChange -= StateHasChanged;
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
|
||||
<div @onclick="ToggleNavMenu">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
.navbar-toggler {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.oi {
|
||||
width: 2rem;
|
||||
font-size: 1.1rem;
|
||||
vertical-align: text-top;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a {
|
||||
color: #d7d7d7;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.25);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep a:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
14
Apps/Client/Shared/Title.razor
Normal file
14
Apps/Client/Shared/Title.razor
Normal file
@ -0,0 +1,14 @@
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<h1>@Value</h1>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public string Value { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
JS.InvokeVoidAsync("setTitle", Value);
|
||||
}
|
||||
}
|
||||
3
Apps/Client/Shared/Title.razor.css
Normal file
3
Apps/Client/Shared/Title.razor.css
Normal file
@ -0,0 +1,3 @@
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
20
Apps/Client/Shared/Toasts.razor
Normal file
20
Apps/Client/Shared/Toasts.razor
Normal file
@ -0,0 +1,20 @@
|
||||
@inject NotificationManager Notifications
|
||||
@implements IDisposable
|
||||
|
||||
@foreach (var message in Notifications.Messages)
|
||||
{
|
||||
<p>⚡ @message</p>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Notifications.OnChange += StateHasChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Notifications.OnChange -= StateHasChanged;
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using Microsoft.JSInterop
|
||||
@using Client
|
||||
@using Client.Shared
|
||||
|
||||
@ -1,50 +1,35 @@
|
||||
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
|
||||
|
||||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
html,
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a, .btn-link {
|
||||
color: #0366d6;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
.valid.modified:not([type="checkbox"]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid red;
|
||||
outline: 1px solid red;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: red;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
@ -1,25 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<title>Client</title>
|
||||
<base href="/" />
|
||||
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="css/app.css" rel="stylesheet" />
|
||||
<link href="Client.styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div id="app">Loading...</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
window.setTitle = (title) => (document.title = title);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
10
README.md
10
README.md
@ -180,6 +180,16 @@ project.csproj
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
## Blazor
|
||||
|
||||
Ajout de l'authentification :
|
||||
|
||||
Ajout du paquet `Microsoft.AspNetCore.Components.Authorization` et du using dans `_Imports.razor`.
|
||||
|
||||
Ensuite, Passage par `AuthorizeRouteView` dans le fichier `App.razor`.
|
||||
|
||||
Ensuite, ajout d'un `AuthenticationStateProvider` custom pour déterminer si l'utilisateur est connecté ou non.
|
||||
|
||||
## Docker
|
||||
|
||||
On build à la racine de la solution avec `docker build -f .\Apps\Website\Dockerfile -t hn .`.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user