Add view story page (#4)

This commit is contained in:
Daniel J. Summers 2021-01-31 20:21:17 -05:00
parent a6fd891cc5
commit 46882bdfc6
10 changed files with 139 additions and 28 deletions

View File

@ -4,6 +4,7 @@ using NodaTime;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace JobsJobsJobs.Client namespace JobsJobsJobs.Client
@ -18,6 +19,17 @@ namespace JobsJobsJobs.Client
/// </summary> /// </summary>
public class AppState public class AppState
{ {
/// <summary>
/// The application version, as a nice display string
/// </summary>
public static Lazy<string> Version => new Lazy<string>(() =>
{
var version = Assembly.GetExecutingAssembly().GetName().Version!;
var display = $"v{version.Major}.{version.Minor}";
if (version.Build > 0) display += $".{version.Build}";
return display;
});
public event Action OnChange = () => { }; public event Action OnChange = () => { };
private UserInfo? _user = null; private UserInfo? _user = null;

View File

@ -13,6 +13,7 @@
<tr> <tr>
<th scope="col">Story</th> <th scope="col">Story</th>
<th scope="col">From</th> <th scope="col">From</th>
<th scope="col">Found Here?</th>
<th scope="col">Recorded On</th> <th scope="col">Recorded On</th>
</tr> </tr>
</thead> </thead>
@ -21,13 +22,30 @@
{ {
<tr> <tr>
<td> <td>
<a href="/success-story/view/@story.Id">View</a> @if (story.HasStory)
{
<a href="/success-story/view/@story.Id">View</a>
}
else
{
<em>None</em>
}
@if (story.CitizenId == state.User!.Id) @if (story.CitizenId == state.User!.Id)
{ {
<text> ~ <a href="/success-story/edit/@story.Id">Edit</a></text> <text> ~ </text><a href="/success-story/edit/@story.Id">Edit</a>
} }
</td> </td>
<td>@story.CitizenName</td> <td>@story.CitizenName</td>
<td>
@if (story.FromHere)
{
<strong>Yes</strong>
}
else
{
<text>No</text>
}
</td>
<td><FullDate TheDate=@story.RecordedOn /></td> <td><FullDate TheDate=@story.RecordedOn /></td>
</tr> </tr>
} }

View File

@ -0,0 +1,19 @@
@page "/success-story/view/{Id}"
@inject HttpClient http
@inject AppState state
<PageTitle Title="Success Story" />
<Loading OnLoad=@RetrieveStory>
<h3>@Citizen.DisplayName&rsquo;s Success Story</h3>
<h4><FullDateTime TheDate=@Story.RecordedOn /></h4>
@if (Story.FromHere)
{
<p><em><strong>Found via Jobs, Jobs, Jobs</strong></em></p>
}
<hr>
@if (Story.Story != null)
{
<div>@(new MarkupString(Story.Story.ToHtml()))</div>
}
</Loading>

View File

@ -0,0 +1,69 @@
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Threading.Tasks;
using Domain = JobsJobsJobs.Shared;
namespace JobsJobsJobs.Client.Pages.SuccessStory
{
public partial class ViewStory : ComponentBase
{
/// <summary>
/// The ID of the success story to display
/// </summary>
[Parameter]
public string Id { get; set; } = default!;
/// <summary>
/// The success story to be displayed
/// </summary>
private Domain.Success Story { get; set; } = default!;
/// <summary>
/// The citizen who authorized this success story
/// </summary>
private Domain.Citizen Citizen { get; set; } = default!;
/// <summary>
/// Retrieve the success story
/// </summary>
/// <param name="errors">The error collection via which errors will be reported</param>
public async Task RetrieveStory(ICollection<string> errors)
{
ServerApi.SetJwt(http, state);
var story = await ServerApi.RetrieveOne<Domain.Success>(http, $"success/{Id}");
if (story.IsOk)
{
if (story.Ok == null)
{
errors.Add($"Success story {Id} not found");
}
else
{
Story = story.Ok;
var citizen = await ServerApi.RetrieveOne<Domain.Citizen>(http, $"citizen/get/{Story.CitizenId}");
if (citizen.IsOk)
{
if (citizen.Ok == null)
{
errors.Add($"Citizen ID {Story.CitizenId} not found");
}
else
{
Citizen = citizen.Ok;
}
}
else
{
errors.Add(citizen.Error);
}
}
}
else
{
errors.Add(story.Error);
}
}
}
}

View File

@ -29,7 +29,6 @@ namespace JobsJobsJobs.Client
/// </summary> /// </summary>
static ServerApi() static ServerApi()
{ {
var options = new JsonSerializerOptions var options = new JsonSerializerOptions
{ {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase PropertyNamingPolicy = JsonNamingPolicy.CamelCase

View File

@ -1,7 +1,5 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace JobsJobsJobs.Client.Shared namespace JobsJobsJobs.Client.Shared

View File

@ -1,7 +1,6 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
@using System.Reflection
@inject IJSRuntime js
@using Blazored.Toast.Configuration @using Blazored.Toast.Configuration
@inject IJSRuntime js
<div class="page"> <div class="page">
<div class="sidebar"> <div class="sidebar">
@ -10,7 +9,7 @@
<div class="main"> <div class="main">
<div class="top-row px-4"> <div class="top-row px-4">
<em>(...and Jobs - <a class="audio" @onclick="PlayJobs">Let's Vote for Jobs!</a>)</em> <em>(&hellip;and Jobs - <a class="audio" @onclick="PlayJobs">Let's Vote for Jobs!</a>)</em>
</div> </div>
<div class="content px-4"> <div class="content px-4">
@ -20,22 +19,12 @@
<source src="/audio/pelosi-jobs.mp3"> <source src="/audio/pelosi-jobs.mp3">
</audio> </audio>
<div class="app-version">Jobs, Jobs, Jobs @Version</div> <div class="app-version">Jobs, Jobs, Jobs @AppState.Version.Value</div>
</div> </div>
</div> </div>
<BlazoredToasts Position="ToastPosition.BottomRight" <BlazoredToasts Position="ToastPosition.BottomRight"
ShowProgressBar="true" /> ShowProgressBar="true" />
@code { @code {
async void PlayJobs() => await js.InvokeVoidAsync("Audio.play", "pelosijobs"); async void PlayJobs() => await js.InvokeVoidAsync("Audio.play", "pelosijobs");
private string Version { get; set; } = "";
protected override void OnInitialized()
{
var version = Assembly.GetExecutingAssembly().GetName().Version!;
Version = $"v{version.Major}.{version.Minor}";
if (version.Build > 0) Version += $".{version.Build}";
base.OnInitialized();
}
} }

View File

@ -4,20 +4,20 @@
<div class="top-row pl-4 navbar navbar-dark"> <div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">Jobs, Jobs, Jobs</a> <a class="navbar-brand" href="">Jobs, Jobs, Jobs</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu"> <button class="navbar-toggler" @onclick=@ToggleNavMenu>
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
</div> </div>
<div class="@NavMenuCssClass" @onclick=@ToggleNavMenu> <div class="@NavMenuCssClass" @onclick=@ToggleNavMenu>
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match=@NavLinkMatch.All>
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
@if (state.User == null) @if (state.User == null)
{ {
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match=@NavLinkMatch.All>
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3"> <li class="nav-item px-3">
<a class="nav-link" href="@AuthUrl"> <a class="nav-link" href="@AuthUrl">
<span class="oi oi-account-login" aria-hidden="true"></span> Log On <span class="oi oi-account-login" aria-hidden="true"></span> Log On

View File

@ -28,7 +28,8 @@ namespace JobsJobsJobs.Server.Data
await db.Successes await db.Successes
.Join(db.Citizens, s => s.CitizenId, c => c.Id, (s, c) => new { Success = s, Citizen = c }) .Join(db.Citizens, s => s.CitizenId, c => c.Id, (s, c) => new { Success = s, Citizen = c })
.OrderByDescending(it => it.Success.RecordedOn) .OrderByDescending(it => it.Success.RecordedOn)
.Select(it => new StoryEntry(it.Success.Id, it.Citizen.Id, it.Citizen.DisplayName, it.Success.RecordedOn)) .Select(it => new StoryEntry(it.Success.Id, it.Citizen.Id, it.Citizen.DisplayName,
it.Success.RecordedOn, it.Success.FromHere, it.Success.Story != null))
.ToListAsync().ConfigureAwait(false); .ToListAsync().ConfigureAwait(false);
} }
} }

View File

@ -5,5 +5,11 @@ namespace JobsJobsJobs.Shared.Api
/// <summary> /// <summary>
/// An entry in the list of success stories /// An entry in the list of success stories
/// </summary> /// </summary>
public record StoryEntry(SuccessId Id, CitizenId CitizenId, string CitizenName, Instant RecordedOn); public record StoryEntry(
SuccessId Id,
CitizenId CitizenId,
string CitizenName,
Instant RecordedOn,
bool FromHere,
bool HasStory);
} }