authentication refresh fixed, movie creation page added
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
67
WatchIt.Website/WatchIt.Website/Components/MediaForm.razor
Normal file
67
WatchIt.Website/WatchIt.Website/Components/MediaForm.razor
Normal 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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#posterInput {
|
||||
width: 300px;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
56
WatchIt.Website/WatchIt.Website/Pages/Admin.razor
Normal file
56
WatchIt.Website/WatchIt.Website/Pages/Admin.razor
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
WatchIt.Website/WatchIt.Website/Pages/Admin.razor.css
Normal file
1
WatchIt.Website/WatchIt.Website/Pages/Admin.razor.css
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
23
WatchIt.Website/WatchIt.Website/Pages/MovieEditPage.razor
Normal file
23
WatchIt.Website/WatchIt.Website/Pages/MovieEditPage.razor
Normal 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>
|
||||
}
|
||||
102
WatchIt.Website/WatchIt.Website/Pages/MovieEditPage.razor.cs
Normal file
102
WatchIt.Website/WatchIt.Website/Pages/MovieEditPage.razor.cs
Normal 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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/poster.png
Normal file
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/poster.png
Normal file
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
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user