Form style / continents

Toward #2 completion; next up, Markdown input fields...
This commit is contained in:
Daniel J. Summers 2020-12-26 21:51:19 -05:00
parent 47e32fd475
commit 4ba5426068
9 changed files with 227 additions and 51 deletions

View File

@ -13,42 +13,78 @@ else if (profileForm != null)
{ {
<EditForm Model="@profileForm" OnValidSubmit="@SaveProfile"> <EditForm Model="@profileForm" OnValidSubmit="@SaveProfile">
<DataAnnotationsValidator /> <DataAnnotationsValidator />
<ValidationSummary /> <div class="form-row">
<label> <div class="col col-xs-12 col-sm-12 col-md-4">
<InputCheckbox @bind-Value="@profileForm.IsSeekingEmployment" /> <div class="form-check">
I am currently seeking employment <InputCheckbox id="seeking" class="form-check-input" @bind-Value="@profileForm.IsSeekingEmployment" />
</label> <label for="seeking" class="form-check-label">I am currently seeking employment</label>
<label> </div>
<InputCheckbox @bind-Value="@profileForm.IsPublic" /> </div>
<div class="col col-xs-12 col-sm-12 col-md-8">
<div class="form-check">
<InputCheckbox id="isPublic" class="form-check-input" @bind-Value="@profileForm.IsPublic" />
<label for="isPublic" class="form-check-label">
Allow my profile to be searched publicly (outside NA Social) Allow my profile to be searched publicly (outside NA Social)
</label><br>
<label>
Continent
<InputSelect @bind-Value="@profileForm.ContinentId" />
</label> </label>
<label> </div>
Region </div>
<InputText @bind-Value="@profileForm.Region" /> </div>
</label><br> <div class="form-row">
<label> <div class="col col-xs-12 col-sm-12 col-md-4">
<InputCheckbox @bind-Value="@profileForm.RemoteWork" /> <div class="form-check">
I am looking for remote work <InputCheckbox id="isRemote" class="form-check-input" @bind-Value="@profileForm.RemoteWork" />
</label> <label for="isRemote" class="form-check-label">I am looking for remote work</label>
<label> </div>
<InputCheckbox @bind-Value="@profileForm.FullTime" /> </div>
I am looking for full-time work <div class="col col-xs-12 col-sm-12 col-md-8">
</label><br> <div class="form-check">
<label> <InputCheckbox id="isFull" class="form-check-input" @bind-Value="@profileForm.FullTime" />
Professional Biography <label for="isFull" class="form-check-label">I am looking for full-time work</label>
<InputTextArea @bind-Value="@profileForm.Biography" /> </div>
</label><br> </div>
<label> </div>
Experience <div class="form-row">
<InputTextArea @bind-Value="@profileForm.Experience" /> <div class="col col-xs-12 col-sm-6 col-md-4">
</label> <div class="form-group">
<p> <label for="continentId">Continent</label>
<InputSelect id="continentId" @bind-Value="@profileForm.ContinentId" class="form-control">
<option>&ndash; Select &ndash;</option>
@foreach (var (id, name) in continents)
{
<option value="@id">@name</option>
}
</InputSelect>
<ValidationMessage For="@(() => profileForm.ContinentId)" />
</div>
</div>
<div class="col col-xs-12 col-sm-6 col-md-8">
<div class="form-group">
<label for="region">Region</label>
<InputText id="region" @bind-Value="@profileForm.Region" class="form-control" />
<ValidationMessage For="@(() => profileForm.Region)" />
</div>
</div>
</div>
<div class="form-row">
<div class="col">
<div class="form-group">
<label for="bio">Professional Biography</label>
<InputTextArea id="bio" @bind-Value="@profileForm.Biography" class="form-control" />
<ValidationMessage For="@(() => profileForm.Biography)" />
</div>
</div>
</div>
<div class="form-row">
<div class="col">
<label for="experience">Experience</label>
<InputTextArea id="experience" @bind-Value="@profileForm.Experience" class="form-control" />
</div>
</div>
<div class="form-row">
<div class="col">
<button type="submit">Save</button> <button type="submit">Save</button>
</p> </div>
</div>
</EditForm> </EditForm>
} }
@ -58,10 +94,22 @@ else if (profileForm != null)
public ProfileForm? profileForm = null; public ProfileForm? profileForm = null;
private IEnumerable<Continent> continents = Enumerable.Empty<Continent>();
public string errorMessage = ""; public string errorMessage = "";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
var continentResult = await ServerApi.AllContinents(http, state);
if (continentResult.IsOk)
{
continents = continentResult.Ok;
}
else
{
errorMessage = continentResult.Error;
}
var result = await ServerApi.RetrieveProfile(http, state); var result = await ServerApi.RetrieveProfile(http, state);
if (result.IsOk) if (result.IsOk)
{ {

View File

@ -77,5 +77,22 @@ namespace JobsJobsJobs.Client
_ => Result<Profile?>.AsError(await res.Content.ReadAsStringAsync()), _ => Result<Profile?>.AsError(await res.Content.ReadAsStringAsync()),
}; };
} }
/// <summary>
/// Retrieve all continents
/// </summary>
/// <param name="http">The HTTP client to use for server communication</param>
/// <param name="state">The current application state</param>
/// <returns>The continents, or an error message if one occurs</returns>
public static async Task<Result<IEnumerable<Continent>>> AllContinents(HttpClient http, AppState state)
{
var req = WithHeader(state, "continent/all");
var res = await http.SendAsync(req);
if (res.IsSuccessStatusCode) {
var continents = await res.Content.ReadFromJsonAsync<IEnumerable<Continent>>();
return Result<IEnumerable<Continent>>.AsOk(continents ?? Enumerable.Empty<Continent>());
}
return Result<IEnumerable<Continent>>.AsError(await res.Content.ReadAsStringAsync());
}
} }
} }

