authentication refresh fixed, movie creation page added

This commit is contained in:
2024-07-30 16:19:51 +02:00
Unverified
parent f9323b3d8c
commit 5b871714fa
63 changed files with 1568 additions and 200 deletions

View File

@@ -1,14 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/>
<link rel="stylesheet" href="bootstrap/bootstrap.min.css"/>
<link rel="stylesheet" href="app.css?version=0.2"/>
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.2"/>
<link rel="icon" type="image/png" href="favicon.png"/>
<!-- CSS -->
<link rel="stylesheet" href="app.css?version=0.7"/>
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.7"/>
<!-- BOOTSTRAP -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<!-- FONTS -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Belanosima:wght@400;600;700&family=PT+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
<HeadOutlet @rendermode="InteractiveServer"/>
</head>

View File

@@ -0,0 +1,67 @@
<div class="row">
<div class="col-auto rounded-3 panel panel-regular m-1">
<img class="rounded-2 m-2 mt-3 shadow" src="@(_posterBase64 is not null ? $"data:{_posterMediaType};base64,{_posterBase64}" : "assets/poster.png")" alt="poster" width="300" height="500"/>
<br/>
<InputFile id="posterInput" class="m-2 form-control" OnChange="LoadPoster" disabled=@(!Id.HasValue) autocomplete="off"/>
@if (_posterChanged)
{
<button id="posterButton" type="button" class="btn btn-secondary m-2 mb-3 form-control" @onclick="SavePoster" disabled=@(!Id.HasValue) autocomplete="off">Save poster</button>
}
</div>
<div class="col rounded-3 panel panel-regular m-1 p-3">
<EditForm Model="Data" FormName="MediaInfo">
<AntiforgeryToken/>
<div class="form-group row my-1">
<label for="title" class="col-2 col-form-label">Title*</label>
<div class="col-10">
<InputText id="title" class="form-control" @bind-Value="Data!.Title"/>
</div>
</div>
<div class="form-group row my-1">
<label for="originalTitle" class="col-2 col-form-label">Original title</label>
<div class="col-10">
<InputText id="originalTitle" class="form-control" @bind-Value="Data!.OriginalTitle"/>
</div>
</div>
<div class="form-group row my-1">
<label for="description" class="col-2 col-form-label">Description</label>
<div class="col-10">
<InputTextArea id="description" class="form-control" rows="14" @bind-Value="Data!.Description"/>
</div>
</div>
<div class="form-group row my-1">
<label for="releaseDate" class="col-2 col-form-label">Release date</label>
<div class="col-10">
<InputDate TValue="DateOnly?" id="releaseDate" class="form-control" @bind-Value="Data!.ReleaseDate"/>
</div>
</div>
<div class="form-group row my-1">
<label for="length" class="col-2 col-form-label">Length</label>
<div class="col-10">
<InputNumber TValue="short?" id="length" class="form-control" @bind-Value="Data!.Length"/>
</div>
</div>
<div class="row my-1 mt-3">
<div class="col d-flex align-items-center">
@if (SaveDataErrors is not null && SaveDataErrors.Any())
{
<p class="text-danger m-0">@(SaveDataErrors.ElementAt(0))</p>
}
else if (!string.IsNullOrWhiteSpace(SaveDataInfo))
{
<p class="text-success m-0">@(SaveDataInfo)</p>
}
</div>
<div class="col-auto">
<button type="button" class="btn btn-secondary" @onclick="SaveDataAction">Save data</button>
</div>
</div>
</EditForm>
</div>
</div>
<style>
#posterInput, #posterButton {
width: 300px;
}
</style>

View File

@@ -0,0 +1,60 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using WatchIt.Common.Model.Media;
namespace WatchIt.Website.Components;
public partial class MediaForm : ComponentBase
{
#region PROPERTIES
[Parameter] public Media Data { get; set; }
[Parameter] public long? Id { get; set; }
[Parameter] public Func<Task> SaveDataAction { get; set; }
[Parameter] public IEnumerable<string>? SaveDataErrors { get; set; }
[Parameter] public string? SaveDataInfo { get; set; }
#endregion
#region FIELDS
private string? _actualPosterBase64 = null;
private string? _actualPosterMediaType = null;
private bool _posterChanged = false;
private string? _posterBase64 = null;
private string? _posterMediaType = null;
#endregion
#region PRIVATE METHODS
private async Task LoadPoster(InputFileChangeEventArgs args)
{
if (args.File.ContentType.StartsWith("image"))
{
Stream stream = args.File.OpenReadStream(5242880);
byte[] array;
using (MemoryStream ms = new MemoryStream())
{
await stream.CopyToAsync(ms);
array = ms.ToArray();
}
_posterMediaType = args.File.ContentType;
_posterBase64 = Convert.ToBase64String(array);
_posterChanged = true;
}
}
private async Task SavePoster()
{
throw new NotImplementedException();
}
#endregion
}

