Form style / continents
Toward #2 completion; next up, Markdown input fields...
This commit is contained in:
parent
47e32fd475
commit
4ba5426068
|
@ -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>– Select –</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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
40
src/JobsJobsJobs/Server/Data/ContinentExtensions.cs
Normal file
40
src/JobsJobsJobs/Server/Data/ContinentExtensions.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user