Eerste commit

This commit is contained in:
jf
2026-09-27 14:21:31 +00:00
commit de4ff128cb
295 changed files with 47374 additions and 0 deletions
@@ -0,0 +1,359 @@
@page "/"
@attribute [Authorize]
@inject OverzichtService Overzichten
@inject LegacyService NalatenschapService
@inject AdvertentieService Adverteren
@inject AccountService Accounts
@inject SamenvoegService Samenvoegen
@rendermode InteractiveServer
<PageTitle>MijnBesluit</PageTitle>
@* Het beginscherm, met dezelfde indeling als in de app: eerst wat er speelt, dan de tegels.
De aantallen staan achter de titel in plaats van op een regel eronder, zodat de tegels
laag blijven. *@
<AuthorizeView Context="auth">
<Authorized>
<h1>@Begroeting(auth.User.Naam())</h1>
@if (Fout is not null)
{
<div class="melding fout">@Fout</div>
}
@if (Melding is not null)
{
<div class="melding goed">@Melding</div>
}
@* Heeft iemand een profiel voor je aangemaakt op jouw adres? Dan staat die vraag hier,
bovenaan, tot je hem beantwoordt. Alleen jij kunt zeggen of dat over jou gaat;
koppelen op e-mailadres doet MijnBesluit niet. Zie docs/Identiteit.md. *@
@foreach (var aantekening in Aantekeningen)
{
<div class="kaart" style="margin-bottom:1rem">
<h2 style="margin-top:0">Ben jij dit?</h2>
<p style="margin-top:0">
<strong>@aantekening.Van</strong> heeft een profiel voor je aangemaakt op jouw
e-mailadres@(aantekening.Relatie is { Length: > 0 } r ? $", als {r.ToLowerInvariant()}" : "").
</p>
<p class="zacht" style="margin-top:0">
Ben jij dat, dan voegen we het bij je eigen profiel: wat hij over je heeft
ingevuld komt bij jou te staan, en vanaf dat moment bepaal jij wat er staat.
Ben jij het niet, dan blijft alles zoals het is en vragen we het niet nog eens.
</p>
<div class="knoppen">
<button class="knop vol" disabled="@Bezig"
@onclick="() => Overnemen(aantekening.PersoonId)">Ja, dat ben ik</button>
<button class="knop rustig" disabled="@Bezig"
@onclick="() => Afwijzen(aantekening.PersoonId)">Nee, dat ben ik niet</button>
</div>
</div>
}
@if (Gegevens is { Regels.Count: > 0 })
{
<div class="stapel speelt">
@foreach (var regel in Gegevens.Regels)
{
<div class="regel @(regel.Soort == "overlijden" ? "let-op" : "")">
<span class="teken">@Teken(regel.Soort)</span>
<div class="groei">
<div>@regel.Tekst</div>
@if (regel.Datum is { } datum)
{
<div class="zacht">@Datums.MetDag(datum)</div>
}
</div>
</div>
}
</div>
@if (Gegevens.Regels.Any(r => r.Soort == "overlijden"))
{
<button class="knop" style="margin-bottom:1rem" disabled="@Bezig" @onclick="IkBenErNog">
Ik ben er nog
</button>
}
}
@* De advertentie staat hier en niet bovenaan: "wat er speelt" gaat voor. Iemand die net
leest dat er een overlijden op zijn naam is gemeld, hoort daar geen reclame boven te
zien. Zie docs/Adverteren.md. *@
@if (Reclame is not null)
{
<a class="advertentie" href="/advertentie/@Reclame.Id/uit"
target="_blank" rel="noopener noreferrer nofollow">
@if (Reclame.HeeftAfbeelding)
{
<img src="/advertentie/@Reclame.Id/afbeelding" alt="" />
}
<span class="groei">
<span class="merkje">Advertentie</span>
<span class="tekst">@Reclame.Tekst</span>
</span>
</a>
}
<div class="raster twee">
<a class="kaart klikbaar" href="/kluis">
<span class="groot">Mijn kluis <span class="aantal">@(Gegevens?.AantalKluis ?? 0)</span></span>
</a>
<a class="kaart klikbaar" href="/familie">
<span class="groot">Mijn mensen <span class="aantal">@(Gegevens?.AantalFamilie ?? 0)</span></span>
</a>
@* Twee richtingen, dus twee aantallen; die staan achter de titel in hetzelfde
rondje als de andere tegels, met hun naam erbij zodat je niet hoeft te raden
welke welke is. Zo past de tegel op één regel, even hoog als de andere drie. *@
<a class="kaart klikbaar" href="/momenten">
<span class="groot">
Momenten
<span class="aantal" title="@(Gegevens?.AantalVoorMij ?? 0) voor mij, @(Gegevens?.AantalVanMij ?? 0) van mij">
&darr;@(Gegevens?.AantalVoorMij ?? 0) &middot; &uarr;@(Gegevens?.AantalVanMij ?? 0)
</span>
@if (Gegevens is { AantalNieuweBerichten: > 0 })
{
<span class="aantal nieuw">@Gegevens.AantalNieuweBerichten</span>
}
</span>
</a>
<a class="kaart klikbaar" href="/nalatenschap">
<span class="groot">Nalatenschap <span class="aantal">@(Gegevens?.AantalBeheerders ?? 0)</span></span>
</a>
</div>
@if (Gegevens is not null)
{
<details class="uitklap" style="margin-top:1.5rem">
<summary>Hoe ver vooruit wil je kijken?</summary>
<p>
Hierboven zie je wat er de komende tijd voor je klaarstaat. Zet het op 0 als je
liever verrast wordt.
</p>
<div class="rij">
<select @bind="Dagen" @bind:after="VooruitblikOpslaan" style="max-width:14rem">
<option value="0">Niets vooruit tonen</option>
<option value="7">7 dagen</option>
<option value="14">14 dagen</option>
<option value="30">30 dagen</option>
<option value="60">60 dagen</option>
</select>
</div>
</details>
}
@* Dezelfde uitleg als in de app en op de pagina /watismijnbesluit, maar hier als popup:
je hoeft dan niet weg van je overzicht en weer terug.
De privacyverklaring staat er om dezelfde reden bij, en op dezelfde plek als in de app.
Er is ook een pagina /privacy - die is er voor wie hem los wil kunnen delen en voor
Google Play, dat een adres wil dat je kunt aanklikken. Maar wie hier zit, hoort hem te
kunnen lezen zonder zijn overzicht kwijt te raken. De tekst zelf staat in
Privacytekst.razor, hetzelfde onderdeel dat /privacy en de app gebruiken. *@
<p style="margin-top:1rem">
<button type="button" class="alsLink" @onclick="() => Uitleg = true">Wat is MijnBesluit?</button>
<br />
<button type="button" class="alsLink" @onclick="() => Privacy = true">Privacyverklaring</button>
<br />
<button type="button" class="alsLink" @onclick="() => VersiehistorieOpen = true">Versiehistorie</button>
</p>
@if (Uitleg)
{
<Popup Titel="Wat is MijnBesluit?" Bevestigtekst="Sluiten" AlleenSluiten="true"
OpBevestigen="() => Uitleg = false" OpSluiten="() => Uitleg = false">
<Productuitleg />
</Popup>
}
@if (Privacy)
{
<Popup Titel="Privacy" Bevestigtekst="Sluiten" AlleenSluiten="true"
OpBevestigen="() => Privacy = false" OpSluiten="() => Privacy = false">
<Privacytekst />
</Popup>
}
@* Zelfde tekst als in de app: Versiehistorie.razor staat twee keer op schijf en een
zelfcontrole vergelijkt ze letterlijk. *@
@if (VersiehistorieOpen)
{
<Popup Titel="Versiehistorie" Bevestigtekst="Sluiten" AlleenSluiten="true"
OpBevestigen="() => VersiehistorieOpen = false" OpSluiten="() => VersiehistorieOpen = false">
<Versiehistorie />
</Popup>
}
<div class="kaart">
<h2>Waar is MijnBesluit voor?</h2>
<p class="zacht" style="margin-bottom:.8rem">
Voor alles wat je zou willen doorgeven maar wat nu alleen in jouw hoofd zit.
</p>
<ul class="zacht" style="padding-left:1.2rem;margin:0">
<li>
<strong>Je kluis</strong> &mdash; wachtwoorden, de pincode van je telefoon,
polissen, documenten, je uitvaartwensen. Alles versleuteld, en jij bepaalt per
onderdeel of het alleen voor jou is, pas na je overlijden vrijkomt, of op een
datum die je zelf kiest.
</li>
<li>
<strong>Je mensen</strong> &mdash; familie en vrienden die je toevoegt en
uitnodigt. Zolang iemand de uitnodiging niet heeft aangenomen, beheer jij dat
profiel; daarna is het van hem of haar.
</li>
<li>
<strong>Momenten</strong> &mdash; een tekst- of videobericht dat je klaarzet
voor iemand. Te bezorgen op een datum die jij kiest, of pas na je overlijden.
Een verjaardag die je niet meer meemaakt, of gewoon iets wat je nog wilt zeggen.
</li>
<li>
<strong>Je nalatenschap</strong> &mdash; wie er straks mag regelen wat er
geregeld moet worden. Die persoon krijgt pas toegang als je overlijden is
bevestigd door meerdere mensen én jij er zelf niet op hebt gereageerd.
</li>
</ul>
</div>
</Authorized>
</AuthorizeView>
@code {
[CascadingParameter] private Task<AuthenticationState>? AuthState { get; set; }
private long _gebruikerId, _persoonId;
private Overzicht? Gegevens;
private int Dagen = 30;
/// <summary>Profielen die anderen op mijn adres hebben aangemaakt; alleen ik kan zeggen of ik dat ben.</summary>
private IReadOnlyList<AccountService.Aantekeningaanbod> Aantekeningen =
Array.Empty<AccountService.Aantekeningaanbod>();
/// <summary>Staat de uitleg open?</summary>
private bool Uitleg;
/// <summary>Staat de privacyverklaring open?</summary>
private bool Privacy;
private bool VersiehistorieOpen;
/// <summary>De advertentie voor deze keer, of null als er geen is.</summary>
private Campagne? Reclame;
private bool Bezig;
private string? Fout, Melding;
protected override async Task OnInitializedAsync()
{
if (AuthState is null) return;
var user = (await AuthState).User;
_gebruikerId = user.UserId() ?? 0;
_persoonId = user.PersonId() ?? 0;
await Laden();
}
private async Task Laden()
{
if (_gebruikerId == 0) return;
var uitkomst = await Overzichten.VoorAsync(_gebruikerId);
if (!uitkomst.Succeeded) { Fout = uitkomst.Error; return; }
Fout = null;
Gegevens = uitkomst.Value;
Dagen = Gegevens!.VooruitblikDagen;
// De keuze en het tellen horen bij elkaar: wat hier wordt uitgekozen komt ook echt in
// beeld. Eén advertentie per keer dat het overzicht wordt geopend; zie docs/Adverteren.md.
//
// Gaat er hier iets mis, dan hoort het overzicht er gewoon te staan. Reclame is het
// minst belangrijke op dit scherm.
try
{
Reclame = await Adverteren.KiesAsync();
if (Reclame is not null) await Adverteren.TelVertoningAsync(Reclame.Id);
}
catch (Exception)
{
Reclame = null;
}
// Heeft iemand een profiel voor mij aangemaakt? Apart opgehaald, en een fout hier hoort
// het overzicht niet weg te houden: dit is een vraag die best een keer later mag komen.
try { Aantekeningen = await Accounts.AantekeningenOverMijAsync(_gebruikerId); }
catch (Exception) { Aantekeningen = Array.Empty<AccountService.Aantekeningaanbod>(); }
}
/// <summary>"Ja, dat ben ik." Daarna staat alles wat die ander over je had bij jou.</summary>
private async Task Overnemen(long persoonId)
{
Bezig = true;
try
{
var uitkomst = await Accounts.NeemAantekeningOverAsync(persoonId, _gebruikerId, Samenvoegen);
Fout = uitkomst.Succeeded ? null : uitkomst.Error;
Melding = uitkomst.Succeeded ? "Klaar. Het profiel staat nu bij jou." : null;
if (uitkomst.Succeeded) await Laden();
}
finally { Bezig = false; }
}
/// <summary>"Nee, dat ben ik niet." De vraag komt niet terug.</summary>
private async Task Afwijzen(long persoonId)
{
Bezig = true;
try
{
var uitkomst = await Accounts.WijsAantekeningAfAsync(persoonId, _gebruikerId);
Fout = uitkomst.Succeeded ? null : uitkomst.Error;
if (uitkomst.Succeeded)
Aantekeningen = Aantekeningen.Where(a => a.PersoonId != persoonId).ToList();
}
finally { Bezig = false; }
}
private async Task VooruitblikOpslaan()
{
Bezig = true;
try
{
var uitkomst = await Overzichten.VooruitblikAsync(_gebruikerId, Dagen);
if (!uitkomst.Succeeded) { Fout = uitkomst.Error; return; }
await Laden();
}
finally { Bezig = false; }
}
private async Task IkBenErNog()
{
Bezig = true;
try
{
var uitkomst = await NalatenschapService.ObjectAsync(_persoonId, _gebruikerId);
Melding = uitkomst.Succeeded ? "Genoteerd. De melding is ingetrokken." : null;
Fout = uitkomst.Succeeded ? null : uitkomst.Error;
if (uitkomst.Succeeded) await Laden();
}
finally { Bezig = false; }
}
private static string Teken(string soort) => soort switch
{
"beheerder" => "\U0001F33F", // blad: iemand beheert jouw nalatenschap
"beheer-voor" => "\U0001F91D", // handdruk: jij beheert die van een ander
"moment" => "✉", // envelop
"overlijden" => "⚠", // waarschuwing
_ => "•"
};
/// <summary>
/// "Dag Jonathan" als we de naam kennen, anders alleen "Welkom". Nooit het e-mailadres:
/// dat is geen naam, en het staat raar boven een scherm over je nalatenschap.
/// </summary>
private static string Begroeting(string naam)
{
var voornaam = naam.Split(' ', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
return string.IsNullOrWhiteSpace(voornaam) ? "Welkom" : $"Dag {voornaam}";
}
}