View File

@@ -0,0 +1,3 @@
#posterInput {
width: 300px;
}

View File

@@ -0,0 +1,7 @@
<div class="row">
<div class="col rounded-3 panel panel-regular m-1">
<div>
<h2 class="text-danger">Sorry. You have no permission to view this site.</h2>
</div>
</div>
</div>

View File

@@ -1,36 +1,47 @@
@using System.Diagnostics
@using System.Text
@using WatchIt.Common.Model.Media
@using WatchIt.Website.Services.WebAPI.Media
@inherits LayoutComponentBase
@inherits LayoutComponentBase
@if (loaded)
@if (_loaded)
{
<div class="container-xl">
<div class="row align-items-center m-2 rounded-3 header panel">
<div class="col-sm-4">
<div class="row align-items-center m-1 my-2 rounded-3 header panel panel-header z-3">
<div class="col-2">
<a class="logo" href="/">
WatchIt
</a>
</div>
<div class="col-sm-4">
<div class="col">
<p>Menu</p>
</div>
<div class="col-sm-4">
<div class="col-auto">
<div class="d-flex flex-row-reverse">
@if (signedIn)
@if (_user is null)
{
<p>test</p>
<a class="main-button" href="/auth">Sign in</a>
}
else
{
<a class="main-button" href="/auth">Sign in or up</a>
<div class="dropdown z-3">
<a class="dropdown-toggle align-items-center text-decoration-none d-flex" id="dropdownUser" aria-expanded="false" @onclick="() => _userMenuIsActive = !_userMenuIsActive">
<img class="rounded-circle" alt="avatar" height="30" src="@(_userProfilePicture)"/>
<div class="text-decoration-none mx-2 text-white">@(_user.Username)</div>
</a>
<ul class="dropdown-menu dropdown-menu-right text-small z-3" id="user-menu" aria-labelledby="dropdownUser">
<li>
@if (_user.IsAdmin)
{
<a class="dropdown-item" href="/admin">Administrator panel</a>
}
<div class="dropdown-menu-separator"></div>
<a class="dropdown-item text-danger" @onclick="UserMenuLogOut">Log out</a>
</li>
</ul>
</div>
}
</div>
</div>
</div>
<div class="row body-content">
<div class="col-sm-12">
<div class="row">
<div class="col z-0">
@Body
</div>
</div>
@@ -38,11 +49,16 @@
<style>
body {
background: url('@background') no-repeat center center fixed;
background: url('@_background') no-repeat center center fixed;
}
.logo, .main-button {
background-image: linear-gradient(45deg, @firstGradientColor, @secondGradientColor);
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
}
#user-menu {
display: @(_userMenuIsActive ? "block" : "none");
position: fixed;
}
</style>
}
@@ -55,6 +71,10 @@
#region SERVICES
[Inject] public ILogger<MainLayout> Logger { get; set; } = default!;
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
[Inject] public ITokensService TokensService { get; set; } = default!;
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
[Inject] public IAccountsWebAPIService AccountsWebAPIService { get; set; } = default!;
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
#endregion
@@ -63,12 +83,15 @@
#region FIELDS
private bool loaded = false;
private bool _loaded = false;
private string background = "assets/background_temp.jpg";
private string firstGradientColor = "#c6721c";
private string secondGradientColor = "#85200c";
private bool signedIn = false;
private string _background = "assets/background_temp.jpg";
private string _firstGradientColor = "#c6721c";
private string _secondGradientColor = "#85200c";
private User? _user = null;
private string _userProfilePicture = "assets/user_placeholder.png";
private bool _userMenuIsActive = false;
#endregion
@@ -76,7 +99,29 @@
#region METHODS
protected override async Task OnInitializedAsync()
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
List<Task> bgTasks = new List<Task>();
bgTasks.Add(GetBackground());
await GetAuthenticatedUser();
if (_user is not null)
{
bgTasks.Add(GetProfilePicture());
}
await Task.WhenAll(bgTasks);
_loaded = true;
StateHasChanged();
}
}
private async Task GetBackground()
{
Action<MediaPhotoResponse> backgroundSuccess = (data) =>
{
@@ -86,13 +131,33 @@
string secondColor = BitConverter.ToString(data.Background.SecondGradientColor)
.Replace("-", string.Empty);
background = $"data:{data.MimeType};base64,{imageBase64}";
firstGradientColor = $"#{firstColor}";
secondGradientColor = $"#{secondColor}";
_background = $"data:{data.MimeType};base64,{imageBase64}";
_firstGradientColor = $"#{firstColor}";
_secondGradientColor = $"#{secondColor}";
};
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess, null);
loaded = true;
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess);
}
private async Task GetAuthenticatedUser()
{
_user = await AuthenticationService.GetUserAsync();
}
private async Task GetProfilePicture()
{
Action<AccountProfilePictureResponse> successAction = (data) =>
{
string imageBase64 = Convert.ToBase64String(data.Image);
_userProfilePicture = $"data:{data.MimeType};base64,{imageBase64}";
};
await AccountsWebAPIService.GetAccountProfilePicture(_user.Id, successAction);
}
private async Task UserMenuLogOut()
{
await AuthenticationService.LogoutAsync();
await TokensService.RemoveAuthenticationData();
NavigationManager.Refresh(true);
}
#endregion

