профиль компании и пользователя + редактрование (работает не все)
This commit is contained in:
parent
3ccdcf3e76
commit
5528e3db15
@ -17,15 +17,15 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
|
||||
_logger = logger;
|
||||
_сompanyStorage = сompanyStorage;
|
||||
}
|
||||
public bool Create(CompanyBindingModel model)
|
||||
public int Create(CompanyBindingModel model)
|
||||
{
|
||||
CheckModel(model);
|
||||
if (_сompanyStorage.Insert(model) == null)
|
||||
{
|
||||
_logger.LogWarning("Insert operation failed");
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
return true;
|
||||
return model.Id;
|
||||
}
|
||||
|
||||
public bool Delete(CompanyBindingModel model)
|
||||
|
@ -5,6 +5,8 @@ using CandidateReviewContracts.ViewModels;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using CandidateReviewContracts.StoragesContracts;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace CandidateReviewBusinessLogic.BusinessLogic
|
||||
{
|
||||
@ -17,9 +19,17 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
|
||||
_logger = logger;
|
||||
_userStorage = userStorage;
|
||||
}
|
||||
|
||||
private string EncryptPassword(string password)
|
||||
{
|
||||
byte[] hashedBytes = SHA256.HashData(Encoding.UTF8.GetBytes(password));
|
||||
return Convert.ToBase64String(hashedBytes);
|
||||
}
|
||||
public bool Create(UserBindingModel model)
|
||||
{
|
||||
CheckModel(model);
|
||||
CheckPassword(model);
|
||||
model.Password = EncryptPassword(model.Password);
|
||||
if (_userStorage.Insert(model) == null)
|
||||
{
|
||||
_logger.LogWarning("Insert operation failed");
|
||||
@ -31,6 +41,7 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
|
||||
public bool Delete(UserBindingModel model)
|
||||
{
|
||||
CheckModel(model, false);
|
||||
CheckPassword(model);
|
||||
_logger.LogInformation("Delete. Id: {Id}", model.Id);
|
||||
if (_userStorage.Delete(model) == null)
|
||||
{
|
||||
@ -47,14 +58,29 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
}
|
||||
var element = _userStorage.GetElement(model);
|
||||
if (element != null)
|
||||
{
|
||||
string hashedPassword = element.Password;
|
||||
if (element != null && model.Password != element.Password && model.Password != null)
|
||||
{
|
||||
hashedPassword = EncryptPassword(model.Password);
|
||||
}
|
||||
if (element == null)
|
||||
{
|
||||
_logger.LogWarning("ReadElement element not found");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (element.Password == hashedPassword)
|
||||
{
|
||||
_logger.LogInformation("ReadElement find. Id: {Id}", element.Id);
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<UserViewModel>? ReadList(UserSearchModel? model)
|
||||
{
|
||||
@ -71,6 +97,19 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
|
||||
public bool Update(UserBindingModel model)
|
||||
{
|
||||
CheckModel(model);
|
||||
var elem = _userStorage.GetElement(new UserSearchModel
|
||||
{
|
||||
Id = model.Id
|
||||
});
|
||||
if (elem != null && model.Password != elem.Password)
|
||||
{
|
||||
if (!Regex.IsMatch(model.Password, @"^^((\w+\d+\W+)|(\w+\W+\d+)|(\d+\w+\W+)|(\d+\W+\w+)|(\W+\w+\d+)|(\W+\d+\w+))[\w\d\W]*$", RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
throw new ArgumentException("Неправильно введенный пароль", nameof(model.Password));
|
||||
}
|
||||
model.Password = EncryptPassword(model.Password);
|
||||
}
|
||||
if (_userStorage.Update(model) == null)
|
||||
{
|
||||
_logger.LogWarning("Update operation failed");
|
||||
@ -101,19 +140,9 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
|
||||
throw new ArgumentNullException("Нет имени пользователя", nameof(model.Name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(model.Password))
|
||||
{
|
||||
throw new ArgumentNullException("Нет пароля пользователя", nameof(model.LastName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(model.Email))
|
||||
{
|
||||
throw new ArgumentNullException("Нет почты пользователя", nameof(model.LastName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(model.Password))
|
||||
{
|
||||
throw new ArgumentNullException("Нет пароля пользователя", nameof(model.LastName));
|
||||
throw new ArgumentNullException("Нет почты пользователя", nameof(model.Email));
|
||||
}
|
||||
|
||||
if (!Regex.IsMatch(model.Email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.IgnoreCase))
|
||||
@ -121,11 +150,6 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
|
||||
throw new ArgumentException("Неправильно введенный email", nameof(model.Email));
|
||||
}
|
||||
|
||||
if (!Regex.IsMatch(model.Password, @"^^((\w+\d+\W+)|(\w+\W+\d+)|(\d+\w+\W+)|(\d+\W+\w+)|(\W+\w+\d+)|(\W+\d+\w+))[\w\d\W]*$", RegexOptions.IgnoreCase))
|
||||
{
|
||||
throw new ArgumentException("Неправильно введенный пароль", nameof(model.Password));
|
||||
}
|
||||
|
||||
var element = _userStorage.GetElement(new UserSearchModel
|
||||
{
|
||||
Email = model.Email
|
||||
@ -136,5 +160,18 @@ namespace CandidateReviewBusinessLogic.BusinessLogic
|
||||
throw new InvalidOperationException("Пользователь с такой почтой уже есть");
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckPassword(UserBindingModel model)
|
||||
{
|
||||
if (string.IsNullOrEmpty(model.Password))
|
||||
{
|
||||
throw new ArgumentNullException("Нет пароля пользователя", nameof(model.Password));
|
||||
}
|
||||
|
||||
if (!Regex.IsMatch(model.Password, @"^^((\w+\d+\W+)|(\w+\W+\d+)|(\d+\w+\W+)|(\d+\W+\w+)|(\W+\w+\d+)|(\W+\d+\w+))[\w\d\W]*$", RegexOptions.IgnoreCase))
|
||||
{
|
||||
throw new ArgumentException("Неправильно введенный пароль", nameof(model.Password));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ namespace CandidateReviewClientApp
|
||||
{
|
||||
private static readonly HttpClient _user = new();
|
||||
public static UserViewModel? User { get; set; } = null;
|
||||
public static CompanyViewModel? Company { get; set; } = null;
|
||||
public static void Connect(IConfiguration configuration)
|
||||
{
|
||||
_user.BaseAddress = new Uri(configuration["IPAddress"]);
|
||||
|
79
CandidateReviewClientApp/Controllers/CompanyController.cs
Normal file
79
CandidateReviewClientApp/Controllers/CompanyController.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using CandidateReviewContracts.BindingModels;
|
||||
using CandidateReviewContracts.ViewModels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CandidateReviewClientApp.Controllers
|
||||
{
|
||||
public class CompanyController : Controller
|
||||
{
|
||||
private readonly ILogger<CompanyController> _logger;
|
||||
public CompanyController(ILogger<CompanyController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult CompanyProfile(int? id)
|
||||
{
|
||||
if (APIClient.User == null)
|
||||
{
|
||||
return Redirect("~/Home/Enter");
|
||||
}
|
||||
if (id.HasValue)
|
||||
{
|
||||
APIClient.Company = APIClient.GetRequest<CompanyViewModel?>($"api/company/profile?id={id}");
|
||||
}
|
||||
var model = APIClient.Company;
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult EditCompanyProfile(int? id)
|
||||
{
|
||||
if (APIClient.User == null)
|
||||
{
|
||||
return Redirect("~/Home/Enter");
|
||||
}
|
||||
if (!id.HasValue)
|
||||
{
|
||||
return View(new CompanyViewModel());
|
||||
}
|
||||
var model = APIClient.GetRequest<CompanyViewModel?>($"api/company/profile?id={id}");
|
||||
if (model != null)
|
||||
{
|
||||
APIClient.PostRequest($"api/user/update", new UserBindingModel { Id = APIClient.User.Id, CompanyId = model.Id });
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void EditCompanyProfile(CompanyBindingModel model)
|
||||
{
|
||||
if (APIClient.User == null)
|
||||
{
|
||||
throw new Exception("Доступно только авторизованным пользователям");
|
||||
}
|
||||
if (model.Id != 0)
|
||||
{
|
||||
APIClient.PostRequest("api/company/update", model);
|
||||
}
|
||||
else
|
||||
{
|
||||
APIClient.PostRequest("api/company/create", model);
|
||||
}
|
||||
Response.Redirect("/Home/Index");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void Delete(int id)
|
||||
{
|
||||
if (APIClient.User == null)
|
||||
{
|
||||
throw new Exception("Доступно только авторизованным пользователям");
|
||||
}
|
||||
|
||||
APIClient.PostRequest($"api/company/delete", new CompanyBindingModel { Id = id });
|
||||
Response.Redirect("/Home/Index");
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ using CandidateReviewClientApp;
|
||||
using CandidateReviewClientApp.Models;
|
||||
using CandidateReviewContracts.BindingModels;
|
||||
using CandidateReviewContracts.ViewModels;
|
||||
using CandidateReviewDataModels.Enums;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Diagnostics;
|
||||
|
||||
@ -44,7 +45,7 @@ namespace CandidateReviewUserApp.Controllers
|
||||
{
|
||||
throw new Exception("Íåâåðíûé ëîãèí/ïàðîëü");
|
||||
}
|
||||
Response.Redirect("Index");
|
||||
Response.Redirect("/Home/Index");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -52,6 +53,7 @@ namespace CandidateReviewUserApp.Controllers
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void Register(string login, string password, string surname, string name, string lastname)
|
||||
{
|
||||
@ -59,6 +61,18 @@ namespace CandidateReviewUserApp.Controllers
|
||||
{
|
||||
throw new Exception("Ââåäèòå ëîãèí, ïàðîëü è ÔÈÎ");
|
||||
}
|
||||
|
||||
RoleEnum role = RoleEnum.Íåèçâåñòåí;
|
||||
|
||||
if (login.Equals("tania.art03@gmail.com", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
role = RoleEnum.Ñîòðóäíèê;
|
||||
}
|
||||
else if (login.Equals("t.artamonova73@icloud.com", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
role = RoleEnum.Ïîëüçîâàòåëü;
|
||||
}
|
||||
|
||||
APIClient.PostRequest("api/user/register", new UserBindingModel
|
||||
{
|
||||
Surname = surname,
|
||||
@ -67,9 +81,10 @@ namespace CandidateReviewUserApp.Controllers
|
||||
Email = login,
|
||||
Password = password,
|
||||
EmailConfirmed = false,
|
||||
Role = CandidateReviewDataModels.Enums.RoleEnum.Íåèçâåñòåí
|
||||
Role = role
|
||||
});
|
||||
Response.Redirect("Enter");
|
||||
|
||||
Response.Redirect("/Home/Enter");
|
||||
return;
|
||||
}
|
||||
|
||||
|
77
CandidateReviewClientApp/Controllers/UserController.cs
Normal file
77
CandidateReviewClientApp/Controllers/UserController.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using CandidateReviewClientApp.Models;
|
||||
using CandidateReviewContracts.BindingModels;
|
||||
using CandidateReviewContracts.ViewModels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CandidateReviewClientApp.Controllers
|
||||
{
|
||||
public class UserController : Controller
|
||||
{
|
||||
private readonly ILogger<UserController> _logger;
|
||||
|
||||
public UserController(ILogger<UserController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult UserProfile(int? id)
|
||||
{
|
||||
var userId = id ?? APIClient.User.Id;
|
||||
|
||||
var model = APIClient.GetRequest<UserViewModel>($"api/user/profile?id={userId}");
|
||||
|
||||
if (model == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult UserProfileEdit()
|
||||
{
|
||||
if (APIClient.User == null)
|
||||
{
|
||||
return Redirect("/Home/Enter");
|
||||
}
|
||||
return View(APIClient.User);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void UserProfileEdit(UserBindingModel model)
|
||||
{
|
||||
APIClient.PostRequest("api/user/update", new UserBindingModel
|
||||
{
|
||||
Id = model.Id,
|
||||
Surname = model.Surname,
|
||||
Name = model.Name,
|
||||
LastName = model.LastName ?? null,
|
||||
CompanyId = model.CompanyId ?? null,
|
||||
Email = model.Email,
|
||||
Password = model.Password,
|
||||
EmailConfirmed = model.EmailConfirmed,
|
||||
Role = model.Role,
|
||||
AvatarFilePath = model.AvatarFilePath ?? null,
|
||||
PhoneNumber = model.PhoneNumber ?? null
|
||||
});
|
||||
|
||||
Response.Redirect("/User/UserProfile");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public void Logout()
|
||||
{
|
||||
APIClient.User = null;
|
||||
Response.Redirect("/Home/Enter");
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,6 @@ app.UseAuthorization();
|
||||
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
pattern: "{controller=Home}/{action=Enter}/{id?}");
|
||||
|
||||
app.Run();
|
||||
|
58
CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml
Normal file
58
CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml
Normal file
@ -0,0 +1,58 @@
|
||||
@using CandidateReviewContracts.ViewModels
|
||||
@model CompanyViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Профиль компании";
|
||||
}
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
@if (!string.IsNullOrEmpty(Model.LogoFilePath))
|
||||
{
|
||||
<img src="@Model.LogoFilePath" class="card-img-top img-fluid" alt="Логотип компании">
|
||||
}
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">@Model.Name</h5>
|
||||
<p class="card-text text-truncate" style="max-height: 100px; overflow: hidden;">@Model.Description</p>
|
||||
<a href="@Model.Website" target="_blank" class="btn btn-primary mt-2">@Model.Website</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Информация о компании</h2>
|
||||
<a asp-action="EditCompanyProfile" asp-controller="Company" asp-route-id="@Model.Id" class="btn btn-primary float-end">Редактировать</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<td>@Model.Name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Описание</th>
|
||||
<td>@Model.Description</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Официальный сайт</th>
|
||||
<td><a href="@Model.Website" target="_blank">@Model.Website</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Адрес</th>
|
||||
<td>@Model.Address</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Контакты</th>
|
||||
<td>@Model.Contacts</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,65 @@
|
||||
@using CandidateReviewContracts.ViewModels
|
||||
@model CompanyViewModel
|
||||
|
||||
@{
|
||||
var title = @Model.Id <= 0 ? "Создать профиль компании" : "Редактировать профиль компании";
|
||||
}
|
||||
|
||||
<div class="container">
|
||||
<h1>@title</h1>
|
||||
<form method="post" class="row g-3 needs-validation" novalidate>
|
||||
<input type="hidden" name="id" value="@Model?.Id" />
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="name" class="form-label">Название <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="@Model?.Name" required placeholder="Введите название компании">
|
||||
<div class="invalid-feedback">Пожалуйста, введите название компании.</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="description" class="form-label">Описание</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="3" placeholder="Введите описание компании">@Model?.Description</textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="website" class="form-label">Официальный сайт</label>
|
||||
<input type="url" class="form-control" id="website" name="website" value="@Model?.Website" placeholder="Введите URL сайта">
|
||||
<div class="invalid-feedback">Пожалуйста, введите валидный URL (например, https://example.com).</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="address" class="form-label">Адрес</label>
|
||||
<input type="text" class="form-control" id="address" name="address" value="@Model?.Address" placeholder="Введите адрес компании">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="contacts" class="form-label">Контакты</label>
|
||||
<input type="text" class="form-control" id="contacts" name="contacts" value="@Model?.Contacts" placeholder="Введите контактную информацию">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">Сохранить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
'use strict'
|
||||
|
||||
var forms = document.querySelectorAll('.needs-validation')
|
||||
|
||||
Array.prototype.slice.call(forms)
|
||||
.forEach(function (form) {
|
||||
form.addEventListener('submit', function (event) {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
form.classList.add('was-validated')
|
||||
}, false)
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
|
@ -1,5 +1,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "Вход";
|
||||
Layout = "_LoginLayout";
|
||||
}
|
||||
|
||||
<div class="container mt-5">
|
||||
@ -22,6 +23,9 @@
|
||||
<div class="form-group text-center mt-3">
|
||||
<button type="submit" class="btn btn-primary">Войти</button>
|
||||
</div>
|
||||
<div class="form-group text-center mt-2">
|
||||
<a asp-area="" asp-controller="Home" asp-action="Register" class="text-muted">Ещё не зарегистрированы? Регистрация</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "Регистрация";
|
||||
Layout = "_LoginLayout";
|
||||
}
|
||||
|
||||
<div class="container mt-5">
|
||||
@ -34,10 +35,12 @@
|
||||
<div class="form-group text-center mt-3">
|
||||
<button type="submit" class="btn btn-primary">Зарегистрироваться</button>
|
||||
</div>
|
||||
<div class="form-group text-center mt-2">
|
||||
<a asp-area="" asp-controller="Home" asp-action="Enter" class="text-muted">Уже зарегистрированы? Войти</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
@using CandidateReviewDataModels.Enums
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@ -13,43 +14,37 @@
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Ревью кандидатов на вакансию</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Candidate Review</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
|
||||
<ul class="navbar-nav flex-grow-1">
|
||||
<div class="collapse navbar-collapse" id="mainNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
@if (APIClient.User?.Role == RoleEnum.Сотрудник)
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Заказы</a>
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Company" asp-action="@(APIClient.User?.CompanyId == null ? "EditCompanyProfile" : "CompanyProfile")">Профиль компании</a>
|
||||
</li>
|
||||
}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Личные данные</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Mails">Письма</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Enter">Вход</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Register">Регистрация</a>
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="UserProfile" asp-route-id="@(APIClient.User?.Id)">Профиль</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="container">
|
||||
<div class="container mt-4">
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted">
|
||||
<footer class="border-top footer text-muted mt-5">
|
||||
<div class="container">
|
||||
© 2024 - Ревью кандидатов на вакансию - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
||||
© 2024 - Ревью кандидатов на вакансию
|
||||
</div>
|
||||
</footer>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
|
48
CandidateReviewClientApp/Views/Shared/_LoginLayout.cshtml
Normal file
48
CandidateReviewClientApp/Views/Shared/_LoginLayout.cshtml
Normal file
@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - CandidateReviewClientApp</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/CandidateReviewClientApp.styles.css" asp-append-version="true" />
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted mt-5">
|
||||
<div class="container">
|
||||
© 2024 - Ревью кандидатов на вакансию
|
||||
</div>
|
||||
</footer>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
52
CandidateReviewClientApp/Views/User/UserProfile.cshtml
Normal file
52
CandidateReviewClientApp/Views/User/UserProfile.cshtml
Normal file
@ -0,0 +1,52 @@
|
||||
@using CandidateReviewContracts.ViewModels
|
||||
@model UserViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Профиль пользователя";
|
||||
}
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card">
|
||||
@if (!string.IsNullOrEmpty(@Model.AvatarFilePath))
|
||||
{
|
||||
<img src="@Model.AvatarFilePath" class="card-img-top img-fluid rounded-circle mx-auto d-block" style="max-width: 150px; max-height: 150px;" alt="Аватар пользователя">
|
||||
}
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title mb-0">
|
||||
@Model.Name @(string.IsNullOrEmpty(@Model.Surname) ? "" : @Model.Surname) @(string.IsNullOrEmpty(@Model.LastName) ? "" : @Model.LastName)
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="mb-0">Контактная информация</h2>
|
||||
<div class="btn-group float-end mt-2" role="group" aria-label="Действия">
|
||||
<a asp-action="UserProfileEdit" asp-controller="User" asp-route-id="@Model?.Id" class="btn btn-primary">Редактировать</a>
|
||||
<a asp-action="Delete" asp-controller="User" asp-route-id="@Model?.Id" class="btn btn-danger" onclick="return confirm('Вы уверены, что хотите удалить профиль?');">Удалить</a>
|
||||
<a asp-action="Logout" asp-controller="User" class="btn btn-secondary">Выйти</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-3">Email</dt>
|
||||
<dd class="col-sm-9">@Model.Email</dd>
|
||||
|
||||
@if (!string.IsNullOrEmpty(@Model.PhoneNumber))
|
||||
{
|
||||
<dt class="col-sm-3">Телефон</dt>
|
||||
<dd class="col-sm-9">@Model.PhoneNumber</dd>
|
||||
}
|
||||
|
||||
<dt class="col-sm-3">Роль</dt>
|
||||
<dd class="col-sm-9">@Model.Role</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
99
CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml
Normal file
99
CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml
Normal file
@ -0,0 +1,99 @@
|
||||
@using CandidateReviewContracts.ViewModels
|
||||
@model UserViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Редактирование профиля";
|
||||
}
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 class="mb-4">Редактирование профиля</h2>
|
||||
<form method="post" class="needs-validation" novalidate>
|
||||
<input type="hidden" name="id" value="@Model?.Id" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="Name" class="form-label">Имя <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="Name" name="Name" value="@Model?.Name" required />
|
||||
<div class="invalid-feedback">Пожалуйста, введите имя.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="Surname" class="form-label">Фамилия <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="Surname" name="Surname" value="@Model?.Surname" required />
|
||||
<div class="invalid-feedback">Пожалуйста, введите фамилию.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="LastName" class="form-label">Отчество</label>
|
||||
<input type="text" class="form-control" id="LastName" name="LastName" value="@Model?.LastName" />
|
||||
<div class="invalid-feedback">Пожалуйста, введите отчество.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="PhoneNumber" class="form-label">Телефон</label>
|
||||
<input type="tel" class="form-control" id="PhoneNumber" name="PhoneNumber" value="@Model?.PhoneNumber" />
|
||||
<div class="invalid-feedback">Пожалуйста, введите телефон.</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="Email" value="@Model?.Email" />
|
||||
<input type="hidden" name="Password" value="@Model?.Password" />
|
||||
<input type="hidden" name="Role" value="@Model?.Role" />
|
||||
<input type="hidden" name="CompanyId" value="@Model?.CompanyId" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="AvatarFilePath" class="form-label">Аватар</label>
|
||||
<input type="file" class="form-control" id="AvatarFilePath" name="AvatarFilePath" value="@Model?.AvatarFilePath" accept=".jpg,.jpeg,.png" />
|
||||
<div class="invalid-feedback">Выберите файл изображения.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<button type="submit" class="btn btn-primary">Сохранить</button>
|
||||
<a asp-controller="User" asp-action="UserProfile" class="btn btn-secondary">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.inputmask/5.0.7-beta.29/jquery.inputmask.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#PhoneNumber').inputmask({
|
||||
mask: '+7 (999) 999-99-99', // Adjust mask as needed
|
||||
showMaskOnHover: false,
|
||||
onincomplete: function () {
|
||||
$(this).removeClass('is-valid').addClass('is-invalid');
|
||||
},
|
||||
oncleared: function () {
|
||||
$(this).removeClass('is-valid is-invalid');
|
||||
},
|
||||
oncomplete: function () {
|
||||
$(this).removeClass('is-invalid').addClass('is-valid');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
(function () {
|
||||
'use strict'
|
||||
var forms = document.querySelectorAll('.needs-validation')
|
||||
Array.prototype.slice.call(forms)
|
||||
.forEach(function (form) {
|
||||
form.addEventListener('submit', function (event) {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
form.classList.add('was-validated')
|
||||
}, false)
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
@{
|
||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ namespace CandidateReviewContracts.BusinessLogicsContracts
|
||||
{
|
||||
List<CompanyViewModel>? ReadList(CompanySearchModel? model);
|
||||
CompanyViewModel? ReadElement(CompanySearchModel model);
|
||||
bool Create(CompanyBindingModel model);
|
||||
int Create(CompanyBindingModel model);
|
||||
bool Update(CompanyBindingModel model);
|
||||
bool Delete(CompanyBindingModel model);
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ namespace CandidateReviewDatabaseImplement.Models
|
||||
Role = model.Role;
|
||||
CompanyId = model.CompanyId;
|
||||
}
|
||||
|
||||
public UserViewModel GetViewModel => new()
|
||||
{
|
||||
Id = Id,
|
||||
|
81
CandidateReviewRestApi/Controllers/CompanyController.cs
Normal file
81
CandidateReviewRestApi/Controllers/CompanyController.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using CandidateReviewContracts.BindingModels;
|
||||
using CandidateReviewContracts.BusinessLogicsContracts;
|
||||
using CandidateReviewContracts.SearchModels;
|
||||
using CandidateReviewContracts.ViewModels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CandidateReviewRestApi.Controllers
|
||||
{
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class CompanyController : Controller
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ICompanyLogic _logic;
|
||||
public CompanyController(ICompanyLogic logic, ILogger<CompanyController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_logic = logic;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public CompanyViewModel? Profile(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _logic.ReadElement(new CompanySearchModel
|
||||
{
|
||||
Id = id
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Ошибка получения профиля компании");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public int Create(CompanyBindingModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
int newCompanyId = _logic.Create(model);
|
||||
return newCompanyId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Ошибка создания профиля компании");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void Update(CompanyBindingModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logic.Update(model);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Ошибка обновления профиля компании");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public void Delete(CompanyBindingModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logic.Delete(model);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Ошибка удаления профиля компании");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -36,6 +36,23 @@ namespace CandidateReviewRestApi.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public UserViewModel? Profile(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _logic.ReadElement(new UserSearchModel
|
||||
{
|
||||
Id = id
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Ошибка получения данных");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void Register(UserBindingModel model)
|
||||
{
|
||||
@ -51,7 +68,7 @@ namespace CandidateReviewRestApi.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public void UpdateData(UserBindingModel model)
|
||||
public void Update(UserBindingModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user