feat(admin): runtime feature flags (catalog-in-code, admin toggle, FE+backend)
Catalog declared in code (Domain/Features/FeatureFlags.cs, build-validated), on/off state
persisted in SQLite (FeatureFlagStore + migration). GET /flags (drives FE gating) + admin
PUT /admin/flags/{key} (new flags:manage capability + FlagsAdmin gate). Enforced end-to-end:
the `inschrijving-open` flag hides the Inschrijven nav item + dashboard action (FE) AND makes
POST /applications for a registratie 403 when off (backend). FE FeatureFlagStore mirrors
AccessStore (enabled() deny-by-default); admin toggle page at /beheer/functies in ADMIN_LINKS.
+4 backend tests, /me cap-list updated, client regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@ import { ApplicationListComponent } from '@shared/ui/application-list/applicatio
|
||||
import { ApplicationLinkComponent } from '@shared/ui/application-link/application-link.component';
|
||||
import { ASYNC } from '@shared/ui/async/async.component';
|
||||
import { AccessStore } from '@shared/application/access.store';
|
||||
import { FeatureFlagStore } from '@shared/application/feature-flags.store';
|
||||
import { FLAG_INSCHRIJVING_OPEN } from '@shared/domain/feature-flag';
|
||||
import { ADMIN_LINKS } from '@shared/layout/admin-links';
|
||||
import { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';
|
||||
import { RegistrationTableComponent } from '@registratie/ui/registration-table/registration-table.component';
|
||||
@@ -176,7 +178,7 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
|
||||
<section>
|
||||
<app-heading [level]="2" i18n="@@dashboard.watWiltUDoen">Wat wilt u doen?</app-heading>
|
||||
<app-application-list class="app-section">
|
||||
@for (a of acties; track a.to) {
|
||||
@for (a of acties(); track a.to) {
|
||||
<li
|
||||
app-application-link
|
||||
[heading]="a.titel"
|
||||
@@ -211,6 +213,7 @@ export class DashboardPage {
|
||||
protected store = inject(BigProfileStore);
|
||||
private apps = inject(ApplicationsStore);
|
||||
private access = inject(AccessStore);
|
||||
private flags = inject(FeatureFlagStore);
|
||||
private router = inject(Router);
|
||||
|
||||
/** Admin pages the current principal may reach — capability-gated (never role-derived),
|
||||
@@ -282,7 +285,7 @@ export class DashboardPage {
|
||||
/** Primary transactional actions, as an "aanvragen" list (see CIBG's
|
||||
componenten/aanvragen). The core portal sections live in the header nav now;
|
||||
the teaching pages (concepts/brief) are only reachable from here. */
|
||||
protected readonly acties = [
|
||||
private readonly allActies = [
|
||||
{
|
||||
to: '/registreren',
|
||||
titel: $localize`:@@dashboard.actie.inschrijven.titel:Inschrijven`,
|
||||
@@ -320,4 +323,11 @@ export class DashboardPage {
|
||||
actie: $localize`:@@dashboard.actie.brief.actie:Start brief`,
|
||||
},
|
||||
];
|
||||
|
||||
/** Hide the "Inschrijven" action when self-service registration is flagged off (WP-47). */
|
||||
protected readonly acties = computed(() =>
|
||||
this.allActies.filter(
|
||||
(a) => a.to !== '/registreren' || this.flags.enabled(FLAG_INSCHRIJVING_OPEN),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user