View File

@@ -0,0 +1,56 @@
@page "/admin"
<PageTitle>WatchIt administrator panel</PageTitle>
@if (_loaded)
{
<div class="container-fluid">
@if (_authenticated)
{
<div class="row">
<div class="col rounded-3 panel panel-regular m-1">
<h2>Add new data</h2>
</div>
</div>
<div class="row">
<a class="col rounded-3 panel panel-regular m-1" href="/movies/new">
<p class="text-center text-decorations-none">New movie</p>
</a>
<div class="col rounded-3 panel panel-regular m-1">
<p class="text-center">New TV series</p>
</div>
<div class="col rounded-3 panel panel-regular m-1">
<p class="text-center">New TV series</p>
</div>
</div>
}
else
{
<NoPermissionComponent/>
}
</div>
}
@code {
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
private bool _loaded = false;
private bool _authenticated = false;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
User? user = await AuthenticationService.GetUserAsync();
if (user is not null && user.IsAdmin)
{
_authenticated = true;
}
_loaded = true;
StateHasChanged();
}
}
}

View File

@@ -0,0 +1 @@

View File

@@ -1,4 +1,11 @@
@page "/auth"
@using System.Text
@using WatchIt.Common.Model.Accounts
@using WatchIt.Common.Model.Media
@using WatchIt.Website.Services.Utility.Authentication
@using WatchIt.Website.Services.Utility.Tokens
@using WatchIt.Website.Services.WebAPI.Accounts
@using WatchIt.Website.Services.WebAPI.Media
@layout EmptyLayout
<PageTitle>WatchIt - @(_authType == AuthType.SignIn ? "Sign in" : "Sign up")</PageTitle>
@@ -6,38 +13,129 @@
<div class="h-100 d-flex align-items-center justify-content-center">
<div class="d-inline-flex flex-column justify-content-center panel rounded-3">
<a class="logo" href="/">
WatchIt
</a>
@if (_authType == AuthType.SignIn)
{
}
@if (_loaded)
{
<div class="h-100 d-flex align-items-center justify-content-center">
<div class="d-inline-flex flex-column justify-content-center panel panel-header rounded-3">
<a class="logo" href="/">
WatchIt
</a>
<div>
@if (_authType == AuthType.SignIn)
{
<form method="post" @onsubmit="Login" @formname="login">
<AntiforgeryToken/>
<div>
<label>
Username or email:
<InputText @bind-Value="_loginModel!.UsernameOrEmail"/>
</label>
</div>
<div>
<label>
Password:
<InputText type="password" @bind-Value="_loginModel!.Password"/>
</label>
</div>
<div>
<label>
<InputCheckbox @bind-Value="_loginModel!.RememberMe"></InputCheckbox>
Remember me
</label>
</div>
<div>
<button type="submit">Sign in</button>
</div>
</form>
}
else
{
<form method="post" @onsubmit="Register" @formname="register">
<AntiforgeryToken/>
<div>
<label>
Username:
<InputText @bind-Value="_registerModel!.Username"/>
</label>
</div>
<div>
<label>
Email:
<InputText @bind-Value="_registerModel!.Email"/>
</label>
</div>
<div>
<label>
Password:
<InputText type="password" @bind-Value="_registerModel!.Password"/>
</label>
</div>
<div>
<label>
Confirm password:
<InputText type="password" @bind-Value="_passwordConfirmation"/>
</label>
</div>
<div>
<button type="submit">Sign up</button>
</div>
</form>
}
</div>
@if (_errors is not null)
{
<div class="text-danger">
@foreach (string error in _errors)
{
@error
<br/>
}
</div>
}
<div>
<label>
<input type="radio" checked="@(() => _authType == AuthType.SignIn)" name="auth" @onchange="@(() => _authType = AuthType.SignIn)" />
Sign in
</label>
<label>
<input type="radio" checked="@(() => _authType == AuthType.SignUp)" name="auth" @onchange="@(() => _authType = AuthType.SignUp)" />
Sign up
</label>
</div>
</div>
</div>
</div>
<style>
body {
background: url('@(_background)') no-repeat center center fixed;
}
.logo {
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
}
</style>
}
<style>
body {
background: url('@(_background)') no-repeat center center fixed;
}
@code
{
#region SERVICES
.logo {
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
}
</style>
[Inject] public ILogger<Auth> Logger { get; set; } = default!;
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
[Inject] public ITokensService TokensService { get; set; } = default!;
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
[Inject] public IAccountsWebAPIService AccountsWebAPIService { get; set; } = default!;
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
@code {
#endregion
#region ENUMS
@@ -52,21 +150,100 @@
#region FIELDS
private bool _loaded = false;
private AuthType _authType = AuthType.SignIn;
private string _background = "assets/background_temp.jpg";
private string _firstGradientColor = "#c6721c";
private string _secondGradientColor = "#85200c";
private AuthenticateRequest _loginModel = new AuthenticateRequest
{
UsernameOrEmail = null,
Password = null
};
private RegisterRequest _registerModel = new RegisterRequest
{
Username = null,
Email = null,
Password = null
};
private string _passwordConfirmation;
private IEnumerable<string> _errors;
#endregion
#region METHODS
protected override Task OnInitializedAsync()
protected override async Task OnInitializedAsync()
{
return base.OnInitializedAsync();
if (await AuthenticationService.GetAuthenticationStatusAsync())
{
NavigationManager.NavigateTo("/");
}
Action<MediaPhotoResponse> backgroundSuccess = (data) =>
{
string imageBase64 = Convert.ToBase64String(data.Image);
string firstColor = BitConverter.ToString(data.Background.FirstGradientColor)
.Replace("-", string.Empty);
string secondColor = BitConverter.ToString(data.Background.SecondGradientColor)
.Replace("-", string.Empty);
_background = $"data:{data.MimeType};base64,{imageBase64}";
_firstGradientColor = $"#{firstColor}";
_secondGradientColor = $"#{secondColor}";
};
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess);
_loaded = true;
}
private async Task Login()
{
await AccountsWebAPIService.Authenticate(_loginModel, LoginSuccess, LoginBadRequest, LoginUnauthorized);
async void LoginSuccess(AuthenticateResponse data)
{
await TokensService.SaveAuthenticationData(data);
NavigationManager.NavigateTo("/");
}
void LoginBadRequest(IDictionary<string, string[]> data)
{
_errors = data.SelectMany(x => x.Value).Select(x => $"• {x}");
}
void LoginUnauthorized()
{
_errors = [ "Incorrect account data" ];
}
}
private async Task Register()
{
if (_registerModel.Password != _passwordConfirmation)
{
_errors = [ "Password fields don't match" ];
return;
}
await AccountsWebAPIService.Register(_registerModel, RegisterSuccess, RegisterBadRequest);
void RegisterSuccess(RegisterResponse data)
{
_authType = AuthType.SignIn;
}
void RegisterBadRequest(IDictionary<string, string[]> data)
{
_errors = data.SelectMany(x => x.Value).Select(x => $"• {x}");
}
}
#endregion

View File

@@ -5,46 +5,50 @@
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
<h2>Hello, world!</h2>
<p>Welcome to your new app.</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,23 @@
@page "/movies/new"
@page "/movies/{id:long}/edit"
<PageTitle>WatchIt - New movie</PageTitle>
@if (_loaded)
{
<div class="container-fluid">
@if (_authenticated)
{
<div class="row">
<div class="col rounded-3 panel panel-regular m-1">
<h2>@(Id is null ? "Create new movie" : $"Edit movie \"{_movieInfo.Title}\"")</h2>
</div>
</div>
<MediaForm Data=@(_movieData) SaveDataAction=SaveData Id=@(Id) SaveDataErrors=@(_movieDataErrors) SaveDataInfo=@(_movieDataInfo)/>
}
else
{
<NoPermissionComponent/>
}
</div>
}

View File

@@ -0,0 +1,102 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Components;
using WatchIt.Common.Model.Movies;
using WatchIt.Website.Services.Utility.Authentication;
using WatchIt.Website.Services.WebAPI.Movies;
namespace WatchIt.Website.Pages;
public partial class MovieEditPage : ComponentBase
{
#region SERVICES
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
[Inject] public IMoviesWebAPIService MoviesWebAPIService { get; set; } = default!;
#endregion
#region PARAMETERS
[Parameter]
public long? Id { get; set; }
#endregion
#region FIELDS
private bool _loaded = false;
private bool _authenticated = false;
private MovieResponse? _movieInfo = null;
private MovieRequest _movieData = new MovieRequest { Title = string.Empty };
private IEnumerable<string>? _movieDataErrors = null;
private string? _movieDataInfo = null;
#endregion
#region PRIVATE METHODS
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
User? user = await AuthenticationService.GetUserAsync();
if (user is not null && user.IsAdmin)
{
_authenticated = true;
await LoadData();
}
_loaded = true;
StateHasChanged();
}
}
private async Task LoadData()
{
if (Id is not null)
{
await MoviesWebAPIService.Get(Id.Value, GetSuccessAction, NoIdAction);
}
return;
void GetSuccessAction(MovieResponse data)
{
_movieInfo = data;
_movieData = new MovieRequest(_movieInfo);
}
void NoIdAction() => NavigationManager.NavigateTo("/movies/new", true); // await for all
}
private async Task SaveData()
{
_movieDataErrors = null;
_movieDataInfo = null;
if (Id is null)
{
await MoviesWebAPIService.Post(_movieData, PostSuccessAction, BadRequestAction, NoPermissionsAction, NoPermissionsAction);
}
else
{
await MoviesWebAPIService.Put(Id.Value, _movieData, PutSuccessAction, BadRequestAction, NoPermissionsAction, NoPermissionsAction);
}
return;
void PutSuccessAction() => _movieDataInfo = "Data saved";
void PostSuccessAction(MovieResponse data) => NavigationManager.NavigateTo($"/movies/{data.Id}/edit", true);
void BadRequestAction(IDictionary<string, string[]> errors) => _movieDataErrors = errors.SelectMany(x => x.Value);
void NoPermissionsAction() => NavigationManager.Refresh(true);
}
#endregion
}