View File

@ -9,16 +9,34 @@ using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Areas.Api.Controllers namespace JobsJobsJobs.Server.Areas.Api.Controllers
{ {
/// <summary>
/// API controller for citizen information
/// </summary>
[Route("api/[controller]")] [Route("api/[controller]")]
[ApiController] [ApiController]
public class CitizenController : ControllerBase public class CitizenController : ControllerBase
{ {
/// <summary>
/// Authorization configuration section
/// </summary>
private readonly IConfigurationSection _config; private readonly IConfigurationSection _config;
/// <summary>
/// NodaTime clock
/// </summary>
private readonly IClock _clock; private readonly IClock _clock;
/// <summary>
/// The data connection to use for this request
/// </summary>
private readonly NpgsqlConnection _db; private readonly NpgsqlConnection _db;
/// <summary>
/// Constructor
/// </summary>
/// <param name="config">The authorization configuration section</param>
/// <param name="clock">The NodaTime clock instance</param>
/// <param name="db">The data connection to use for this request</param>
public CitizenController(IConfiguration config, IClock clock, NpgsqlConnection db) public CitizenController(IConfiguration config, IClock clock, NpgsqlConnection db)
{ {
_config = config.GetSection("Auth"); _config = config.GetSection("Auth");

View File

@ -0,0 +1,38 @@
using JobsJobsJobs.Server.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Npgsql;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Areas.Api.Controllers
{
/// <summary>
/// API endpoint for continent information
/// </summary>
[Route("api/[controller]")]
[Authorize]
[ApiController]
public class ContinentController : ControllerBase
{
/// <summary>
/// The database connection to use for this request
/// </summary>
private readonly NpgsqlConnection _db;
/// <summary>
/// Constructor
/// </summary>
/// <param name="db">The database connection to use for this request</param>
public ContinentController(NpgsqlConnection db)
{
_db = db;
}
[HttpGet("all")]
public async Task<IActionResult> All()
{
await _db.OpenAsync();
return Ok(await _db.AllContinents());
}
}
}

View File

@ -13,6 +13,9 @@ using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Areas.Api.Controllers namespace JobsJobsJobs.Server.Areas.Api.Controllers
{ {
/// <summary>
/// API controller for employment profile information
/// </summary>
[Route("api/[controller]")] [Route("api/[controller]")]
[ApiController] [ApiController]
public class ProfileController : ControllerBase public class ProfileController : ControllerBase
@ -20,19 +23,23 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
/// <summary> /// <summary>
/// The database connection /// The database connection
/// </summary> /// </summary>
private readonly NpgsqlConnection db; private readonly NpgsqlConnection _db;
public ProfileController(NpgsqlConnection dbConn) /// <summary>
/// Constructor
/// </summary>
/// <param name="db">The database connection to use for this request</param>
public ProfileController(NpgsqlConnection db)
{ {
db = dbConn; _db = db;
} }
[Authorize] [Authorize]
[HttpGet("")] [HttpGet("")]
public async Task<IActionResult> Get() public async Task<IActionResult> Get()
{ {
await db.OpenAsync(); await _db.OpenAsync();
var profile = await db.FindProfileByCitizen( var profile = await _db.FindProfileByCitizen(
CitizenId.Parse(User.FindFirst(ClaimTypes.NameIdentifier)!.Value)); CitizenId.Parse(User.FindFirst(ClaimTypes.NameIdentifier)!.Value));
return profile == null ? NoContent() : Ok(profile); return profile == null ? NoContent() : Ok(profile);
} }

View File

@ -0,0 +1,40 @@
using JobsJobsJobs.Shared;
using Npgsql;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Data
{
/// <summary>
/// Data extensions for manipulation of continent objects
/// </summary>
public static class ContinentExtensions
{
/// <summary>
/// Create a continent from the current row in the data reader
/// </summary>
/// <param name="rdr">The data reader</param>
/// <returns>The current row's values as a continent object</returns>
private static Continent ToContinent(NpgsqlDataReader rdr) =>
new Continent(ContinentId.Parse(rdr.GetString("id")), rdr.GetString("name"));
/// <summary>
/// Retrieve all continents
/// </summary>
/// <returns>All continents</returns>
public static async Task<IEnumerable<Continent>> AllContinents(this NpgsqlConnection conn)
{
using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM continent ORDER BY name";
using var rdr = await cmd.ExecuteReaderAsync().ConfigureAwait(false);
var continents = new List<Continent>();
while (await rdr.ReadAsync())
{
continents.Add(ToContinent(rdr));
}
return continents;
}
}
}

View File

@ -4,8 +4,8 @@
<RazorPage_SelectedScaffolderID>RazorPageScaffolder</RazorPage_SelectedScaffolderID> <RazorPage_SelectedScaffolderID>RazorPageScaffolder</RazorPage_SelectedScaffolderID>
<RazorPage_SelectedScaffolderCategoryPath>root/Common/RazorPage</RazorPage_SelectedScaffolderCategoryPath> <RazorPage_SelectedScaffolderCategoryPath>root/Common/RazorPage</RazorPage_SelectedScaffolderCategoryPath>
<ActiveDebugProfile>JobsJobsJobs.Server</ActiveDebugProfile> <ActiveDebugProfile>JobsJobsJobs.Server</ActiveDebugProfile>
<Controller_SelectedScaffolderID>ApiControllerEmptyScaffolder</Controller_SelectedScaffolderID> <Controller_SelectedScaffolderID>MvcControllerEmptyScaffolder</Controller_SelectedScaffolderID>
<Controller_SelectedScaffolderCategoryPath>root/Common/Api</Controller_SelectedScaffolderCategoryPath> <Controller_SelectedScaffolderCategoryPath>root/Common/MVC/Controller</Controller_SelectedScaffolderCategoryPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor> <DebuggerFlavor>ProjectDebugger</DebuggerFlavor>

View File

@ -36,6 +36,14 @@ COMMENT ON COLUMN jjj.continent.id
COMMENT ON COLUMN jjj.continent.name COMMENT ON COLUMN jjj.continent.name
IS 'The name of the continent'; IS 'The name of the continent';
INSERT INTO jjj.continent VALUES ('QbpCTd3mDbTU', 'Africa');
INSERT INTO jjj.continent VALUES ('yHGzQvD80uS9', 'Antarctica');
INSERT INTO jjj.continent VALUES ('jEvaGTKwnDB8', 'Asia');
INSERT INTO jjj.continent VALUES ('mUN8AOddmx2i', 'Australia');
INSERT INTO jjj.continent VALUES ('-TXN-0Cvn_RT', 'Europe');
INSERT INTO jjj.continent VALUES ('2ELDbrTvVSPO', 'North America');
INSERT INTO jjj.continent VALUES ('XxnCcCHu8wqL', 'South America');
CREATE TABLE jjj.profile ( CREATE TABLE jjj.profile (
citizen_id VARCHAR(12) NOT NULL, citizen_id VARCHAR(12) NOT NULL,
seeking_employment BOOLEAN NOT NULL, seeking_employment BOOLEAN NOT NULL,