Register page
This commit is contained in:
@@ -7,6 +7,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||||
|
<PackageReference Include="FluentValidation" Version="11.9.2" />
|
||||||
|
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" Version="3.2.0-rc1.20223.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.1" PrivateAssets="all"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.1" PrivateAssets="all"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -15,4 +19,8 @@
|
|||||||
<ProjectReference Include="..\VisaApiClient\VisaApiClient.csproj" />
|
<ProjectReference Include="..\VisaApiClient\VisaApiClient.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<_ContentIncludedByDefault Remove="wwwroot\sample-data\weather.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
@using VisaApiClient
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div >
|
||||||
|
<label >
|
||||||
|
Country:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="Address.Country"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Address.Country"></ValidationMessage><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label >
|
||||||
|
City:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="Address.City"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Address.City"></ValidationMessage><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label >
|
||||||
|
Street:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="Address.Street"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Address.Street"></ValidationMessage><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label >
|
||||||
|
Building:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="Address.Building"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Address.Building"></ValidationMessage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter, EditorRequired] public AddressModel Address { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
@using VisaApiClient
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div >
|
||||||
|
<label >
|
||||||
|
Email:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="AuthData.Email"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => AuthData.Email"></ValidationMessage><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label >
|
||||||
|
Password:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="AuthData.Password"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => AuthData.Password"></ValidationMessage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter, EditorRequired] public AuthData AuthData { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
@using System.ComponentModel.DataAnnotations
|
||||||
|
@using System.Linq.Expressions
|
||||||
|
@using System.Reflection
|
||||||
|
@typeparam TItem where TItem : class
|
||||||
|
@typeparam TMember where TMember : struct, Enum
|
||||||
|
|
||||||
|
<InputSelect TValue="TMember" @bind-Value="selected">
|
||||||
|
@foreach (var value in enumValues)
|
||||||
|
{
|
||||||
|
<option value="@value.Key">@value.Value</option>
|
||||||
|
}
|
||||||
|
</InputSelect><br/>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter, EditorRequired] public TItem Model { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter, EditorRequired] public Expression<Func<TItem, TMember>> EnumProperty { get; set; } = null!;
|
||||||
|
|
||||||
|
private Dictionary<TMember, string> enumValues = new();
|
||||||
|
private PropertyInfo modelMemberInfo = null!;
|
||||||
|
private TMember selected;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
var enumMembers = typeof(TMember).GetMembers();
|
||||||
|
var modelMemberName = ((MemberExpression)EnumProperty.Body).Member.Name;
|
||||||
|
modelMemberInfo = typeof(TItem).GetProperty(modelMemberName)!;
|
||||||
|
|
||||||
|
foreach (var value in Enum.GetValues<TMember>())
|
||||||
|
{
|
||||||
|
var member = enumMembers.First(info => info.Name == value.ToString());
|
||||||
|
var displayAttribute = (DisplayAttribute?)member
|
||||||
|
.GetCustomAttributes(typeof(DisplayAttribute), false)
|
||||||
|
.FirstOrDefault();
|
||||||
|
var displayName = displayAttribute?.Name ?? value.ToString();
|
||||||
|
enumValues.Add(value, displayName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAfterRender(bool firstRender)
|
||||||
|
{
|
||||||
|
OnValueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnValueChanged()
|
||||||
|
{
|
||||||
|
modelMemberInfo.SetValue(Model, selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
@using VisaApiClient
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
First name:<br/>
|
||||||
|
<InputText DisplayName="First name" class="rounded" @bind-Value="Name.FirstName"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Name.FirstName"></ValidationMessage>
|
||||||
|
</div><br/>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Surname:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="Name.Surname"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Name.Surname"></ValidationMessage>
|
||||||
|
</div><br/>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Patronymic:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="Name.Patronymic"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Name.Patronymic"></ValidationMessage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter, EditorRequired] public NameModel Name { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services
|
||||||
|
@using VisaApiClient
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Passport number:<br/>
|
||||||
|
<InputText DisplayName="Passport number" class="rounded" @bind-Value="Passport.Number"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Passport.Number"></ValidationMessage>
|
||||||
|
</div><br/>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Issuer:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="Passport.Issuer"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Passport.Issuer"></ValidationMessage>
|
||||||
|
</div><br/>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Issue date:<br/>
|
||||||
|
<InputDate DisplayName="Issue date" class="rounded" @bind-Value="Passport.IssueDate" max="DateTimeProvider.Now()"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Passport.IssueDate"></ValidationMessage>
|
||||||
|
</div><br/>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Expiration date:<br/>
|
||||||
|
<InputDate DisplayName="Expiration date" class="rounded" @bind-Value="Passport.ExpirationDate" min="DateTimeProvider.Now()"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => Passport.ExpirationDate"></ValidationMessage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string now = DateTime.Now.ToString("yyyy-MM-dd");
|
||||||
|
|
||||||
|
[Parameter, EditorRequired] public PassportModel Passport { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject] IDateTimeProvider DateTimeProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
Passport.IssueDate = DateTime.Now;
|
||||||
|
Passport.ExpirationDate = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
@using BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Models
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div >
|
||||||
|
<label >
|
||||||
|
Name:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="PlaceOfWork.Name"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => PlaceOfWork.Name"></ValidationMessage><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label >
|
||||||
|
Phone number:<br/>
|
||||||
|
<InputText DisplayName="Phone number" class="rounded" @bind-Value="PlaceOfWork.PhoneNum"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => PlaceOfWork.PhoneNum"></ValidationMessage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter, EditorRequired] public PlaceOfWorkModel PlaceOfWork { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<h3>CustomValidation</h3>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ValidationMessageStore? messageStore;
|
||||||
|
|
||||||
|
[CascadingParameter]
|
||||||
|
private EditContext? CurrentEditContext { get; set; }
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (CurrentEditContext is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"{nameof(CustomValidation)} requires a cascading " +
|
||||||
|
$"parameter of type {nameof(EditContext)}. " +
|
||||||
|
$"For example, you can use {nameof(CustomValidation)} " +
|
||||||
|
$"inside an {nameof(EditForm)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
messageStore = new(CurrentEditContext);
|
||||||
|
|
||||||
|
CurrentEditContext.OnValidationRequested += (_, _) =>
|
||||||
|
messageStore?.Clear();
|
||||||
|
CurrentEditContext.OnFieldChanged += (_, e) =>
|
||||||
|
messageStore?.Clear(e.FieldIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisplayErrors(Dictionary<string, List<string>> errors)
|
||||||
|
{
|
||||||
|
if (CurrentEditContext is not null)
|
||||||
|
{
|
||||||
|
foreach (var err in errors)
|
||||||
|
{
|
||||||
|
messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentEditContext.NotifyValidationStateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearErrors()
|
||||||
|
{
|
||||||
|
messageStore?.Clear();
|
||||||
|
CurrentEditContext?.NotifyValidationStateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using VisaApiClient;
|
||||||
|
|
||||||
|
namespace BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Models
|
||||||
|
{
|
||||||
|
/// Model of place of work with attributes required for validation to work
|
||||||
|
public class PlaceOfWorkModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[StringLength(ConfigurationConstraints.PlaceOfWorkNameLength, MinimumLength = 1)]
|
||||||
|
public string Name { get; set; } = default!;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[ValidateComplexType]
|
||||||
|
public AddressModel Address { get; set; } = new AddressModel();
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(ConfigurationConstraints.PhoneNumberLength, MinimumLength = ConfigurationConstraints.PhoneNumberMinLength)]
|
||||||
|
public string PhoneNum { get; set; } = default!;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
using VisaApiClient;
|
||||||
|
|
||||||
|
namespace BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Models
|
||||||
|
{
|
||||||
|
/// Model of request with attributes required for validation to work
|
||||||
|
public class RegisterApplicantRequestModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[ValidateComplexType]
|
||||||
|
public RegisterRequestModel RegisterRequest { get; set; } = new();
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[ValidateComplexType]
|
||||||
|
public NameModel ApplicantName { get; set; } = new();
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[ValidateComplexType]
|
||||||
|
public PassportModel Passport { get; set; } = new();
|
||||||
|
|
||||||
|
[Required(AllowEmptyStrings = true)]
|
||||||
|
public DateTimeOffset BirthDate { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(70, MinimumLength = 1)]
|
||||||
|
public string CityOfBirth { get; set; } = default!;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(70, MinimumLength = 1)]
|
||||||
|
public string CountryOfBirth { get; set; } = default!;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(30, MinimumLength = 1)]
|
||||||
|
public string Citizenship { get; set; } = default!;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(30, MinimumLength = 1)]
|
||||||
|
public string CitizenshipByBirth { get; set; } = default!;
|
||||||
|
|
||||||
|
[Required(AllowEmptyStrings = true)]
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public Gender Gender { get; set; }
|
||||||
|
|
||||||
|
[Required(AllowEmptyStrings = true)]
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public MaritalStatus MaritalStatus { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[ValidateComplexType]
|
||||||
|
public NameModel FatherName { get; set; } = new();
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[ValidateComplexType]
|
||||||
|
public NameModel MotherName { get; set; } = new();
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(50, MinimumLength = 1)]
|
||||||
|
public string JobTitle { get; set; } = default!;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[ValidateComplexType]
|
||||||
|
public PlaceOfWorkModel PlaceOfWork { get; set; } = new();
|
||||||
|
|
||||||
|
public bool IsNonResident { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using VisaApiClient;
|
||||||
|
|
||||||
|
namespace BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Models
|
||||||
|
{
|
||||||
|
/// Model of request with attributes required for validation to work
|
||||||
|
public class RegisterRequestModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[ValidateComplexType]
|
||||||
|
public AuthData AuthData { get; set; } = new AuthData();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using VisaApiClient;
|
||||||
|
|
||||||
|
namespace BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Validators;
|
||||||
|
|
||||||
|
public class NameModelValidator : AbstractValidator<NameModel>
|
||||||
|
{
|
||||||
|
public NameModelValidator()
|
||||||
|
{
|
||||||
|
RuleFor(m => m.FirstName)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("First Name can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.NameLength)
|
||||||
|
.WithMessage($"First Name length must be less than {ConfigurationConstraints.NameLength}");
|
||||||
|
|
||||||
|
RuleFor(m => m.Surname)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Surname can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.NameLength)
|
||||||
|
.WithMessage($"Surname length must be less than {ConfigurationConstraints.NameLength}");
|
||||||
|
|
||||||
|
RuleFor(m => m.Patronymic)
|
||||||
|
.MaximumLength(ConfigurationConstraints.NameLength)
|
||||||
|
.WithMessage($"Patronymic length must be less than {ConfigurationConstraints.NameLength}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using BlazorWebAssemblyVisaApiClient.Infrastructure.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using VisaApiClient;
|
||||||
|
|
||||||
|
namespace BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Validators;
|
||||||
|
|
||||||
|
public class PassportModelValidator : AbstractValidator<PassportModel>
|
||||||
|
{
|
||||||
|
public PassportModelValidator(IDateTimeProvider dateTimeProvider)
|
||||||
|
{
|
||||||
|
RuleFor(r => r.Issuer)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Passport issuer can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.IssuerNameLength)
|
||||||
|
.WithMessage($"Passport issuer length must be less than {ConfigurationConstraints.IssuerNameLength}");
|
||||||
|
|
||||||
|
RuleFor(r => r.Number)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Passport number can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.PassportNumberLength)
|
||||||
|
.WithMessage($"Passport number length must be less than {ConfigurationConstraints.PassportNumberLength}");
|
||||||
|
|
||||||
|
RuleFor(r => r.ExpirationDate)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Passport expiration date can not be empty")
|
||||||
|
.GreaterThan(dateTimeProvider.Now())
|
||||||
|
.WithMessage("Can not approve visa for applicants with expired passport");
|
||||||
|
|
||||||
|
RuleFor(r => r.IssueDate)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Passport issue date can not be empty")
|
||||||
|
.LessThanOrEqualTo(dateTimeProvider.Now())
|
||||||
|
.WithMessage("Passport issue date must be in past");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Models;
|
||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Validators;
|
||||||
|
|
||||||
|
public class PlaceOfWorkModelValidator : AbstractValidator<PlaceOfWorkModel>
|
||||||
|
{
|
||||||
|
public PlaceOfWorkModelValidator()
|
||||||
|
{
|
||||||
|
RuleFor(p => p.Name)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Place of work name can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.PlaceOfWorkNameLength)
|
||||||
|
.WithMessage($"Place of work name length must be less than {ConfigurationConstraints.PlaceOfWorkNameLength}");
|
||||||
|
|
||||||
|
RuleFor(p => p.PhoneNum)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Place of work phone number can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.PhoneNumberLength)
|
||||||
|
.WithMessage(
|
||||||
|
$"Phone number length must be in range from {ConfigurationConstraints.PhoneNumberMinLength} to {ConfigurationConstraints.PhoneNumberLength}")
|
||||||
|
.MinimumLength(ConfigurationConstraints.PhoneNumberMinLength)
|
||||||
|
.WithMessage(
|
||||||
|
$"Phone number length must be in range from {ConfigurationConstraints.PhoneNumberMinLength} to {ConfigurationConstraints.PhoneNumberLength}");
|
||||||
|
|
||||||
|
RuleFor(p => p.Address.Country)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Country name of place of work can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.CountryNameLength)
|
||||||
|
.WithMessage($"Country name of place of work length must be less than {ConfigurationConstraints.CountryNameLength}");
|
||||||
|
|
||||||
|
RuleFor(p => p.Address.City)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("City name of place of work can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.CityNameLength)
|
||||||
|
.WithMessage($"City name of place of work length must be less than {ConfigurationConstraints.CityNameLength}");
|
||||||
|
|
||||||
|
RuleFor(p => p.Address.Street)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Street name of place of work can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.StreetNameLength)
|
||||||
|
.WithMessage($"Street name of place of work length must be less than {ConfigurationConstraints.StreetNameLength}");
|
||||||
|
|
||||||
|
RuleFor(p => p.Address.Building)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Building of place of work can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.CountryNameLength)
|
||||||
|
.WithMessage($"Building of place of work length must be less than {ConfigurationConstraints.BuildingNumberLength}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Models;
|
||||||
|
using BlazorWebAssemblyVisaApiClient.Infrastructure.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using VisaApiClient;
|
||||||
|
using PlaceOfWorkModel = BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Models.PlaceOfWorkModel;
|
||||||
|
|
||||||
|
namespace BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Validators;
|
||||||
|
|
||||||
|
public class RegisterApplicantRequestValidator : AbstractValidator<RegisterApplicantRequestModel>
|
||||||
|
{
|
||||||
|
public RegisterApplicantRequestValidator(
|
||||||
|
IDateTimeProvider dateTimeProvider,
|
||||||
|
IValidator<NameModel> nameValidator,
|
||||||
|
IValidator<RegisterRequestModel> registerRequestModelValidator,
|
||||||
|
IValidator<PassportModel> passportValidator,
|
||||||
|
IValidator<PlaceOfWorkModel> placeOfWorkModelValidator)
|
||||||
|
{
|
||||||
|
RuleFor(r => r.RegisterRequest)
|
||||||
|
.SetValidator(registerRequestModelValidator);
|
||||||
|
|
||||||
|
RuleFor(r => r.ApplicantName)
|
||||||
|
.SetValidator(nameValidator);
|
||||||
|
|
||||||
|
RuleFor(r => r.FatherName)
|
||||||
|
.SetValidator(nameValidator);
|
||||||
|
|
||||||
|
RuleFor(r => r.MotherName)
|
||||||
|
.SetValidator(nameValidator);
|
||||||
|
|
||||||
|
RuleFor(r => r.Passport)
|
||||||
|
.SetValidator(passportValidator);
|
||||||
|
|
||||||
|
RuleFor(r => r.BirthDate)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Birth date can not be empty")
|
||||||
|
.LessThanOrEqualTo(dateTimeProvider.Now().AddYears(-ConfigurationConstraints.ApplicantMinAge))
|
||||||
|
.WithMessage($"Applicant must be older than {ConfigurationConstraints.ApplicantMinAge}");
|
||||||
|
|
||||||
|
RuleFor(r => r.CountryOfBirth)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Country of birth can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.CountryNameLength)
|
||||||
|
.WithMessage($"Country of birth name length must be less than {ConfigurationConstraints.CountryNameLength}");
|
||||||
|
|
||||||
|
RuleFor(r => r.CityOfBirth)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("City of birth can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.CityNameLength)
|
||||||
|
.WithMessage($"City of birth name length must be less than {ConfigurationConstraints.CityNameLength}");
|
||||||
|
|
||||||
|
RuleFor(r => r.Citizenship)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Citizenship can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.CitizenshipLength)
|
||||||
|
.WithMessage($"Citizenship length must be less than {ConfigurationConstraints.CitizenshipLength}");
|
||||||
|
|
||||||
|
RuleFor(r => r.CitizenshipByBirth)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Citizenship by birth can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.CitizenshipLength)
|
||||||
|
.WithMessage($"Citizenship by birth length must be less than {ConfigurationConstraints.CitizenshipLength}");
|
||||||
|
|
||||||
|
RuleFor(r => r.Gender).IsInEnum();
|
||||||
|
|
||||||
|
RuleFor(r => r.MaritalStatus).IsInEnum();
|
||||||
|
|
||||||
|
RuleFor(r => r.JobTitle)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Title of job can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.JobTitleLength)
|
||||||
|
.WithMessage($"Title of job length must be less than {ConfigurationConstraints.JobTitleLength}");
|
||||||
|
|
||||||
|
RuleFor(r => r.PlaceOfWork)
|
||||||
|
.SetValidator(placeOfWorkModelValidator);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using VisaApiClient;
|
||||||
|
|
||||||
|
namespace BlazorWebAssemblyVisaApiClient.FluentValidation.Common;
|
||||||
|
|
||||||
|
public class AuthDataValidator : AbstractValidator<AuthData>
|
||||||
|
{
|
||||||
|
public AuthDataValidator()
|
||||||
|
{
|
||||||
|
RuleFor(d => d.Email)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Email can not be empty")
|
||||||
|
.EmailAddress()
|
||||||
|
.WithMessage("Email must be valid")
|
||||||
|
.MaximumLength(ConfigurationConstraints.EmailLength)
|
||||||
|
.WithMessage($"Email length must be less than {ConfigurationConstraints.EmailLength}");
|
||||||
|
|
||||||
|
RuleFor(d => d.Password)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("Password can not be empty")
|
||||||
|
.MaximumLength(ConfigurationConstraints.PasswordLength)
|
||||||
|
.WithMessage($"Password length must be less than {ConfigurationConstraints.PasswordLength}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Models;
|
||||||
|
using FluentValidation;
|
||||||
|
using VisaApiClient;
|
||||||
|
|
||||||
|
namespace BlazorWebAssemblyVisaApiClient.FluentValidation.Common;
|
||||||
|
|
||||||
|
public class RegisterRequestModelValidator : AbstractValidator<RegisterRequestModel>
|
||||||
|
{
|
||||||
|
public RegisterRequestModelValidator(IValidator<AuthData> authDataValidator)
|
||||||
|
{
|
||||||
|
RuleFor(r => r.AuthData)
|
||||||
|
.SetValidator(authDataValidator);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace BlazorWebAssemblyVisaApiClient.FluentValidation;
|
||||||
|
|
||||||
|
public static class ConfigurationConstraints
|
||||||
|
{
|
||||||
|
public const int CityNameLength = 70;
|
||||||
|
public const int CountryNameLength = 70;
|
||||||
|
public const int CitizenshipLength = 30;
|
||||||
|
public const int ReentryPermitNumberLength = 25;
|
||||||
|
public const int IssuerNameLength = 200;
|
||||||
|
public const int VisaNameLength = 70;
|
||||||
|
public const int StreetNameLength = 100;
|
||||||
|
public const int PlaceOfWorkNameLength = 200;
|
||||||
|
public const int NameLength = 50;
|
||||||
|
public const int BuildingNumberLength = 10;
|
||||||
|
public const int PassportNumberLength = 20;
|
||||||
|
public const int PhoneNumberLength = 13;
|
||||||
|
public const int PhoneNumberMinLength = 11;
|
||||||
|
public const int EmailLength = 254;
|
||||||
|
public const int PasswordLength = 50;
|
||||||
|
public const int ApplicantMinAge = 14;
|
||||||
|
public const int JobTitleLength = 50;
|
||||||
|
public const int MaxValidDays = 90;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Models;
|
||||||
|
using VisaApiClient;
|
||||||
|
|
||||||
|
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.AutoMapper.Profiles
|
||||||
|
{
|
||||||
|
public class RegisterApplicantRequestProfile : Profile
|
||||||
|
{
|
||||||
|
public RegisterApplicantRequestProfile()
|
||||||
|
{
|
||||||
|
CreateMap<RegisterApplicantRequestModel, RegisterApplicantRequest>(MemberList.Destination);
|
||||||
|
|
||||||
|
CreateMap<RegisterRequestModel, RegisterRequest>(MemberList.Destination);
|
||||||
|
|
||||||
|
CreateMap<FluentValidation.Applicants.Models.PlaceOfWorkModel, VisaApiClient.PlaceOfWorkModel>(MemberList.Destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.Services
|
||||||
|
{
|
||||||
|
public class DateTimeProvider : IDateTimeProvider
|
||||||
|
{
|
||||||
|
public DateTime Now() => DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.Services
|
||||||
|
{
|
||||||
|
public interface IDateTimeProvider
|
||||||
|
{
|
||||||
|
DateTime Now();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div class="top-row px-4">
|
<article class="content px-4 fullscreen">
|
||||||
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<article class="content px-4">
|
|
||||||
@Body
|
@Body
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="top-row ps-3 navbar navbar-dark">
|
<div class="top-row ps-3 navbar navbar-dark">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="">BlazorWebAssemblyVisaApiClient</a>
|
<a class="navbar-brand" href="">Schengen Visa</a>
|
||||||
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
@@ -11,17 +11,7 @@
|
|||||||
<nav class="flex-column">
|
<nav class="flex-column">
|
||||||
<div class="nav-item px-3">
|
<div class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Login
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
<div class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="counter">
|
|
||||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
<div class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="weather">
|
|
||||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
|
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,31 +1,38 @@
|
|||||||
@page "/login"
|
@page "/"
|
||||||
@using VisaApiClient
|
@using VisaApiClient
|
||||||
|
@using BlazorWebAssemblyVisaApiClient.Components.FormComponents.Applicants
|
||||||
|
|
||||||
<PageTitle>Authentication</PageTitle>
|
<PageTitle>Authentication</PageTitle>
|
||||||
|
|
||||||
<EditForm Model="loginData" OnValidSubmit="TryLogin">
|
<div class="with-centered-content">
|
||||||
|
<EditForm class="form" Model="loginData" OnValidSubmit="TryLogin">
|
||||||
<DataAnnotationsValidator/>
|
<DataAnnotationsValidator/>
|
||||||
<label >Email: <InputText @bind-Value="loginData.Email"/></label>
|
|
||||||
<label >Password: <InputText @bind-Value="loginData.Password"/></label>
|
<AuthDataInput AuthData="loginData"/><br/>
|
||||||
<input type="submit" value="Login"/>
|
|
||||||
</EditForm>
|
<input class="btn-outline-primary rounded" type="submit" value="Login"/>
|
||||||
|
or
|
||||||
|
<NavLink href="register">Register</NavLink >
|
||||||
<p>@loginResult</p>
|
<p>@loginResult</p>
|
||||||
|
</EditForm>
|
||||||
|
</div>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private AuthData loginData = new();
|
private AuthData loginData = new();
|
||||||
private string loginResult = string.Empty;
|
private string loginResult = string.Empty;
|
||||||
|
|
||||||
[Inject]
|
[Inject] private Client Client { get; set; } = null!;
|
||||||
private Client Client { get; set; } = null!;
|
|
||||||
|
|
||||||
private async Task TryLogin(EditContext obj)
|
private async Task TryLogin()
|
||||||
{
|
{
|
||||||
|
loginResult = "Wait...";
|
||||||
|
StateHasChanged();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var token = await Client.LoginAsync(loginData.Email, loginData.Password);
|
var token = await Client.LoginAsync(loginData.Email, loginData.Password);
|
||||||
Client.SetAuthToken(token);
|
Client.SetAuthToken(token);
|
||||||
loginResult = "Logged in successfully";
|
loginResult = "Logged in successfully.";
|
||||||
}
|
}
|
||||||
catch (ApiException<ProblemDetails> e)
|
catch (ApiException<ProblemDetails> e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
@page "/counter"
|
|
||||||
|
|
||||||
<PageTitle>Counter</PageTitle>
|
|
||||||
|
|
||||||
<h1>Counter</h1>
|
|
||||||
|
|
||||||
<p role="status">Current count: @currentCount</p>
|
|
||||||
|
|
||||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private int currentCount = 0;
|
|
||||||
|
|
||||||
private void IncrementCount()
|
|
||||||
{
|
|
||||||
currentCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
@page "/"
|
|
||||||
|
|
||||||
<PageTitle>Home</PageTitle>
|
|
||||||
|
|
||||||
<h1>Hello, world!</h1>
|
|
||||||
|
|
||||||
Welcome to your new app.
|
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
@page "/register"
|
||||||
|
@using System.Net
|
||||||
|
@using System.Text
|
||||||
|
@using AutoMapper
|
||||||
|
@using VisaApiClient
|
||||||
|
@using BlazorWebAssemblyVisaApiClient.Components.FormComponents.Applicants
|
||||||
|
@using BlazorWebAssemblyVisaApiClient.FluentValidation.Applicants.Models
|
||||||
|
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services
|
||||||
|
@using global::FluentValidation
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
@using Newtonsoft.Json.Linq
|
||||||
|
|
||||||
|
<PageTitle>Registration</PageTitle>
|
||||||
|
|
||||||
|
<div class="horizontal-centered-content">
|
||||||
|
<h3>Registration data</h3>
|
||||||
|
<EditForm class="form" Model="requestModel" OnValidSubmit="TryRegisterApplicant">
|
||||||
|
<ObjectGraphDataAnnotationsValidator/>
|
||||||
|
|
||||||
|
<div class="form-block">
|
||||||
|
<h5>Authentication data</h5>
|
||||||
|
<AuthDataInput AuthData="requestModel.RegisterRequest.AuthData"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-block">
|
||||||
|
<h5>Your Fullname</h5>
|
||||||
|
<NameInput Name="requestModel.ApplicantName"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-block">
|
||||||
|
<h5>Fullname of your mother</h5>
|
||||||
|
<NameInput Name="requestModel.MotherName"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-block">
|
||||||
|
<h5>Fullname of your father</h5>
|
||||||
|
<NameInput Name="requestModel.FatherName"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-block">
|
||||||
|
<h5>Your passport</h5>
|
||||||
|
<PassportInput Passport="requestModel.Passport"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-block">
|
||||||
|
<h5>Birth data</h5>
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Country of birth:<br/>
|
||||||
|
<InputText DisplayName="Country of birth" class="rounded" @bind-Value="requestModel.CountryOfBirth"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => requestModel.CountryOfBirth"></ValidationMessage><br/>
|
||||||
|
<label>
|
||||||
|
City of birth:<br/>
|
||||||
|
<InputText DisplayName="City of birth" class="rounded" @bind-Value="requestModel.CityOfBirth"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => requestModel.CityOfBirth"></ValidationMessage><br/>
|
||||||
|
<label>
|
||||||
|
Birth date:<br/>
|
||||||
|
<InputDate DisplayName="Birth date" class="rounded" @bind-Value="requestModel.BirthDate" max="@DateTimeProvider.Now()"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => requestModel.BirthDate"></ValidationMessage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-block">
|
||||||
|
<h5>Citizenship</h5>
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Citizenship:<br/>
|
||||||
|
<InputText class="rounded" @bind-Value="requestModel.Citizenship"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => requestModel.Citizenship"></ValidationMessage><br/>
|
||||||
|
<label>
|
||||||
|
Citizenship by birth:<br/>
|
||||||
|
<InputText DisplayName="Citizenship by birth" class="rounded" @bind-Value="requestModel.CitizenshipByBirth"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => requestModel.CitizenshipByBirth"></ValidationMessage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-block">
|
||||||
|
<h5>Address of your place of work</h5>
|
||||||
|
<div >
|
||||||
|
<AddressInput Address="requestModel.PlaceOfWork.Address"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-block">
|
||||||
|
<h5>Place of work data</h5>
|
||||||
|
<div >
|
||||||
|
<PlaceOfWorkInput PlaceOfWork="requestModel.PlaceOfWork"/><br/>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Job title:<br/>
|
||||||
|
<InputText DisplayName="Job title" class="rounded" @bind-Value="requestModel.JobTitle"/>
|
||||||
|
</label><br/>
|
||||||
|
<ValidationMessage For="() => requestModel.JobTitle"></ValidationMessage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-block">
|
||||||
|
<h5>Other</h5>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Gender: <EnumInputList Model="requestModel" EnumProperty="r => r.Gender"/>
|
||||||
|
</label>
|
||||||
|
</div><br/>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Marital status: <EnumInputList Model="requestModel" EnumProperty="r => r.MaritalStatus"/>
|
||||||
|
</label>
|
||||||
|
</div><br/>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<label>
|
||||||
|
Non-resident: <InputCheckbox @bind-Value="requestModel.IsNonResident"/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div><br/>
|
||||||
|
|
||||||
|
<input type="submit" class="btn-outline-primary" value="Register"/>
|
||||||
|
<p class="@requestResultClass">@((MarkupString)requestResult)</p>
|
||||||
|
</EditForm>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private RegisterApplicantRequestModel requestModel = new();
|
||||||
|
private string requestResult = string.Empty;
|
||||||
|
private string requestResultClass = string.Empty;
|
||||||
|
|
||||||
|
[Inject] public Client Client { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject] IValidator<RegisterApplicantRequestModel> RegisterApplicantRequestValidator { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject] IMapper Mapper { get; set; } = null!;
|
||||||
|
|
||||||
|
[Inject] IDateTimeProvider DateTimeProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
requestModel.BirthDate = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetRequestResultMessage(string message)
|
||||||
|
{
|
||||||
|
requestResult = message;
|
||||||
|
requestResultClass = string.Empty;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetRequestResultSuccess(string message)
|
||||||
|
{
|
||||||
|
requestResult = message;
|
||||||
|
requestResultClass = "text-success";
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetRequestResultError(string message)
|
||||||
|
{
|
||||||
|
requestResult = message;
|
||||||
|
requestResultClass = "validation-message";
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ErrorsToString(IEnumerable<string> errors)
|
||||||
|
{
|
||||||
|
var stringBuilder = new StringBuilder();
|
||||||
|
foreach (var error in errors)
|
||||||
|
{
|
||||||
|
stringBuilder.Append($"{error}<br/>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void TryRegisterApplicant()
|
||||||
|
{
|
||||||
|
var validationResult = await RegisterApplicantRequestValidator.ValidateAsync(requestModel);
|
||||||
|
if (!validationResult.IsValid)
|
||||||
|
{
|
||||||
|
var errorsString = ErrorsToString(validationResult.Errors.Select(e => e.ErrorMessage));
|
||||||
|
SetRequestResultError(errorsString);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetRequestResultMessage("Wait...");
|
||||||
|
|
||||||
|
var request = Mapper.Map<RegisterApplicantRequest>(requestModel);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Client.RegisterAsync(request);
|
||||||
|
SetRequestResultSuccess("Register successful. Now log in.");
|
||||||
|
}
|
||||||
|
catch (ApiException<ProblemDetails> e)
|
||||||
|
{
|
||||||
|
if (e.StatusCode == (int)HttpStatusCode.BadRequest
|
||||||
|
&& e.Result.AdditionalProperties.TryGetValue("errors", out var errors))
|
||||||
|
{
|
||||||
|
var errorsList = ((JArray)errors).ToObject<List<string>>();
|
||||||
|
if (errorsList is null)
|
||||||
|
{
|
||||||
|
throw new JsonException();
|
||||||
|
}
|
||||||
|
|
||||||
|
SetRequestResultError(ErrorsToString(errorsList));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetRequestResultError(e.Result.Detail!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
@page "/weather"
|
|
||||||
@inject HttpClient Http
|
|
||||||
|
|
||||||
<PageTitle>Weather</PageTitle>
|
|
||||||
|
|
||||||
<h1>Weather</h1>
|
|
||||||
|
|
||||||
<p>This component demonstrates fetching data from the server.</p>
|
|
||||||
|
|
||||||
@if (forecasts == null)
|
|
||||||
{
|
|
||||||
<p>
|
|
||||||
<em>Loading...</em>
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Temp. (C)</th>
|
|
||||||
<th>Temp. (F)</th>
|
|
||||||
<th>Summary</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach (var forecast in forecasts)
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>@forecast.Date.ToShortDateString()</td>
|
|
||||||
<td>@forecast.TemperatureC</td>
|
|
||||||
<td>@forecast.TemperatureF</td>
|
|
||||||
<td>@forecast.Summary</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private WeatherForecast[]? forecasts;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WeatherForecast
|
|
||||||
{
|
|
||||||
public DateOnly Date { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureC { get; set; }
|
|
||||||
|
|
||||||
public string? Summary { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using BlazorWebAssemblyVisaApiClient.Infrastructure.Services;
|
||||||
|
using FluentValidation;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
using VisaApiClient;
|
using VisaApiClient;
|
||||||
@@ -17,8 +20,12 @@ public class Program
|
|||||||
|
|
||||||
//todo make pretty
|
//todo make pretty
|
||||||
builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(baseAddress) });
|
builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(baseAddress) });
|
||||||
builder.Services.AddScoped<Client>(sp =>
|
builder.Services.AddScoped<Client>(sp => new Client(baseAddress, sp.GetRequiredService<HttpClient>()));
|
||||||
new Client(baseAddress, sp.GetRequiredService<HttpClient>()));
|
|
||||||
|
builder.Services.AddSingleton<IDateTimeProvider, DateTimeProvider>();
|
||||||
|
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
|
||||||
|
|
||||||
|
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
await builder.Build().RunAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,37 @@ a, .btn-link {
|
|||||||
content: var(--blazor-load-percentage-text, "Loading");
|
content: var(--blazor-load-percentage-text, "Loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fullscreen {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.with-centered-content {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.with-centered-content > * {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-centered-content > * {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
width: fit-content;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-block {
|
||||||
|
margin: 10px 20px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
color: #c02d76;
|
color: #c02d76;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"date": "2022-01-06",
|
|
||||||
"temperatureC": 1,
|
|
||||||
"summary": "Freezing"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"date": "2022-01-07",
|
|
||||||
"temperatureC": 14,
|
|
||||||
"summary": "Bracing"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"date": "2022-01-08",
|
|
||||||
"temperatureC": -13,
|
|
||||||
"summary": "Freezing"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"date": "2022-01-09",
|
|
||||||
"temperatureC": -16,
|
|
||||||
"summary": "Balmy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"date": "2022-01-10",
|
|
||||||
"temperatureC": -2,
|
|
||||||
"summary": "Chilly"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -20,7 +20,7 @@ public class GlobalExceptionsFilter : IAsyncExceptionFilter
|
|||||||
switch (exception)
|
switch (exception)
|
||||||
{
|
{
|
||||||
case ValidationException validationException:
|
case ValidationException validationException:
|
||||||
problemDetails.Extensions.Add("Errors", validationException.Errors.Select(e => e.ErrorMessage));
|
problemDetails.Extensions.Add("errors", validationException.Errors.Select(e => e.ErrorMessage));
|
||||||
problemDetails.Detail = "Validation errors occured";
|
problemDetails.Detail = "Validation errors occured";
|
||||||
problemDetails.Status = StatusCodes.Status400BadRequest;
|
problemDetails.Status = StatusCodes.Status400BadRequest;
|
||||||
problemDetails.Title = "Bad request";
|
problemDetails.Title = "Bad request";
|
||||||
|
|||||||
Reference in New Issue
Block a user