View File

@@ -0,0 +1 @@

View File

@@ -1,7 +1,13 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.Authorization;
using WatchIt.Common.Services.HttpClient;
using WatchIt.Website.Services.Utility.Authentication;
using WatchIt.Website.Services.Utility.Configuration;
using WatchIt.Website.Services.Utility.Tokens;
using WatchIt.Website.Services.WebAPI.Accounts;
using WatchIt.Website.Services.WebAPI.Media;
using WatchIt.Website.Services.WebAPI.Movies;
namespace WatchIt.Website;
@@ -13,6 +19,7 @@ public static class Program
{
WebApplication app = WebApplication.CreateBuilder(args)
.SetupServices()
.SetupAuthentication()
.SetupApplication()
.Build();
@@ -40,18 +47,29 @@ public static class Program
#region PRIVATE METHODS
private static WebApplicationBuilder SetupServices(this WebApplicationBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddSingleton<HttpClient>();
// Utility
builder.Services.AddSingleton<IHttpClientService, HttpClientService>();
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
builder.Services.AddScoped<ITokensService, TokensService>();
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
// WebAPI
builder.Services.AddSingleton<IAccountsWebAPIService, AccountsWebAPIService>();
builder.Services.AddScoped<IAccountsWebAPIService, AccountsWebAPIService>();
builder.Services.AddSingleton<IMediaWebAPIService, MediaWebAPIService>();
builder.Services.AddSingleton<IMoviesWebAPIService, MoviesWebAPIService>();
return builder;
}
private static WebApplicationBuilder SetupAuthentication(this WebApplicationBuilder builder)
{
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, JWTAuthenticationStateProvider>();
return builder;
}

View File

@@ -15,9 +15,13 @@
<ItemGroup>
<ProjectReference Include="..\..\WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj" />
<ProjectReference Include="..\..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Tokens\WatchIt.WebAPI.Services.Utility.Tokens.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Authentication\WatchIt.Website.Services.Utility.Authentication.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Tokens\WatchIt.Website.Services.Utility.Tokens.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Accounts\WatchIt.Website.Services.WebAPI.Accounts.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Media\WatchIt.Website.Services.WebAPI.Media.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Movies\WatchIt.Website.Services.WebAPI.Movies.csproj" />
</ItemGroup>
<ItemGroup>
@@ -27,6 +31,8 @@
<_ContentIncludedByDefault Remove="Components\Pages\Error.razor" />
<_ContentIncludedByDefault Remove="Components\Pages\Home.razor" />
<_ContentIncludedByDefault Remove="Components\Pages\Weather.razor" />
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css.map" />
</ItemGroup>
<ItemGroup>
@@ -34,9 +40,4 @@
<AdditionalFiles Include="Pages\Home.razor" />
</ItemGroup>
<ItemGroup>
<Folder Include="Components\" />
<Folder Include="wwwroot\assets\" />
</ItemGroup>
</Project>

View File

@@ -7,4 +7,11 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using WatchIt.Website
@using WatchIt.Website.Layout
@using WatchIt.Website.Layout
@using WatchIt.Website.Components
@using WatchIt.Common.Model.Accounts
@using WatchIt.Common.Model.Media
@using WatchIt.Website.Services.Utility.Tokens
@using WatchIt.Website.Services.Utility.Authentication
@using WatchIt.Website.Services.WebAPI.Accounts
@using WatchIt.Website.Services.WebAPI.Media

View File

@@ -6,13 +6,19 @@
}
},
"AllowedHosts": "*",
"StorageKeys": {
"AccessToken": "access_token",
"RefreshToken": "refresh_token"
},
"Endpoints": {
"Base": "https://localhost:7160",
"Accounts": {
"Base": "/accounts",
"Register": "/register",
"Authenticate": "/authenticate",
"AuthenticateRefresh": "/authenticate-refresh"
"AuthenticateRefresh": "/authenticate-refresh",
"Logout": "/logout",
"GetProfilePicture": "/{0}/profile-picture"
},
"Genres": {
"Base": "/genres",
@@ -28,10 +34,7 @@
"Get": "/{0}",
"Post": "",
"Put": "/{0}",
"Delete": "/{0}",
"GetGenres": "/{0}/genres",
"PostGenre": "{0}/genres/{1}",
"DeleteGenre": "{0}/genres/{1}"
"Delete": "/{0}"
},
"Media": {
"Base": "/media",

View File

@@ -1,33 +1,31 @@
@font-face {
font-family: Belanosima;
src: url(fonts/Belanosima-Regular.ttf) format('truetype');
}
@font-face {
font-family: Belanosima;
src: url(fonts/Belanosima-Bold.ttf) format('truetype');
font-weight: bold;
}
body, html {
background-color: transparent;
height: 100%;
margin: 0;
padding: 0;
color: lightgray;
font-family: "PT Sans";
}
.logo {
font-family: Belanosima;
font-family: "Belanosima";
text-decoration: none;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.panel {
.panel-header {
background-color: rgba(0, 0, 0, 0.8);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.panel-regular {
background-color: rgba(0, 0, 0, 0.6);
}
.panel {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(25px);
z-index: 1000;
}
.main-button {
@@ -77,4 +75,9 @@ body, html {
.main-button:hover::before {
-webkit-mask:none;
}
.dropdown-menu-left {
right: auto;
left: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long