diff --git a/documentation.json b/documentation.json index 4fbdf98..19c6bb1 100644 --- a/documentation.json +++ b/documentation.json @@ -4,7 +4,7 @@ { "name": "Aantekening", "id": "interface-Aantekening-2ecca36e413e22c1c5967d07b811fa4171ff578eb6b47a7275723bcca44e93a2db27d77a765b82e5f6182492464cc8b9268c9724e13b400718d30da7bc4ba50c", - "file": "src/app/core/models.ts", + "file": "src/app/registratie/domain/registration.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", @@ -47,13 +47,97 @@ "extends": [] }, { - "name": "ChangeRequest", - "id": "interface-ChangeRequest-e366867f9f2a82d4c6cafc6fbdce9749e7775f651192c4de62cefc40a237a6e2c6772537e64a4aeeb6667ffa33d9a69ee6114c69160674807408090b1cbc283a", - "file": "src/app/organisms/change-request-form/change-request-form.component.ts", + "name": "Adres", + "id": "interface-Adres-07cec46d80a41919e40d0bcb002981627d1a30edb363d64a0a140a8af2c38ad85be5603d88030d0704870800694c5bed9c709ae6891fe83e646e90c1a67cf548", + "file": "src/app/registratie/domain/person.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", - "sourceCode": "import { Component, output, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { Postcode, parsePostcode } from '../../core/parse';\n\n/** A submitted change request carries a *parsed* postcode (branded Postcode),\n not a raw string — downstream code can't receive an unvalidated one. */\nexport interface ChangeRequest {\n street: string;\n zip: Postcode;\n city: string;\n}\n\n/** Organism: change-request (adreswijziging) form. Reuses the same form-field\n molecule + text-input/button atoms as the login form. Field errors come\n straight from the parser's Result — no parallel \"is it valid\" flag to drift. */\n@Component({\n selector: 'app-change-request-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, HeadingComponent],\n template: `\n Adreswijziging doorgeven\n
\n \n \n \n \n \n \n \n \n \n
\n Wijziging indienen\n
\n
\n `,\n})\nexport class ChangeRequestFormComponent {\n street = '';\n zip = '';\n city = '';\n streetError = signal('');\n zipError = signal('');\n submitted = output();\n\n onSubmit() {\n const street = this.street.trim();\n this.streetError.set(street ? '' : 'Vul straat en huisnummer in.');\n\n const postcode = parsePostcode(this.zip);\n this.zipError.set(postcode.ok ? '' : postcode.error);\n\n if (!street || !postcode.ok) return;\n this.submitted.emit({ street, zip: postcode.value, city: this.city.trim() });\n }\n}\n", + "sourceCode": "export interface Adres {\n straat: string;\n postcode: string;\n woonplaats: string;\n}\n\nexport interface Person {\n naam: string;\n geboortedatum: string; // ISO date\n adres: Adres;\n}\n", + "properties": [ + { + "name": "postcode", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 4 + }, + { + "name": "straat", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 3 + }, + { + "name": "woonplaats", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 5 + } + ], + "indexSignatures": [], + "kind": 172, + "description": "

Person identity as supplied by the BRP (Basisregistratie Personen).

\n", + "rawdescription": "\nPerson identity as supplied by the BRP (Basisregistratie Personen).", + "methods": [], + "extends": [] + }, + { + "name": "BigProfile", + "id": "interface-BigProfile-c363e317899ff3cd424b0033c2f57ea14e9617f52401171543b5a82d67046a894a2806afc198812808d0080b3093471e081f7e79b12f2c8c37eef68785e47a43", + "file": "src/app/registratie/domain/big-profile.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "import { Registration } from './registration';\nimport { Person } from './person';\n\n/**\n * The view the dashboard/detail render: a registration (from the BIG-register)\n * enriched with person data (from the BRP). It only exists when BOTH sources\n * have loaded — see BigProfileStore, which builds it with map2.\n */\nexport interface BigProfile {\n registration: Registration;\n person: Person;\n}\n", + "properties": [ + { + "name": "person", + "deprecated": false, + "deprecationMessage": "", + "type": "Person", + "indexKey": "", + "optional": false, + "description": "", + "line": 11 + }, + { + "name": "registration", + "deprecated": false, + "deprecationMessage": "", + "type": "Registration", + "indexKey": "", + "optional": false, + "description": "", + "line": 10 + } + ], + "indexSignatures": [], + "kind": 172, + "description": "

The view the dashboard/detail render: a registration (from the BIG-register)\nenriched with person data (from the BRP). It only exists when BOTH sources\nhave loaded — see BigProfileStore, which builds it with map2.

\n", + "rawdescription": "\n\nThe view the dashboard/detail render: a registration (from the BIG-register)\nenriched with person data (from the BRP). It only exists when BOTH sources\nhave loaded — see BigProfileStore, which builds it with map2.\n", + "methods": [], + "extends": [] + }, + { + "name": "ChangeRequest", + "id": "interface-ChangeRequest-8d79b6a9ce75f62f4e04f9d3ed0867e00d005c9c05cf267503536c4eec9813372aff51a3c8686927fd07f46503b60ddb4107edcc5f57fdfa1b6b52fe2a98026c", + "file": "src/app/registratie/ui/change-request-form/change-request-form.component.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "import { Component, output, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '@shared/ui/form-field/form-field.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { HeadingComponent } from '@shared/ui/heading/heading.component';\nimport { Postcode, parsePostcode } from '@registratie/domain/value-objects/postcode';\n\n/** A submitted change request carries a *parsed* postcode (branded Postcode),\n not a raw string — downstream code can't receive an unvalidated one. */\nexport interface ChangeRequest {\n street: string;\n zip: Postcode;\n city: string;\n}\n\n/** Organism: change-request (adreswijziging) form. Reuses the same form-field\n molecule + text-input/button atoms as the login form. Field errors come\n straight from the parser's Result — no parallel \"is it valid\" flag to drift. */\n@Component({\n selector: 'app-change-request-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, HeadingComponent],\n template: `\n Adreswijziging doorgeven\n
\n \n \n \n \n \n \n \n \n \n
\n Wijziging indienen\n
\n
\n `,\n})\nexport class ChangeRequestFormComponent {\n street = '';\n zip = '';\n city = '';\n streetError = signal('');\n zipError = signal('');\n submitted = output();\n\n onSubmit() {\n const street = this.street.trim();\n this.streetError.set(street ? '' : 'Vul straat en huisnummer in.');\n\n const postcode = parsePostcode(this.zip);\n this.zipError.set(postcode.ok ? '' : postcode.error);\n\n if (!street || !postcode.ok) return;\n this.submitted.emit({ street, zip: postcode.value, city: this.city.trim() });\n }\n}\n", "properties": [ { "name": "city", @@ -95,12 +179,12 @@ }, { "name": "Draft", - "id": "interface-Draft-6fcaabe58c126a645622a7a77942a847877e8d80e60ddd45cb9ba4c247cede3ee197def542b645ef02b0ce5dd0deab6c644edddf2f37dec67a129b27ba53bb23", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "id": "interface-Draft-6d0ec48dfedad1e9d3dc32caaf0639d35f1430f1e8081a0a719ad0675d06912a90de0d8159326e13c9cc3c8f82521e3fded8d06a5feb1480be4c084a967e6aad", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", - "sourceCode": "import { Result } from '../../core/fp';\nimport { Uren, parseUren } from '../../core/parse';\n\n/** What the user is typing (raw, possibly invalid). */\nexport interface Draft {\n uren: string;\n punten: string;\n}\n\n/** What we have AFTER parsing — branded/typed, guaranteed valid. */\nexport interface Valid {\n uren: Uren;\n punten: number;\n}\n\n/**\n * The whole wizard as one tagged union. `step` and `errors` exist ONLY while\n * Editing; Submitting/Submitted/Failed carry a `Valid` payload and nothing else.\n * So \"submitting while a field is invalid\" or \"showing the success screen with\n * errors set\" are unrepresentable — the bug class is gone by construction.\n */\nexport type WizardState =\n | { tag: 'Editing'; step: 1 | 2; draft: Draft; errors: Partial> }\n | { tag: 'Submitting'; data: Valid }\n | { tag: 'Submitted'; data: Valid }\n | { tag: 'Failed'; data: Valid; error: string };\n\nexport const initial: WizardState = { tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} };\n\n/** Parse every field; on success hand back a Valid, else the per-field errors. */\nfunction validate(draft: Draft): Result>, Valid> {\n const uren = parseUren(draft.uren);\n const punten = parseUren(draft.punten);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!punten.ok) errors.punten = punten.error;\n if (uren.ok && punten.ok) return { ok: true, value: { uren: uren.value, punten: punten.value } };\n return { ok: false, error: errors };\n}\n\n/** Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op. */\nexport function next(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 1) return s;\n const uren = parseUren(s.draft.uren);\n return uren.ok\n ? { ...s, step: 2, errors: {} }\n : { ...s, errors: { uren: uren.error } };\n}\n\nexport function back(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n return { ...s, step: 1, errors: {} };\n}\n\n/** Step 2 submit: parse everything; move to Submitting only with Valid data. */\nexport function submit(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n const result = validate(s.draft);\n return result.ok ? { tag: 'Submitting', data: result.value } : { ...s, errors: result.error };\n}\n\n/** Resolve the async submit. Only meaningful while Submitting. */\nexport function resolve(s: WizardState, r: Result): WizardState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n", + "sourceCode": "import { Result, assertNever } from '@shared/kernel/fp';\nimport { Uren, parseUren } from '@registratie/domain/value-objects/uren';\n\n/** What the user is typing (raw, possibly invalid). */\nexport interface Draft {\n uren: string;\n punten: string;\n}\n\n/** What we have AFTER parsing — branded/typed, guaranteed valid. */\nexport interface Valid {\n uren: Uren;\n punten: number;\n}\n\n/**\n * The whole wizard as one tagged union. `step` and `errors` exist ONLY while\n * Editing; Submitting/Submitted/Failed carry a `Valid` payload and nothing else.\n * So \"submitting while a field is invalid\" or \"showing the success screen with\n * errors set\" are unrepresentable — the bug class is gone by construction.\n */\nexport type WizardState =\n | { tag: 'Editing'; step: 1 | 2; draft: Draft; errors: Partial> }\n | { tag: 'Submitting'; data: Valid }\n | { tag: 'Submitted'; data: Valid }\n | { tag: 'Failed'; data: Valid; error: string };\n\nexport const initial: WizardState = { tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} };\n\n/** Parse every field; on success hand back a Valid, else the per-field errors. */\nfunction validate(draft: Draft): Result>, Valid> {\n const uren = parseUren(draft.uren);\n const punten = parseUren(draft.punten);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!punten.ok) errors.punten = punten.error;\n if (uren.ok && punten.ok) return { ok: true, value: { uren: uren.value, punten: punten.value } };\n return { ok: false, error: errors };\n}\n\n/** Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op. */\nexport function next(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 1) return s;\n const uren = parseUren(s.draft.uren);\n return uren.ok\n ? { ...s, step: 2, errors: {} }\n : { ...s, errors: { uren: uren.error } };\n}\n\nexport function back(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n return { ...s, step: 1, errors: {} };\n}\n\n/** Step 2 submit: parse everything; move to Submitting only with Valid data. */\nexport function submit(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n const result = validate(s.draft);\n return result.ok ? { tag: 'Submitting', data: result.value } : { ...s, errors: result.error };\n}\n\n/** Resolve the async submit. Only meaningful while Submitting. */\nexport function resolve(s: WizardState, r: Result): WizardState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n\n/** Update one draft field while editing; ignored in any other state. */\nexport function setField(s: WizardState, key: keyof Draft, value: string): WizardState {\n if (s.tag !== 'Editing') return s;\n return { ...s, draft: { ...s.draft, [key]: value } };\n}\n\n/**\n * Every event that can happen to the wizard, as one message type. The component\n * sends a WizardMsg; `reduce` decides the next state. This is the Elm\n * Model+Msg+update pattern: ONE pure function describes all state changes.\n */\nexport type WizardMsg =\n | { tag: 'SetField'; key: keyof Draft; value: string }\n | { tag: 'Next' }\n | { tag: 'Back' }\n | { tag: 'Submit' }\n | { tag: 'Retry' }\n | { tag: 'SubmitConfirmed' }\n | { tag: 'SubmitFailed'; error: string }\n | { tag: 'Seed'; state: WizardState }; // mount a specific state (stories/showcase)\n\nexport function reduce(s: WizardState, m: WizardMsg): WizardState {\n switch (m.tag) {\n case 'SetField':\n return setField(s, m.key, m.value);\n case 'Next':\n return next(s);\n case 'Back':\n return back(s);\n case 'Submit':\n return submit(s);\n case 'Retry':\n return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;\n case 'SubmitConfirmed':\n return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data } : s;\n case 'SubmitFailed':\n return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;\n case 'Seed':\n return m.state;\n default:\n return assertNever(m);\n }\n}\n", "properties": [ { "name": "punten", @@ -130,10 +214,55 @@ "methods": [], "extends": [] }, + { + "name": "Person", + "id": "interface-Person-07cec46d80a41919e40d0bcb002981627d1a30edb363d64a0a140a8af2c38ad85be5603d88030d0704870800694c5bed9c709ae6891fe83e646e90c1a67cf548", + "file": "src/app/registratie/domain/person.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "export interface Adres {\n straat: string;\n postcode: string;\n woonplaats: string;\n}\n\nexport interface Person {\n naam: string;\n geboortedatum: string; // ISO date\n adres: Adres;\n}\n", + "properties": [ + { + "name": "adres", + "deprecated": false, + "deprecationMessage": "", + "type": "Adres", + "indexKey": "", + "optional": false, + "description": "", + "line": 11 + }, + { + "name": "geboortedatum", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 10 + }, + { + "name": "naam", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 9 + } + ], + "indexSignatures": [], + "kind": 172, + "methods": [], + "extends": [] + }, { "name": "Registration", "id": "interface-Registration-2ecca36e413e22c1c5967d07b811fa4171ff578eb6b47a7275723bcca44e93a2db27d77a765b82e5f6182492464cc8b9268c9724e13b400718d30da7bc4ba50c", - "file": "src/app/core/models.ts", + "file": "src/app/registratie/domain/registration.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", @@ -206,13 +335,122 @@ "extends": [] }, { - "name": "Valid", - "id": "interface-Valid-6fcaabe58c126a645622a7a77942a847877e8d80e60ddd45cb9ba4c247cede3ee197def542b645ef02b0ce5dd0deab6c644edddf2f37dec67a129b27ba53bb23", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "name": "Session", + "id": "interface-Session-4f483129ecb1f3747a600d06904b954a4950b10c4da782d9635896d2c39feaa7777d0e9ed018efa5d4252fd44a29035a431e7dcc15ec19c903f710b163e162f2", + "file": "src/app/auth/domain/session.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", - "sourceCode": "import { Result } from '../../core/fp';\nimport { Uren, parseUren } from '../../core/parse';\n\n/** What the user is typing (raw, possibly invalid). */\nexport interface Draft {\n uren: string;\n punten: string;\n}\n\n/** What we have AFTER parsing — branded/typed, guaranteed valid. */\nexport interface Valid {\n uren: Uren;\n punten: number;\n}\n\n/**\n * The whole wizard as one tagged union. `step` and `errors` exist ONLY while\n * Editing; Submitting/Submitted/Failed carry a `Valid` payload and nothing else.\n * So \"submitting while a field is invalid\" or \"showing the success screen with\n * errors set\" are unrepresentable — the bug class is gone by construction.\n */\nexport type WizardState =\n | { tag: 'Editing'; step: 1 | 2; draft: Draft; errors: Partial> }\n | { tag: 'Submitting'; data: Valid }\n | { tag: 'Submitted'; data: Valid }\n | { tag: 'Failed'; data: Valid; error: string };\n\nexport const initial: WizardState = { tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} };\n\n/** Parse every field; on success hand back a Valid, else the per-field errors. */\nfunction validate(draft: Draft): Result>, Valid> {\n const uren = parseUren(draft.uren);\n const punten = parseUren(draft.punten);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!punten.ok) errors.punten = punten.error;\n if (uren.ok && punten.ok) return { ok: true, value: { uren: uren.value, punten: punten.value } };\n return { ok: false, error: errors };\n}\n\n/** Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op. */\nexport function next(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 1) return s;\n const uren = parseUren(s.draft.uren);\n return uren.ok\n ? { ...s, step: 2, errors: {} }\n : { ...s, errors: { uren: uren.error } };\n}\n\nexport function back(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n return { ...s, step: 1, errors: {} };\n}\n\n/** Step 2 submit: parse everything; move to Submitting only with Valid data. */\nexport function submit(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n const result = validate(s.draft);\n return result.ok ? { tag: 'Submitting', data: result.value } : { ...s, errors: result.error };\n}\n\n/** Resolve the async submit. Only meaningful while Submitting. */\nexport function resolve(s: WizardState, r: Result): WizardState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n", + "sourceCode": "export interface Session {\n readonly bsn: string;\n readonly naam: string;\n}\n\nexport function isAuthenticated(s: Session | null): s is Session {\n return s !== null;\n}\n", + "properties": [ + { + "name": "bsn", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 3, + "modifierKind": [ + 148 + ] + }, + { + "name": "naam", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 4, + "modifierKind": [ + 148 + ] + } + ], + "indexSignatures": [], + "kind": 172, + "description": "

Who is logged in. Framework-free domain type.

\n", + "rawdescription": "\nWho is logged in. Framework-free domain type.", + "methods": [], + "extends": [] + }, + { + "name": "Store", + "id": "interface-Store-de6d0ec7e0704c5d9490e9a33e624477e924daff53384c527eb4aaa76178bd1716ada071d7e46af3e0a859a7cbec63cdc114f61db5d36b61e49effcd5ac744f9", + "file": "src/app/shared/application/store.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "import { Signal, signal } from '@angular/core';\n\n/**\n * A tiny \"Elm-style\" store. The whole idea: all state lives in ONE value\n * (the Model). The only way to change it is to send a message (Msg) to a PURE\n * function `update(model, msg)` that returns the next Model. Nothing else\n * mutates state, so to understand the app you only read the update function.\n *\n * Side effects (HTTP, timers) do NOT go in `update` — that stays pure and easy\n * to test. Instead, effectful \"command\" functions call the network and then\n * `dispatch` a message describing what happened (e.g. Loaded / Failed).\n */\nexport interface Store {\n /** The current state, as a read-only Angular signal. */\n readonly model: Signal;\n /** Send a message; the model becomes update(model, msg). */\n dispatch(msg: Msg): void;\n}\n\nexport function createStore(\n init: Model,\n update: (model: Model, msg: Msg) => Model,\n): Store {\n const model = signal(init);\n return {\n model: model.asReadonly(),\n dispatch: (msg) => model.set(update(model(), msg)),\n };\n}\n", + "properties": [ + { + "name": "model", + "deprecated": false, + "deprecationMessage": "", + "type": "Signal", + "indexKey": "", + "optional": false, + "description": "

The current state, as a read-only Angular signal.

\n", + "line": 15, + "rawdescription": "\nThe current state, as a read-only Angular signal.", + "modifierKind": [ + 148 + ] + } + ], + "indexSignatures": [], + "kind": 174, + "description": "

A tiny "Elm-style" store. The whole idea: all state lives in ONE value\n(the Model). The only way to change it is to send a message (Msg) to a PURE\nfunction update(model, msg) that returns the next Model. Nothing else\nmutates state, so to understand the app you only read the update function.

\n

Side effects (HTTP, timers) do NOT go in update — that stays pure and easy\nto test. Instead, effectful "command" functions call the network and then\ndispatch a message describing what happened (e.g. Loaded / Failed).

\n", + "rawdescription": "\n\nA tiny \"Elm-style\" store. The whole idea: all state lives in ONE value\n(the Model). The only way to change it is to send a message (Msg) to a PURE\nfunction `update(model, msg)` that returns the next Model. Nothing else\nmutates state, so to understand the app you only read the update function.\n\nSide effects (HTTP, timers) do NOT go in `update` — that stays pure and easy\nto test. Instead, effectful \"command\" functions call the network and then\n`dispatch` a message describing what happened (e.g. Loaded / Failed).\n", + "methods": [ + { + "name": "dispatch", + "args": [ + { + "name": "msg", + "type": "Msg", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + } + ], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 17, + "deprecated": false, + "deprecationMessage": "", + "rawdescription": "\nSend a message; the model becomes update(model, msg).", + "description": "

Send a message; the model becomes update(model, msg).

\n", + "jsdoctags": [ + { + "name": "msg", + "type": "Msg", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } + ], + "extends": [] + }, + { + "name": "Valid", + "id": "interface-Valid-6d0ec48dfedad1e9d3dc32caaf0639d35f1430f1e8081a0a719ad0675d06912a90de0d8159326e13c9cc3c8f82521e3fded8d06a5feb1480be4c084a967e6aad", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "import { Result, assertNever } from '@shared/kernel/fp';\nimport { Uren, parseUren } from '@registratie/domain/value-objects/uren';\n\n/** What the user is typing (raw, possibly invalid). */\nexport interface Draft {\n uren: string;\n punten: string;\n}\n\n/** What we have AFTER parsing — branded/typed, guaranteed valid. */\nexport interface Valid {\n uren: Uren;\n punten: number;\n}\n\n/**\n * The whole wizard as one tagged union. `step` and `errors` exist ONLY while\n * Editing; Submitting/Submitted/Failed carry a `Valid` payload and nothing else.\n * So \"submitting while a field is invalid\" or \"showing the success screen with\n * errors set\" are unrepresentable — the bug class is gone by construction.\n */\nexport type WizardState =\n | { tag: 'Editing'; step: 1 | 2; draft: Draft; errors: Partial> }\n | { tag: 'Submitting'; data: Valid }\n | { tag: 'Submitted'; data: Valid }\n | { tag: 'Failed'; data: Valid; error: string };\n\nexport const initial: WizardState = { tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} };\n\n/** Parse every field; on success hand back a Valid, else the per-field errors. */\nfunction validate(draft: Draft): Result>, Valid> {\n const uren = parseUren(draft.uren);\n const punten = parseUren(draft.punten);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!punten.ok) errors.punten = punten.error;\n if (uren.ok && punten.ok) return { ok: true, value: { uren: uren.value, punten: punten.value } };\n return { ok: false, error: errors };\n}\n\n/** Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op. */\nexport function next(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 1) return s;\n const uren = parseUren(s.draft.uren);\n return uren.ok\n ? { ...s, step: 2, errors: {} }\n : { ...s, errors: { uren: uren.error } };\n}\n\nexport function back(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n return { ...s, step: 1, errors: {} };\n}\n\n/** Step 2 submit: parse everything; move to Submitting only with Valid data. */\nexport function submit(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n const result = validate(s.draft);\n return result.ok ? { tag: 'Submitting', data: result.value } : { ...s, errors: result.error };\n}\n\n/** Resolve the async submit. Only meaningful while Submitting. */\nexport function resolve(s: WizardState, r: Result): WizardState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n\n/** Update one draft field while editing; ignored in any other state. */\nexport function setField(s: WizardState, key: keyof Draft, value: string): WizardState {\n if (s.tag !== 'Editing') return s;\n return { ...s, draft: { ...s.draft, [key]: value } };\n}\n\n/**\n * Every event that can happen to the wizard, as one message type. The component\n * sends a WizardMsg; `reduce` decides the next state. This is the Elm\n * Model+Msg+update pattern: ONE pure function describes all state changes.\n */\nexport type WizardMsg =\n | { tag: 'SetField'; key: keyof Draft; value: string }\n | { tag: 'Next' }\n | { tag: 'Back' }\n | { tag: 'Submit' }\n | { tag: 'Retry' }\n | { tag: 'SubmitConfirmed' }\n | { tag: 'SubmitFailed'; error: string }\n | { tag: 'Seed'; state: WizardState }; // mount a specific state (stories/showcase)\n\nexport function reduce(s: WizardState, m: WizardMsg): WizardState {\n switch (m.tag) {\n case 'SetField':\n return setField(s, m.key, m.value);\n case 'Next':\n return next(s);\n case 'Back':\n return back(s);\n case 'Submit':\n return submit(s);\n case 'Retry':\n return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;\n case 'SubmitConfirmed':\n return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data } : s;\n case 'SubmitFailed':\n return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;\n case 'Seed':\n return m.state;\n default:\n return assertNever(m);\n }\n}\n", "properties": [ { "name": "punten", @@ -245,9 +483,184 @@ ], "injectables": [ { - "name": "RegistrationService", - "id": "injectable-RegistrationService-03230b1d6b08769d82b6b7d378749dd92ae4e2487883f5e23a741e40a04d5d54b22e38f1301b13de7a23c8d0e57015e3f77dfb741fb51883d9c30d7bdabd0291", - "file": "src/app/core/registration.service.ts", + "name": "BigProfileStore", + "id": "injectable-BigProfileStore-d0fd61e9f3ea54ee7ebca6eb9a097da261a7447f337e972e7bbb2a8c238f15a8f175b1fddac6c031fc95f77ab8d2778748a149f358ef5f5ec22ecaa23971ef9a", + "file": "src/app/registratie/application/big-profile.store.ts", + "properties": [ + { + "name": "aantekeningen", + "defaultValue": "computed>(() =>\n fromResource(this.aantekeningenRes, (v) => v.length === 0),\n )", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "

Specialisms/notes stay a separate stream (they have their own empty state).

\n", + "line": 41, + "rawdescription": "\nSpecialisms/notes stay a separate stream (they have their own empty state).", + "modifierKind": [ + 148 + ] + }, + { + "name": "aantekeningenRes", + "defaultValue": "this.big.aantekeningenResource()", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 26, + "modifierKind": [ + 123 + ] + }, + { + "name": "big", + "defaultValue": "inject(BigRegisterAdapter)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 22, + "modifierKind": [ + 123 + ] + }, + { + "name": "brp", + "defaultValue": "inject(BrpAdapter)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 23, + "modifierKind": [ + 123 + ] + }, + { + "name": "pending", + "defaultValue": "signal(false)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 46, + "modifierKind": [ + 123 + ] + }, + { + "name": "pendingHerregistratie", + "defaultValue": "this.pending.asReadonly()", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "

True while a herregistratie submission is in flight or just submitted.

\n", + "line": 48, + "rawdescription": "\nTrue while a herregistratie submission is in flight or just submitted.", + "modifierKind": [ + 148 + ] + }, + { + "name": "personRes", + "defaultValue": "this.brp.personResource()", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 27, + "modifierKind": [ + 123 + ] + }, + { + "name": "profile", + "defaultValue": "computed>(() =>\n map2(\n fromResource(this.registrationRes),\n fromResource(this.personRes),\n // httpResource types value as T | undefined; in the Success branch it is\n // always present, so narrowing here is safe.\n (registration, person) => ({ registration: registration!, person: person! }),\n ),\n )", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "

BIG-register + BRP folded into one state.

\n", + "line": 30, + "rawdescription": "\nBIG-register + BRP folded into one state.", + "modifierKind": [ + 148 + ] + }, + { + "name": "registrationRes", + "defaultValue": "this.big.registrationResource()", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 25, + "modifierKind": [ + 123 + ] + } + ], + "methods": [ + { + "name": "beginHerregistratie", + "args": [], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 50, + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "confirmHerregistratie", + "args": [], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 53, + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "rollbackHerregistratie", + "args": [], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 57, + "deprecated": false, + "deprecationMessage": "" + } + ], + "deprecated": false, + "deprecationMessage": "", + "description": "

The single source of truth for the logged-in professional's profile, shared\nacross pages (providedIn:'root' = one instance). It owns the httpResources\n(created here, in the required injection context) and exposes them as\nRemoteData signals.

\n

The headline trick: profile combines TWO independent services — the\nBIG-register and the BRP — into ONE RemoteData via map2. A page renders a\nsingle state (loading / error / loaded), never juggling three.

\n", + "rawdescription": "\n\nThe single source of truth for the logged-in professional's profile, shared\nacross pages (providedIn:'root' = one instance). It owns the httpResources\n(created here, in the required injection context) and exposes them as\nRemoteData signals.\n\nThe headline trick: `profile` combines TWO independent services — the\nBIG-register and the BRP — into ONE RemoteData via map2. A page renders a\nsingle state (loading / error / loaded), never juggling three.\n", + "sourceCode": "import { Injectable, computed, inject, signal } from '@angular/core';\nimport { RemoteData, fromResource, map2 } from '@shared/application/remote-data';\nimport { Aantekening } from '../domain/registration';\nimport { BigProfile } from '../domain/big-profile';\nimport { BigRegisterAdapter } from '../infrastructure/big-register.adapter';\nimport { BrpAdapter } from '../infrastructure/brp.adapter';\n\ntype Err = Error | undefined;\n\n/**\n * The single source of truth for the logged-in professional's profile, shared\n * across pages (providedIn:'root' = one instance). It owns the httpResources\n * (created here, in the required injection context) and exposes them as\n * RemoteData signals.\n *\n * The headline trick: `profile` combines TWO independent services — the\n * BIG-register and the BRP — into ONE RemoteData via map2. A page renders a\n * single state (loading / error / loaded), never juggling three.\n */\n@Injectable({ providedIn: 'root' })\nexport class BigProfileStore {\n private big = inject(BigRegisterAdapter);\n private brp = inject(BrpAdapter);\n\n private registrationRes = this.big.registrationResource();\n private aantekeningenRes = this.big.aantekeningenResource();\n private personRes = this.brp.personResource();\n\n /** BIG-register + BRP folded into one state. */\n readonly profile = computed>(() =>\n map2(\n fromResource(this.registrationRes),\n fromResource(this.personRes),\n // httpResource types value as T | undefined; in the Success branch it is\n // always present, so narrowing here is safe.\n (registration, person) => ({ registration: registration!, person: person! }),\n ),\n );\n\n /** Specialisms/notes stay a separate stream (they have their own empty state). */\n readonly aantekeningen = computed>(() =>\n fromResource(this.aantekeningenRes, (v) => v.length === 0),\n );\n\n // --- Optimistic herregistratie state, shared with the dashboard -----------\n private pending = signal(false);\n /** True while a herregistratie submission is in flight or just submitted. */\n readonly pendingHerregistratie = this.pending.asReadonly();\n\n beginHerregistratie() {\n this.pending.set(true); // optimistic: show it immediately on the dashboard\n }\n confirmHerregistratie() {\n this.pending.set(false);\n this.registrationRes.reload(); // invalidate: re-fetch the now-updated registration\n }\n rollbackHerregistratie() {\n this.pending.set(false); // submission failed — undo the optimistic flag\n }\n}\n", + "extends": [], + "type": "injectable" + }, + { + "name": "BigRegisterAdapter", + "id": "injectable-BigRegisterAdapter-b658d9915e8f09eed460d1f75d5dc229398186277cfa4d35fd143799842c80174c41daf2f30936090fda88e72f3bbfc3e9422cbfa7b1d1342eb004cc2d80e3ae", + "file": "src/app/registratie/infrastructure/big-register.adapter.ts", "properties": [], "methods": [ { @@ -273,9 +686,203 @@ ], "deprecated": false, "deprecationMessage": "", - "description": "

Exposes signal-based resources (Angular's httpResource). Each returns a\nResource with status()/value()/error()/reload() — consumed by ,\nwhich turns those signals into the loading/empty/error/loaded UI.\nCall these from a component field initializer (injection context required).

\n", - "rawdescription": "\n\nExposes signal-based resources (Angular's httpResource). Each returns a\nResource with status()/value()/error()/reload() — consumed by ,\nwhich turns those signals into the loading/empty/error/loaded UI.\nCall these from a component field initializer (injection context required).\n", - "sourceCode": "import { Injectable } from '@angular/core';\nimport { httpResource } from '@angular/common/http';\nimport { Registration, Aantekening } from './models';\n\n/**\n * Exposes signal-based resources (Angular's httpResource). Each returns a\n * Resource with status()/value()/error()/reload() — consumed by ,\n * which turns those signals into the loading/empty/error/loaded UI.\n * Call these from a component field initializer (injection context required).\n */\n@Injectable({ providedIn: 'root' })\nexport class RegistrationService {\n registrationResource() {\n return httpResource(() => 'mock/registration.json');\n }\n\n aantekeningenResource() {\n return httpResource(() => 'mock/notes.json', { defaultValue: [] });\n }\n}\n", + "description": "

Infrastructure adapter for the BIG-register source. Exposes signal-based\nresources (Angular's httpResource); each returns a Resource with\nstatus()/value()/error()/reload(). Call from an injection context\n(a field initializer in the store).

\n", + "rawdescription": "\n\nInfrastructure adapter for the BIG-register source. Exposes signal-based\nresources (Angular's httpResource); each returns a Resource with\nstatus()/value()/error()/reload(). Call from an injection context\n(a field initializer in the store).\n", + "sourceCode": "import { Injectable } from '@angular/core';\nimport { httpResource } from '@angular/common/http';\nimport { Registration, Aantekening } from '../domain/registration';\n\n/**\n * Infrastructure adapter for the BIG-register source. Exposes signal-based\n * resources (Angular's httpResource); each returns a Resource with\n * status()/value()/error()/reload(). Call from an injection context\n * (a field initializer in the store).\n */\n@Injectable({ providedIn: 'root' })\nexport class BigRegisterAdapter {\n registrationResource() {\n return httpResource(() => 'mock/registration.json');\n }\n\n aantekeningenResource() {\n return httpResource(() => 'mock/notes.json', { defaultValue: [] });\n }\n}\n", + "extends": [], + "type": "injectable" + }, + { + "name": "BrpAdapter", + "id": "injectable-BrpAdapter-66b7fdb8331dd6cebe24a4713e26a6b025c4c97b239a8de8a81f9b72167dc376459e44aca001b24a7c44bdba6b97fa82d248762bbd1fca3a476993f709834b89", + "file": "src/app/registratie/infrastructure/brp.adapter.ts", + "properties": [], + "methods": [ + { + "name": "personResource", + "args": [], + "optional": false, + "returnType": "any", + "typeParameters": [], + "line": 8, + "deprecated": false, + "deprecationMessage": "" + } + ], + "deprecated": false, + "deprecationMessage": "", + "description": "

Infrastructure adapter for the BRP (Basisregistratie Personen) source.

\n", + "rawdescription": "\nInfrastructure adapter for the BRP (Basisregistratie Personen) source.", + "sourceCode": "import { Injectable } from '@angular/core';\nimport { httpResource } from '@angular/common/http';\nimport { Person } from '../domain/person';\n\n/** Infrastructure adapter for the BRP (Basisregistratie Personen) source. */\n@Injectable({ providedIn: 'root' })\nexport class BrpAdapter {\n personResource() {\n return httpResource(() => 'mock/brp.json');\n }\n}\n", + "extends": [], + "type": "injectable" + }, + { + "name": "DigidAdapter", + "id": "injectable-DigidAdapter-6b0a6d77a6f7411602d1c257ac45d4d45631556b1a468f7f38abfbbe375a3ac5e566fa6024ba56cb59f8418174ed2dbc6ea937b7b7999e58655acf01c7d7ecc9", + "file": "src/app/auth/infrastructure/digid.adapter.ts", + "properties": [], + "methods": [ + { + "name": "authenticate", + "args": [ + { + "name": "bsn", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + } + ], + "optional": false, + "returnType": "Promise>", + "typeParameters": [], + "line": 10, + "deprecated": false, + "deprecationMessage": "", + "modifierKind": [ + 134 + ], + "jsdoctags": [ + { + "name": "bsn", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } + ], + "deprecated": false, + "deprecationMessage": "", + "description": "

Infrastructure: talks to the (mock) DigiD identity provider.

\n", + "rawdescription": "\nInfrastructure: talks to the (mock) DigiD identity provider.", + "sourceCode": "import { Injectable } from '@angular/core';\nimport { Result, ok, err } from '@shared/kernel/fp';\nimport { Session } from '../domain/session';\n\n/** Infrastructure: talks to the (mock) DigiD identity provider. */\n@Injectable({ providedIn: 'root' })\nexport class DigidAdapter {\n // ponytail: fake DigiD — any 9-digit BSN authenticates to a fixed identity.\n // Swap for a real OIDC redirect flow when there's a backend.\n async authenticate(bsn: string): Promise> {\n const t = bsn.trim();\n if (!/^\\d{9}$/.test(t)) return err('Voer een geldig BSN van 9 cijfers in.');\n return ok({ bsn: t, naam: 'Dr. A. (Anna) de Vries' });\n }\n}\n", + "extends": [], + "type": "injectable" + }, + { + "name": "SessionStore", + "id": "injectable-SessionStore-73a990a2a5a1c0f7177443b4e29c7af3da44fcbabcc4e6637486764225143283202ac5da0ee355ed30f829eab6f88a25f05dac7fce22d60cbf2dda7e0956b949", + "file": "src/app/auth/application/session.store.ts", + "properties": [ + { + "name": "_session", + "defaultValue": "signal(null)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 15, + "modifierKind": [ + 123 + ] + }, + { + "name": "digid", + "defaultValue": "inject(DigidAdapter)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 14, + "modifierKind": [ + 123 + ] + }, + { + "name": "isAuthenticated", + "defaultValue": "computed(() => this._session() !== null)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 18, + "modifierKind": [ + 148 + ] + }, + { + "name": "session", + "defaultValue": "this._session.asReadonly()", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 17, + "modifierKind": [ + 148 + ] + } + ], + "methods": [ + { + "name": "login", + "args": [ + { + "name": "bsn", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + } + ], + "optional": false, + "returnType": "Promise>", + "typeParameters": [], + "line": 21, + "deprecated": false, + "deprecationMessage": "", + "rawdescription": "\nEffectful command: authenticate, then store the session on success.", + "description": "

Effectful command: authenticate, then store the session on success.

\n", + "modifierKind": [ + 134 + ], + "jsdoctags": [ + { + "name": "bsn", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "logout", + "args": [], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 27, + "deprecated": false, + "deprecationMessage": "" + } + ], + "deprecated": false, + "deprecationMessage": "", + "description": "

Holds the current session for the whole app. Because it is providedIn:'root'\nthere is exactly one instance — every component that injects it sees the same\nsession signal, so logging in is instantly visible everywhere (the guard, the\nheader, etc.). ponytail: in-memory only; a refresh logs you out.

\n", + "rawdescription": "\n\nHolds the current session for the whole app. Because it is providedIn:'root'\nthere is exactly one instance — every component that injects it sees the same\nsession signal, so logging in is instantly visible everywhere (the guard, the\nheader, etc.). ponytail: in-memory only; a refresh logs you out.\n", + "sourceCode": "import { Injectable, computed, inject, signal } from '@angular/core';\nimport { Result } from '@shared/kernel/fp';\nimport { Session } from '../domain/session';\nimport { DigidAdapter } from '../infrastructure/digid.adapter';\n\n/**\n * Holds the current session for the whole app. Because it is providedIn:'root'\n * there is exactly one instance — every component that injects it sees the same\n * session signal, so logging in is instantly visible everywhere (the guard, the\n * header, etc.). ponytail: in-memory only; a refresh logs you out.\n */\n@Injectable({ providedIn: 'root' })\nexport class SessionStore {\n private digid = inject(DigidAdapter);\n private _session = signal(null);\n\n readonly session = this._session.asReadonly();\n readonly isAuthenticated = computed(() => this._session() !== null);\n\n /** Effectful command: authenticate, then store the session on success. */\n async login(bsn: string): Promise> {\n const r = await this.digid.authenticate(bsn);\n if (r.ok) this._session.set(r.value);\n return r;\n }\n\n logout() {\n this._session.set(null);\n }\n}\n", "extends": [], "type": "injectable" } @@ -286,12 +893,12 @@ "directives": [ { "name": "AsyncEmptyDirective", - "id": "directive-AsyncEmptyDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9", - "file": "src/app/molecules/async/async.component.ts", + "id": "directive-AsyncEmptyDirective-b3cfb3c4a98444c42ba42c11d432aca9cf324dba35b23ccb8af26360530e37b4edcc64b110afd78fb4d7e4eef1b24c72be872007aaa92127f22e76e1ae7c0964", + "file": "src/app/shared/ui/async/async.component.ts", "type": "directive", "description": "", "rawdescription": "\n", - "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", + "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '@shared/ui/spinner/spinner.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { RemoteData, fromResource, foldRemote } from '@shared/application/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n // Two ways to feed this component:\n // [resource] — a raw httpResource (the common case), or\n // [data] — an already-combined RemoteData (e.g. from a store via map2).\n resource = input>();\n data = input>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the supplied RemoteData, or the resource projected into one.\n protected rd = computed>(() => {\n const data = this.data();\n if (data) return data;\n const r = this.resource();\n return r ? fromResource(r, this.isEmpty()) : { tag: 'Loading' };\n });\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if (r && 'reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", "selector": "[appAsyncEmpty]", "providers": [], "hostDirectives": [], @@ -352,12 +959,12 @@ }, { "name": "AsyncErrorDirective", - "id": "directive-AsyncErrorDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9", - "file": "src/app/molecules/async/async.component.ts", + "id": "directive-AsyncErrorDirective-b3cfb3c4a98444c42ba42c11d432aca9cf324dba35b23ccb8af26360530e37b4edcc64b110afd78fb4d7e4eef1b24c72be872007aaa92127f22e76e1ae7c0964", + "file": "src/app/shared/ui/async/async.component.ts", "type": "directive", "description": "", "rawdescription": "\n", - "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", + "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '@shared/ui/spinner/spinner.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { RemoteData, fromResource, foldRemote } from '@shared/application/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n // Two ways to feed this component:\n // [resource] — a raw httpResource (the common case), or\n // [data] — an already-combined RemoteData (e.g. from a store via map2).\n resource = input>();\n data = input>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the supplied RemoteData, or the resource projected into one.\n protected rd = computed>(() => {\n const data = this.data();\n if (data) return data;\n const r = this.resource();\n return r ? fromResource(r, this.isEmpty()) : { tag: 'Loading' };\n });\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if (r && 'reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", "selector": "[appAsyncError]", "providers": [], "hostDirectives": [], @@ -418,12 +1025,12 @@ }, { "name": "AsyncLoadedDirective", - "id": "directive-AsyncLoadedDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9", - "file": "src/app/molecules/async/async.component.ts", + "id": "directive-AsyncLoadedDirective-b3cfb3c4a98444c42ba42c11d432aca9cf324dba35b23ccb8af26360530e37b4edcc64b110afd78fb4d7e4eef1b24c72be872007aaa92127f22e76e1ae7c0964", + "file": "src/app/shared/ui/async/async.component.ts", "type": "directive", "description": "", "rawdescription": "\n", - "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", + "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '@shared/ui/spinner/spinner.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { RemoteData, fromResource, foldRemote } from '@shared/application/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n // Two ways to feed this component:\n // [resource] — a raw httpResource (the common case), or\n // [data] — an already-combined RemoteData (e.g. from a store via map2).\n resource = input>();\n data = input>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the supplied RemoteData, or the resource projected into one.\n protected rd = computed>(() => {\n const data = this.data();\n if (data) return data;\n const r = this.resource();\n return r ? fromResource(r, this.isEmpty()) : { tag: 'Loading' };\n });\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if (r && 'reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", "selector": "[appAsyncLoaded]", "providers": [], "hostDirectives": [], @@ -484,12 +1091,12 @@ }, { "name": "AsyncLoadingDirective", - "id": "directive-AsyncLoadingDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9", - "file": "src/app/molecules/async/async.component.ts", + "id": "directive-AsyncLoadingDirective-b3cfb3c4a98444c42ba42c11d432aca9cf324dba35b23ccb8af26360530e37b4edcc64b110afd78fb4d7e4eef1b24c72be872007aaa92127f22e76e1ae7c0964", + "file": "src/app/shared/ui/async/async.component.ts", "type": "directive", "description": "", "rawdescription": "\n", - "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", + "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '@shared/ui/spinner/spinner.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { RemoteData, fromResource, foldRemote } from '@shared/application/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n // Two ways to feed this component:\n // [resource] — a raw httpResource (the common case), or\n // [data] — an already-combined RemoteData (e.g. from a store via map2).\n resource = input>();\n data = input>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the supplied RemoteData, or the resource projected into one.\n protected rd = computed>(() => {\n const data = this.data();\n if (data) return data;\n const r = this.resource();\n return r ? fromResource(r, this.isEmpty()) : { tag: 'Loading' };\n });\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if (r && 'reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", "selector": "[appAsyncLoading]", "providers": [], "hostDirectives": [], @@ -553,7 +1160,7 @@ { "name": "AlertComponent", "id": "component-AlertComponent-7d03ee2c0179c2f8d44795112c823951406810027571b5471bc765873f1aeaee550dcb89c6ebd8514ee36f2d96c8c6ba118293bf44a4a1316f61e91fa3c06f4e", - "file": "src/app/atoms/alert/alert.component.ts", + "file": "src/app/shared/ui/alert/alert.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -639,8 +1246,8 @@ }, { "name": "AsyncComponent", - "id": "component-AsyncComponent-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9", - "file": "src/app/molecules/async/async.component.ts", + "id": "component-AsyncComponent-b3cfb3c4a98444c42ba42c11d432aca9cf324dba35b23ccb8af26360530e37b4edcc64b110afd78fb4d7e4eef1b24c72be872007aaa92127f22e76e1ae7c0964", + "file": "src/app/shared/ui/async/async.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -654,6 +1261,17 @@ "viewProviders": [], "hostDirectives": [], "inputsClass": [ + { + "name": "data", + "deprecated": false, + "deprecationMessage": "", + "type": "RemoteData", + "indexKey": "", + "optional": false, + "description": "", + "line": 70, + "required": false + }, { "name": "isEmpty", "defaultValue": "() => false", @@ -663,7 +1281,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 67, + "line": 71, "required": false }, { @@ -674,8 +1292,8 @@ "indexKey": "", "optional": false, "description": "", - "line": 66, - "required": true + "line": 69, + "required": false } ], "outputsClass": [], @@ -689,7 +1307,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 71 + "line": 75 }, { "name": "error", @@ -700,7 +1318,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 82, + "line": 91, "modifierKind": [ 124 ] @@ -714,7 +1332,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 72 + "line": 76 }, { "name": "loadedTpl", @@ -725,7 +1343,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 69 + "line": 73 }, { "name": "loadingTpl", @@ -736,18 +1354,18 @@ "indexKey": "", "optional": false, "description": "", - "line": 70 + "line": 74 }, { "name": "rd", - "defaultValue": "computed(() => fromResource(this.resource(), this.isEmpty()))", + "defaultValue": "computed>(() => {\n const data = this.data();\n if (data) return data;\n const r = this.resource();\n return r ? fromResource(r, this.isEmpty()) : { tag: 'Loading' };\n })", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 75, + "line": 79, "modifierKind": [ 124 ] @@ -761,7 +1379,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 86 + "line": 95 }, { "name": "value", @@ -772,7 +1390,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 79, + "line": 88, "modifierKind": [ 124 ] @@ -804,7 +1422,7 @@ "description": "

Renders exactly ONE of loading / empty / error / loaded for a signal-based\nresource (e.g. httpResource). Built on a RemoteData tagged union (see\ncore/remote-data.ts), so the states are mutually exclusive by construction —\nthe UI can never show two at once ("impossible states"). Unprovided slots\nfall back to sensible defaults.

\n", "rawdescription": "\n\nRenders exactly ONE of loading / empty / error / loaded for a signal-based\nresource (e.g. httpResource). Built on a RemoteData tagged union (see\ncore/remote-data.ts), so the states are mutually exclusive by construction —\nthe UI can never show two at once (\"impossible states\"). Unprovided slots\nfall back to sensible defaults.\n", "type": "component", - "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", + "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '@shared/ui/spinner/spinner.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { RemoteData, fromResource, foldRemote } from '@shared/application/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n // Two ways to feed this component:\n // [resource] — a raw httpResource (the common case), or\n // [data] — an already-combined RemoteData (e.g. from a store via map2).\n resource = input>();\n data = input>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the supplied RemoteData, or the resource projected into one.\n protected rd = computed>(() => {\n const data = this.data();\n if (data) return data;\n const r = this.resource();\n return r ? fromResource(r, this.isEmpty()) : { tag: 'Loading' };\n });\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if (r && 'reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -813,7 +1431,7 @@ { "name": "ButtonComponent", "id": "component-ButtonComponent-6eb936521438d91cc3a2276d1b8e366496e907e8a01d7b84b327c3cc31fa1dfadbb9502c6646472736722cf7ced9950fcaa4d8435ddf28224b3e7c9b7bd29505", - "file": "src/app/atoms/button/button.component.ts", + "file": "src/app/shared/ui/button/button.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -883,8 +1501,8 @@ }, { "name": "ChangeRequestFormComponent", - "id": "component-ChangeRequestFormComponent-e366867f9f2a82d4c6cafc6fbdce9749e7775f651192c4de62cefc40a237a6e2c6772537e64a4aeeb6667ffa33d9a69ee6114c69160674807408090b1cbc283a", - "file": "src/app/organisms/change-request-form/change-request-form.component.ts", + "id": "component-ChangeRequestFormComponent-8d79b6a9ce75f62f4e04f9d3ed0867e00d005c9c05cf267503536c4eec9813372aff51a3c8686927fd07f46503b60ddb4107edcc5f57fdfa1b6b52fe2a98026c", + "file": "src/app/registratie/ui/change-request-form/change-request-form.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1010,7 +1628,7 @@ "description": "

Organism: change-request (adreswijziging) form. Reuses the same form-field\nmolecule + text-input/button atoms as the login form. Field errors come\nstraight from the parser's Result — no parallel "is it valid" flag to drift.

\n", "rawdescription": "\nOrganism: change-request (adreswijziging) form. Reuses the same form-field\nmolecule + text-input/button atoms as the login form. Field errors come\nstraight from the parser's Result — no parallel \"is it valid\" flag to drift.", "type": "component", - "sourceCode": "import { Component, output, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { Postcode, parsePostcode } from '../../core/parse';\n\n/** A submitted change request carries a *parsed* postcode (branded Postcode),\n not a raw string — downstream code can't receive an unvalidated one. */\nexport interface ChangeRequest {\n street: string;\n zip: Postcode;\n city: string;\n}\n\n/** Organism: change-request (adreswijziging) form. Reuses the same form-field\n molecule + text-input/button atoms as the login form. Field errors come\n straight from the parser's Result — no parallel \"is it valid\" flag to drift. */\n@Component({\n selector: 'app-change-request-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, HeadingComponent],\n template: `\n Adreswijziging doorgeven\n
\n \n \n \n \n \n \n \n \n \n
\n Wijziging indienen\n
\n
\n `,\n})\nexport class ChangeRequestFormComponent {\n street = '';\n zip = '';\n city = '';\n streetError = signal('');\n zipError = signal('');\n submitted = output();\n\n onSubmit() {\n const street = this.street.trim();\n this.streetError.set(street ? '' : 'Vul straat en huisnummer in.');\n\n const postcode = parsePostcode(this.zip);\n this.zipError.set(postcode.ok ? '' : postcode.error);\n\n if (!street || !postcode.ok) return;\n this.submitted.emit({ street, zip: postcode.value, city: this.city.trim() });\n }\n}\n", + "sourceCode": "import { Component, output, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '@shared/ui/form-field/form-field.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { HeadingComponent } from '@shared/ui/heading/heading.component';\nimport { Postcode, parsePostcode } from '@registratie/domain/value-objects/postcode';\n\n/** A submitted change request carries a *parsed* postcode (branded Postcode),\n not a raw string — downstream code can't receive an unvalidated one. */\nexport interface ChangeRequest {\n street: string;\n zip: Postcode;\n city: string;\n}\n\n/** Organism: change-request (adreswijziging) form. Reuses the same form-field\n molecule + text-input/button atoms as the login form. Field errors come\n straight from the parser's Result — no parallel \"is it valid\" flag to drift. */\n@Component({\n selector: 'app-change-request-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, HeadingComponent],\n template: `\n Adreswijziging doorgeven\n
\n \n \n \n \n \n \n \n \n \n
\n Wijziging indienen\n
\n
\n `,\n})\nexport class ChangeRequestFormComponent {\n street = '';\n zip = '';\n city = '';\n streetError = signal('');\n zipError = signal('');\n submitted = output();\n\n onSubmit() {\n const street = this.street.trim();\n this.streetError.set(street ? '' : 'Vul straat en huisnummer in.');\n\n const postcode = parsePostcode(this.zip);\n this.zipError.set(postcode.ok ? '' : postcode.error);\n\n if (!street || !postcode.ok) return;\n this.submitted.emit({ street, zip: postcode.value, city: this.city.trim() });\n }\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -1018,8 +1636,8 @@ }, { "name": "ConceptsPage", - "id": "component-ConceptsPage-6ebc285de6e31b814c5a5499d296626fc51bce08971ae012bfa0176580e767b207f13bbc91e38ad1d616cb28e386208e7686eef9bdf8f5e0ddf89b7ee67362cb", - "file": "src/app/pages/concepts/concepts.page.ts", + "id": "component-ConceptsPage-951fb54ea356331be584db1248fbc5d516618c3b5b9f960359a4a642e7d9ce7d4d240852a31bbbcfea0e0bec6c0feeb398ae79c181480422a7477c3b600c88a5", + "file": "src/app/showcase/concepts.page.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1205,7 +1823,7 @@ "description": "

Teaching showcase: each section pairs the impossible-state-permitting "before"\nwith the "after" where the type system rules it out. Composition-only.

\n", "rawdescription": "\nTeaching showcase: each section pairs the impossible-state-permitting \"before\"\nwith the \"after\" where the type system rules it out. Composition-only.", "type": "component", - "sourceCode": "import { Component, computed, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport type { Resource } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ASYNC } from '../../molecules/async/async.component';\nimport { SkeletonComponent } from '../../atoms/skeleton/skeleton.component';\nimport { RegistrationSummaryComponent } from '../../organisms/registration-summary/registration-summary.component';\nimport { HerregistratieWizardComponent } from '../../organisms/herregistratie-wizard/herregistratie-wizard.component';\nimport { Registration } from '../../core/models';\nimport { parsePostcode } from '../../core/parse';\n\n/** Minimal fake Resource so can be driven through every state without HTTP. */\nfunction fakeResource(status: string, value?: T, error?: Error): Resource {\n return { value: () => value as T, status: () => status, error: () => error, hasValue: () => value !== undefined, reload: () => {} } as unknown as Resource;\n}\n\n/** Teaching showcase: each section pairs the impossible-state-permitting \"before\"\n with the \"after\" where the type system rules it out. Composition-only. */\n@Component({\n selector: 'app-concepts-page',\n imports: [\n FormsModule, PageShellComponent, HeadingComponent, AlertComponent, TextInputComponent,\n ...ASYNC, SkeletonComponent, RegistrationSummaryComponent, HerregistratieWizardComponent,\n ],\n styles: [`\n .cols { display:flex; flex-wrap:wrap; gap:1.5rem; margin:1rem 0 2.5rem }\n .col { flex:1 1 20rem; min-width:18rem }\n .tag { font-weight:700; font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em }\n .bad { color:var(--rhc-color-rood-500) }\n .good { color:var(--rhc-color-groen-500) }\n pre { background:var(--rhc-color-grijs-100,#f3f3f3); padding:1rem; border-radius:4px; overflow:auto; font-size:0.85rem }\n `],\n template: `\n \n \n Vier functionele patronen die met atomic design makkelijker te tonen zijn — telkens \"fout\"\n (de oude vorm liet het toe) naast \"goed\" (het type maakt het onmogelijk).\n \n\n \n 1 · Discriminated unions\n
\n
\n

Fout — vlakke interface

\n
{{ unionBad }}
\n

Een doorgehaalde registratie houdt tóch een herregistratiedatum: onmogelijke toestand.

\n
\n
\n

Goed — sum type

\n \n

De variant Doorgehaald kent geen herregistratiedatum, dus de rij bestaat simpelweg niet.

\n
\n
\n\n \n 2 · RemoteData fold\n
\n
\n

Eén molecuul, vier elkaar uitsluitende toestanden

\n

Loading

\n {{ v }}\n

Empty

\n {{ v }}\n

Failure

\n {{ v }}\n

Success

\n
    @for (i of v; track i) {
  • {{ i }}
  • }
\n
\n
\n

De exhaustieve fold

\n
{{ foldCode }}
\n

Een nieuwe variant toevoegen breekt de compile via assertNever tot je hem afhandelt.

\n
\n
\n\n \n 3 · Parse, don't validate\n
\n
\n

Smart constructor → Result

\n \n
\n
\n @if (parsed().ok) {\n

ok

\n
Postcode = \"{{ parsed().ok ? $any(parsed()).value : '' }}\"
\n

Een gevalideerde Postcode is een ander type dan een ruwe string.

\n } @else {\n

err

\n
{{ $any(parsed()).error }}
\n }\n
\n
\n\n \n 4 · Form als state machine\n
\n
\n

Fout — losse booleans

\n
{{ machineBad }}
\n

Niets verhindert \"submitting\" mét validatiefouten of een successcherm met errors.

\n
\n
\n

Goed — één tagged union stuurt de UI

\n \n
\n
\n
\n `,\n})\nexport class ConceptsPage {\n isEmpty = (v: string[]) => !v || v.length === 0;\n\n doorgehaald: Registration = {\n bigNummer: '19012345601', naam: 'Dr. A. (Anna) de Vries', beroep: 'Arts',\n registratiedatum: '2012-09-01', geboortedatum: '1985-03-14',\n status: { tag: 'Doorgehaald', doorgehaaldOp: '2024-05-01', reden: 'Op eigen verzoek' },\n };\n\n loadingRes = fakeResource('loading');\n emptyRes = fakeResource('resolved', []);\n errorRes = fakeResource('error', undefined, new Error('Demo'));\n successRes = fakeResource('resolved', ['Huisartsgeneeskunde', 'Spoedeisende hulp']);\n\n raw = signal('');\n parsed = computed(() => parsePostcode(this.raw()));\n\n unionBad = `interface Registration {\n status: 'Geregistreerd' | 'Doorgehaald';\n herregistratieDatum: string; // altijd aanwezig 😬\n}`;\n\n foldCode = `foldRemote(rd, {\n loading: () => spinner,\n empty: () => 'geen data',\n failure: (e) => alert(e),\n success: (v) => render(v),\n}); // mist er één → compile-fout`;\n\n machineBad = `submitting = signal(false);\nsubmitted = signal(false);\nerrors = signal<...>({});\n// submitting === true && errors.size > 0 ? 🤷`;\n}\n", + "sourceCode": "import { Component, computed, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport type { Resource } from '@angular/core';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { HeadingComponent } from '@shared/ui/heading/heading.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ASYNC } from '@shared/ui/async/async.component';\nimport { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';\nimport { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';\nimport { HerregistratieWizardComponent } from '@herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component';\nimport { Registration } from '@registratie/domain/registration';\nimport { parsePostcode } from '@registratie/domain/value-objects/postcode';\n\n/** Minimal fake Resource so can be driven through every state without HTTP. */\nfunction fakeResource(status: string, value?: T, error?: Error): Resource {\n return { value: () => value as T, status: () => status, error: () => error, hasValue: () => value !== undefined, reload: () => {} } as unknown as Resource;\n}\n\n/** Teaching showcase: each section pairs the impossible-state-permitting \"before\"\n with the \"after\" where the type system rules it out. Composition-only. */\n@Component({\n selector: 'app-concepts-page',\n imports: [\n FormsModule, PageShellComponent, HeadingComponent, AlertComponent, TextInputComponent,\n ...ASYNC, SkeletonComponent, RegistrationSummaryComponent, HerregistratieWizardComponent,\n ],\n styles: [`\n .cols { display:flex; flex-wrap:wrap; gap:1.5rem; margin:1rem 0 2.5rem }\n .col { flex:1 1 20rem; min-width:18rem }\n .tag { font-weight:700; font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em }\n .bad { color:var(--rhc-color-rood-500) }\n .good { color:var(--rhc-color-groen-500) }\n pre { background:var(--rhc-color-grijs-100,#f3f3f3); padding:1rem; border-radius:4px; overflow:auto; font-size:0.85rem }\n `],\n template: `\n \n \n Vier functionele patronen die met atomic design makkelijker te tonen zijn — telkens \"fout\"\n (de oude vorm liet het toe) naast \"goed\" (het type maakt het onmogelijk).\n \n\n \n 1 · Discriminated unions\n
\n
\n

Fout — vlakke interface

\n
{{ unionBad }}
\n

Een doorgehaalde registratie houdt tóch een herregistratiedatum: onmogelijke toestand.

\n
\n
\n

Goed — sum type

\n \n

De variant Doorgehaald kent geen herregistratiedatum, dus de rij bestaat simpelweg niet.

\n
\n
\n\n \n 2 · RemoteData fold\n
\n
\n

Eén molecuul, vier elkaar uitsluitende toestanden

\n

Loading

\n {{ v }}\n

Empty

\n {{ v }}\n

Failure

\n {{ v }}\n

Success

\n
    @for (i of v; track i) {
  • {{ i }}
  • }
\n
\n
\n

De exhaustieve fold

\n
{{ foldCode }}
\n

Een nieuwe variant toevoegen breekt de compile via assertNever tot je hem afhandelt.

\n
\n
\n\n \n 3 · Parse, don't validate\n
\n
\n

Smart constructor → Result

\n \n
\n
\n @if (parsed().ok) {\n

ok

\n
Postcode = \"{{ parsed().ok ? $any(parsed()).value : '' }}\"
\n

Een gevalideerde Postcode is een ander type dan een ruwe string.

\n } @else {\n

err

\n
{{ $any(parsed()).error }}
\n }\n
\n
\n\n \n 4 · Form als state machine\n
\n
\n

Fout — losse booleans

\n
{{ machineBad }}
\n

Niets verhindert \"submitting\" mét validatiefouten of een successcherm met errors.

\n
\n
\n

Goed — één tagged union stuurt de UI

\n \n
\n
\n
\n `,\n})\nexport class ConceptsPage {\n isEmpty = (v: string[]) => !v || v.length === 0;\n\n doorgehaald: Registration = {\n bigNummer: '19012345601', naam: 'Dr. A. (Anna) de Vries', beroep: 'Arts',\n registratiedatum: '2012-09-01', geboortedatum: '1985-03-14',\n status: { tag: 'Doorgehaald', doorgehaaldOp: '2024-05-01', reden: 'Op eigen verzoek' },\n };\n\n loadingRes = fakeResource('loading');\n emptyRes = fakeResource('resolved', []);\n errorRes = fakeResource('error', undefined, new Error('Demo'));\n successRes = fakeResource('resolved', ['Huisartsgeneeskunde', 'Spoedeisende hulp']);\n\n raw = signal('');\n parsed = computed(() => parsePostcode(this.raw()));\n\n unionBad = `interface Registration {\n status: 'Geregistreerd' | 'Doorgehaald';\n herregistratieDatum: string; // altijd aanwezig 😬\n}`;\n\n foldCode = `foldRemote(rd, {\n loading: () => spinner,\n empty: () => 'geen data',\n failure: (e) => alert(e),\n success: (v) => render(v),\n}); // mist er één → compile-fout`;\n\n machineBad = `submitting = signal(false);\nsubmitted = signal(false);\nerrors = signal<...>({});\n// submitting === true && errors.size > 0 ? 🤷`;\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "\n .cols { display:flex; flex-wrap:wrap; gap:1.5rem; margin:1rem 0 2.5rem }\n .col { flex:1 1 20rem; min-width:18rem }\n .tag { font-weight:700; font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em }\n .bad { color:var(--rhc-color-rood-500) }\n .good { color:var(--rhc-color-groen-500) }\n pre { background:var(--rhc-color-grijs-100,#f3f3f3); padding:1rem; border-radius:4px; overflow:auto; font-size:0.85rem }\n \n", @@ -1213,8 +1831,8 @@ }, { "name": "DashboardPage", - "id": "component-DashboardPage-f363992c5154b037dc0dc2a461da66f9a297af7bc7ba9dd9f19a1bef213af9b9fb15a92e6d5e1922040acbf5aa3f7ec27db8035944e496c8f97bb519b4652cb3", - "file": "src/app/pages/dashboard/dashboard.page.ts", + "id": "component-DashboardPage-3876b122573c06608aa1fa82196e04af53fa3720906e138b095aa46a13efbd18be0b42e0a6024ee8df168faecfbf50c6a9ad9a5a7afff5447fec52dcafc73d49", + "file": "src/app/registratie/ui/dashboard.page.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1223,7 +1841,7 @@ "selector": "app-dashboard-page", "styleUrls": [], "styles": [], - "template": "\n \n \n \n \n \n \n \n \n \n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n

\n Functionele patronen (impossible states) →\n

\n
\n", + "template": "\n @if (store.pendingHerregistratie()) {\n Uw herregistratie-aanvraag is in behandeling.\n }\n\n \n \n \n \n
\n Persoonsgegevens (BRP)\n
\n \n \n \n
\n
\n
\n \n \n \n
\n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n

\n Functionele patronen (impossible states) →\n

\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -1231,61 +1849,17 @@ "outputsClass": [], "propertiesClass": [ { - "name": "notes", - "defaultValue": "this.svc.aantekeningenResource()", + "name": "store", + "defaultValue": "inject(BigProfileStore)", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 60 - }, - { - "name": "notesEmpty", - "defaultValue": "() => {...}", - "deprecated": false, - "deprecationMessage": "", - "type": "unknown", - "indexKey": "", - "optional": false, - "description": "", - "line": 61 - }, - { - "name": "reg", - "defaultValue": "this.svc.registrationResource()", - "deprecated": false, - "deprecationMessage": "", - "type": "unknown", - "indexKey": "", - "optional": false, - "description": "", - "line": 59 - }, - { - "name": "regEmpty", - "defaultValue": "() => {...}", - "deprecated": false, - "deprecationMessage": "", - "type": "unknown", - "indexKey": "", - "optional": false, - "description": "", - "line": 62 - }, - { - "name": "svc", - "defaultValue": "inject(RegistrationService)", - "deprecated": false, - "deprecationMessage": "", - "type": "unknown", - "indexKey": "", - "optional": false, - "description": "", - "line": 58, + "line": 71, "modifierKind": [ - 123 + 124 ] } ], @@ -1308,10 +1882,18 @@ "name": "LinkComponent", "type": "component" }, + { + "name": "AlertComponent", + "type": "component" + }, { "name": "SkeletonComponent", "type": "component" }, + { + "name": "DataRowComponent", + "type": "component" + }, { "name": "ASYNC" }, @@ -1327,7 +1909,7 @@ "description": "", "rawdescription": "\n", "type": "component", - "sourceCode": "import { Component, inject } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { LinkComponent } from '../../atoms/link/link.component';\nimport { SkeletonComponent } from '../../atoms/skeleton/skeleton.component';\nimport { ASYNC } from '../../molecules/async/async.component';\nimport { RegistrationSummaryComponent } from '../../organisms/registration-summary/registration-summary.component';\nimport { RegistrationTableComponent } from '../../organisms/registration-table/registration-table.component';\nimport { RegistrationService } from '../../core/registration.service';\nimport { Aantekening } from '../../core/models';\n\n@Component({\n selector: 'app-dashboard-page',\n imports: [\n PageShellComponent, HeadingComponent, LinkComponent, SkeletonComponent, ...ASYNC,\n RegistrationSummaryComponent, RegistrationTableComponent,\n ],\n template: `\n \n \n \n \n \n \n \n \n \n \n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n

\n Functionele patronen (impossible states) →\n

\n
\n `,\n})\nexport class DashboardPage {\n private svc = inject(RegistrationService);\n reg = this.svc.registrationResource();\n notes = this.svc.aantekeningenResource();\n notesEmpty = (v: Aantekening[]) => v.length === 0;\n regEmpty = (v: unknown) => !v || Object.keys(v).length === 0;\n}\n", + "sourceCode": "import { Component, inject } from '@angular/core';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { HeadingComponent } from '@shared/ui/heading/heading.component';\nimport { LinkComponent } from '@shared/ui/link/link.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';\nimport { DataRowComponent } from '@shared/ui/data-row/data-row.component';\nimport { ASYNC } from '@shared/ui/async/async.component';\nimport { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';\nimport { RegistrationTableComponent } from '@registratie/ui/registration-table/registration-table.component';\nimport { BigProfileStore } from '@registratie/application/big-profile.store';\n\n@Component({\n selector: 'app-dashboard-page',\n imports: [\n PageShellComponent, HeadingComponent, LinkComponent, AlertComponent, SkeletonComponent,\n DataRowComponent, ...ASYNC, RegistrationSummaryComponent, RegistrationTableComponent,\n ],\n template: `\n \n @if (store.pendingHerregistratie()) {\n Uw herregistratie-aanvraag is in behandeling.\n }\n\n \n \n \n \n
\n Persoonsgegevens (BRP)\n
\n \n \n \n
\n
\n
\n \n \n \n
\n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n

\n Functionele patronen (impossible states) →\n

\n
\n `,\n})\nexport class DashboardPage {\n protected store = inject(BigProfileStore);\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -1336,7 +1918,7 @@ { "name": "DataRowComponent", "id": "component-DataRowComponent-bf012e0b5e277c5c6d53311a7649373316677ab4f6d5ba0b313f5eaeb5a061ea55cb08b0af237c487dcc3be5b15014d5c2b21b6f6903ff7689881fc8607eb1b2", - "file": "src/app/molecules/data-row/data-row.component.ts", + "file": "src/app/shared/ui/data-row/data-row.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1395,7 +1977,7 @@ { "name": "FormFieldComponent", "id": "component-FormFieldComponent-9f7fb7fac967056161f8440bccbd87825bc2897463f63a612a12ac6154cfaa4e17a2a6469fbe936deba74319ac6029c6aa024abfc07095b226f5980e654cb12c", - "file": "src/app/molecules/form-field/form-field.component.ts", + "file": "src/app/shared/ui/form-field/form-field.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1475,7 +2057,7 @@ { "name": "HeadingComponent", "id": "component-HeadingComponent-0f0b442fc9ccbfde8481137b728000bb093868ddc931f507bbd4a86ee528820cf4c6dab36a9a82b4a940bb96c2355f42bc34a94700acc7df3e7dfeea627be67c", - "file": "src/app/atoms/heading/heading.component.ts", + "file": "src/app/shared/ui/heading/heading.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1526,8 +2108,8 @@ }, { "name": "HerregistratiePage", - "id": "component-HerregistratiePage-e6eeb0932b0d6c7f4073bda82d95601ef7d31f17ac310061dcf3347c8f30d1444f50e124bfedda286285ebfda9932bb075fdaf20683413b524570e51fa350c3f", - "file": "src/app/pages/herregistratie/herregistratie.page.ts", + "id": "component-HerregistratiePage-320bc6fae5182789d36effc63292c612ce5b125b9cf1bb77b7bdc1be837ca444255723e8a2a48eb9abcada973007eec4badbaf82e60644b869c5d66ffd68d97d", + "file": "src/app/herregistratie/ui/herregistratie.page.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1536,13 +2118,42 @@ "selector": "app-herregistratie-page", "styleUrls": [], "styles": [], - "template": "\n \n Uw huidige registratie verloopt op 1 september 2027. Vraag tijdig herregistratie aan.\n \n
\n \n
\n
\n", + "template": "\n \n \n @if (eligible) {\n \n Uw huidige registratie verloopt binnenkort. Vraag tijdig herregistratie aan.\n \n
\n \n
\n } @else {\n \n Voor uw huidige registratiestatus is herregistratie niet mogelijk.\n \n }\n
\n
\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], "inputsClass": [], "outputsClass": [], - "propertiesClass": [], + "propertiesClass": [ + { + "name": "eligibility", + "defaultValue": "computed(() =>\n map(this.store.profile(), (p) => isHerregistratieEligible(p.registration, new Date())),\n )", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 39, + "modifierKind": [ + 124 + ] + }, + { + "name": "store", + "defaultValue": "inject(BigProfileStore)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 37, + "modifierKind": [ + 123 + ] + } + ], "methodsClass": [], "deprecated": false, "deprecationMessage": "", @@ -1558,15 +2169,18 @@ "name": "AlertComponent", "type": "component" }, + { + "name": "ASYNC" + }, { "name": "HerregistratieWizardComponent", "type": "component" } ], - "description": "

A whole new page built from existing building blocks — no new components.\nThis is the atomic-design payoff: a new flow is just composition.

\n", - "rawdescription": "\nA whole new page built from existing building blocks — no new components.\nThis is the atomic-design payoff: a new flow is just composition.", + "description": "

A whole new page built from existing building blocks. Eligibility is a pure\ndomain rule (registration.policy) read from the shared profile state.

\n", + "rawdescription": "\nA whole new page built from existing building blocks. Eligibility is a pure\ndomain rule (registration.policy) read from the shared profile state.", "type": "component", - "sourceCode": "import { Component } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { HerregistratieWizardComponent } from '../../organisms/herregistratie-wizard/herregistratie-wizard.component';\n\n/** A whole new page built from existing building blocks — no new components.\n This is the atomic-design payoff: a new flow is just composition. */\n@Component({\n selector: 'app-herregistratie-page',\n imports: [PageShellComponent, AlertComponent, HerregistratieWizardComponent],\n template: `\n \n \n Uw huidige registratie verloopt op 1 september 2027. Vraag tijdig herregistratie aan.\n \n
\n \n
\n
\n `,\n})\nexport class HerregistratiePage {}\n", + "sourceCode": "import { Component, computed, inject } from '@angular/core';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { ASYNC } from '@shared/ui/async/async.component';\nimport { map } from '@shared/application/remote-data';\nimport { BigProfileStore } from '@registratie/application/big-profile.store';\nimport { isHerregistratieEligible } from '@registratie/domain/registration.policy';\nimport { HerregistratieWizardComponent } from '@herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component';\n\n/** A whole new page built from existing building blocks. Eligibility is a pure\n domain rule (registration.policy) read from the shared profile state. */\n@Component({\n selector: 'app-herregistratie-page',\n imports: [PageShellComponent, AlertComponent, ...ASYNC, HerregistratieWizardComponent],\n template: `\n \n \n \n @if (eligible) {\n \n Uw huidige registratie verloopt binnenkort. Vraag tijdig herregistratie aan.\n \n
\n \n
\n } @else {\n \n Voor uw huidige registratiestatus is herregistratie niet mogelijk.\n \n }\n
\n
\n
\n `,\n})\nexport class HerregistratiePage {\n private store = inject(BigProfileStore);\n // Derive a boolean RemoteData from the combined profile via map (pure).\n protected eligibility = computed(() =>\n map(this.store.profile(), (p) => isHerregistratieEligible(p.registration, new Date())),\n );\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -1574,8 +2188,8 @@ }, { "name": "HerregistratieWizardComponent", - "id": "component-HerregistratieWizardComponent-ad587121f38c0c6e6bb4c8525e7b626054b7790ec708c99a0f6e66b3b7703b935b47c6b141072f7f8c9e567974dd162961ef5e17b983e373d6d7a3a7814aaf80", - "file": "src/app/organisms/herregistratie-wizard/herregistratie-wizard.component.ts", + "id": "component-HerregistratieWizardComponent-45976389894f1e7f8d41c6517df80210bfb72ce0c6d94a26e8efaea3128977466c34eab7a568b372079d6c5295253c988e2b3d96ffc7267e02a1160e75cef160", + "file": "src/app/herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1584,7 +2198,7 @@ "selector": "app-herregistratie-wizard", "styleUrls": [], "styles": [], - "template": "@switch (state().tag) {\n @case ('Editing') {\n

Stap {{ step() }} van 2

\n
\n @if (step() === 1) {\n \n \n \n Volgende\n } @else {\n \n \n \n
\n Vorige\n Herregistratie aanvragen\n
\n }\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n }\n @case ('Failed') {\n Indienen mislukt. Probeer het opnieuw.\n
\n Opnieuw proberen\n
\n }\n}\n", + "template": "@switch (state().tag) {\n @case ('Editing') {\n

Stap {{ step() }} van 2

\n
\n @if (step() === 1) {\n \n \n \n Volgende\n } @else {\n \n \n \n
\n Vorige\n Herregistratie aanvragen\n
\n }\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n }\n @case ('Failed') {\n Indienen mislukt: {{ failedError() }}\n
\n Opnieuw proberen\n
\n }\n}\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -1598,7 +2212,7 @@ "indexKey": "", "optional": false, "description": "

Optional seed so Storybook / the showcase can mount any state directly.

\n", - "line": 58, + "line": 65, "rawdescription": "\nOptional seed so Storybook / the showcase can mount any state directly.", "required": false } @@ -1606,15 +2220,18 @@ "outputsClass": [], "propertiesClass": [ { - "name": "back", - "defaultValue": "back", + "name": "dispatch", + "defaultValue": "this.store.dispatch", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 61 + "line": 68, + "modifierKind": [ + 124 + ] }, { "name": "draft", @@ -1625,7 +2242,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 65, + "line": 72, "modifierKind": [ 124 ] @@ -1639,7 +2256,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 63, + "line": 70, "modifierKind": [ 123 ] @@ -1653,7 +2270,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 67, + "line": 74, "modifierKind": [ 124 ] @@ -1667,21 +2284,52 @@ "indexKey": "", "optional": false, "description": "", - "line": 66, + "line": 73, "modifierKind": [ 124 ] }, { - "name": "state", - "defaultValue": "signal(initial)", + "name": "failedError", + "defaultValue": "computed(() => (this.state().tag === 'Failed' ? (this.state() as Extract).error : ''))", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 59 + "line": 75, + "modifierKind": [ + 124 + ] + }, + { + "name": "profile", + "defaultValue": "inject(BigProfileStore)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 61, + "modifierKind": [ + 123 + ] + }, + { + "name": "state", + "defaultValue": "this.store.model", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 67, + "modifierKind": [ + 124 + ] }, { "name": "step", @@ -1692,10 +2340,24 @@ "indexKey": "", "optional": false, "description": "", - "line": 64, + "line": 71, "modifierKind": [ 124 ] + }, + { + "name": "store", + "defaultValue": "createStore(initial, reduce)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 62, + "modifierKind": [ + 123 + ] } ], "methodsClass": [ @@ -1705,7 +2367,7 @@ "optional": false, "returnType": "void", "typeParameters": [], - "line": 79, + "line": 81, "deprecated": false, "deprecationMessage": "" }, @@ -1715,7 +2377,7 @@ "optional": false, "returnType": "void", "typeParameters": [], - "line": 86, + "line": 88, "deprecated": false, "deprecationMessage": "" }, @@ -1723,64 +2385,16 @@ "name": "runIfSubmitting", "args": [], "optional": false, - "returnType": "void", + "returnType": "any", "typeParameters": [], - "line": 94, + "line": 95, "deprecated": false, "deprecationMessage": "", + "rawdescription": "\nThe effect: when we entered Submitting, call the backend command, flip the\noptimistic cross-page flag, then dispatch the result (and commit/rollback).", + "description": "

The effect: when we entered Submitting, call the backend command, flip the\noptimistic cross-page flag, then dispatch the result (and commit/rollback).

\n", "modifierKind": [ - 123 - ] - }, - { - "name": "set", - "args": [ - { - "name": "key", - "type": "unknown", - "optional": false, - "dotDotDotToken": false, - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "value", - "type": "string", - "optional": false, - "dotDotDotToken": false, - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 73, - "deprecated": false, - "deprecationMessage": "", - "jsdoctags": [ - { - "name": "key", - "type": "unknown", - "optional": false, - "dotDotDotToken": false, - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "value", - "type": "string", - "optional": false, - "dotDotDotToken": false, - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } + 123, + 134 ] } ], @@ -1815,10 +2429,10 @@ "type": "component" } ], - "description": "

Organism: multi-step herregistratie wizard driven entirely by a state\nmachine (wizard.machine.ts). The signal IS the union, so the UI just folds\nover its tag — no booleans like submitting/submitted that could contradict\neach other. Composition-only: reuses existing form-field/input/button/alert.

\n", - "rawdescription": "\nOrganism: multi-step herregistratie wizard driven entirely by a state\nmachine (wizard.machine.ts). The signal IS the union, so the UI just folds\nover its tag — no booleans like `submitting`/`submitted` that could contradict\neach other. Composition-only: reuses existing form-field/input/button/alert.", + "description": "

Organism: multi-step herregistratie wizard. ALL state lives in one signal\ndriven by the pure reduce function (see herregistratie.machine.ts) via an\nElm-style store. The UI just sends messages and folds over the state's tag —\nno booleans like submitting/submitted that could contradict each other.\nSubmitting also flips an optimistic flag on the shared BigProfileStore, so\nthe dashboard shows "in behandeling" immediately.

\n", + "rawdescription": "\nOrganism: multi-step herregistratie wizard. ALL state lives in one signal\ndriven by the pure `reduce` function (see herregistratie.machine.ts) via an\nElm-style store. The UI just sends messages and folds over the state's tag —\nno booleans like `submitting`/`submitted` that could contradict each other.\nSubmitting also flips an optimistic flag on the shared BigProfileStore, so\nthe dashboard shows \"in behandeling\" immediately.", "type": "component", - "sourceCode": "import { Component, computed, input, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { ok } from '../../core/fp';\nimport { WizardState, Draft, initial, next, back, submit, resolve } from './wizard.machine';\n\n/** Organism: multi-step herregistratie wizard driven entirely by a state\n machine (wizard.machine.ts). The signal IS the union, so the UI just folds\n over its tag — no booleans like `submitting`/`submitted` that could contradict\n each other. Composition-only: reuses existing form-field/input/button/alert. */\n@Component({\n selector: 'app-herregistratie-wizard',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, AlertComponent, SpinnerComponent],\n template: `\n @switch (state().tag) {\n @case ('Editing') {\n

Stap {{ step() }} van 2

\n
\n @if (step() === 1) {\n \n \n \n Volgende\n } @else {\n \n \n \n
\n Vorige\n Herregistratie aanvragen\n
\n }\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n }\n @case ('Failed') {\n Indienen mislukt. Probeer het opnieuw.\n
\n Opnieuw proberen\n
\n }\n }\n `,\n})\nexport class HerregistratieWizardComponent {\n /** Optional seed so Storybook / the showcase can mount any state directly. */\n seed = input(initial);\n state = signal(initial);\n\n back = back;\n\n private editing = computed(() => (this.state().tag === 'Editing' ? (this.state() as Extract) : null));\n protected step = computed(() => this.editing()?.step ?? 1);\n protected draft = computed(() => this.editing()?.draft ?? { uren: '', punten: '' });\n protected errUren = computed(() => this.editing()?.errors.uren ?? '');\n protected errPunten = computed(() => this.editing()?.errors.punten ?? '');\n\n constructor() {\n queueMicrotask(() => this.state.set(this.seed()));\n }\n\n set(key: keyof Draft, value: string) {\n const s = this.state();\n if (s.tag !== 'Editing') return;\n this.state.set({ ...s, draft: { ...s.draft, [key]: value } });\n }\n\n onPrimary() {\n const s = this.state();\n if (s.tag !== 'Editing') return;\n this.state.set(s.step === 1 ? next(s) : submit(s));\n this.runIfSubmitting();\n }\n\n onRetry() {\n const s = this.state();\n if (s.tag !== 'Failed') return;\n this.state.set({ tag: 'Submitting', data: s.data });\n this.runIfSubmitting();\n }\n\n // ponytail: fake the backend with a timer; swap for a real httpResource call later.\n private runIfSubmitting() {\n if (this.state().tag !== 'Submitting') return;\n setTimeout(() => this.state.set(resolve(this.state(), ok(undefined))), 800);\n }\n}\n", + "sourceCode": "import { Component, computed, inject, input } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '@shared/ui/form-field/form-field.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { SpinnerComponent } from '@shared/ui/spinner/spinner.component';\nimport { createStore } from '@shared/application/store';\nimport { BigProfileStore } from '@registratie/application/big-profile.store';\nimport { WizardState, WizardMsg, Draft, initial, reduce } from '@herregistratie/domain/herregistratie.machine';\nimport { submitHerregistratie } from '@herregistratie/application/submit-herregistratie';\n\n/** Organism: multi-step herregistratie wizard. ALL state lives in one signal\n driven by the pure `reduce` function (see herregistratie.machine.ts) via an\n Elm-style store. The UI just sends messages and folds over the state's tag —\n no booleans like `submitting`/`submitted` that could contradict each other.\n Submitting also flips an optimistic flag on the shared BigProfileStore, so\n the dashboard shows \"in behandeling\" immediately. */\n@Component({\n selector: 'app-herregistratie-wizard',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, AlertComponent, SpinnerComponent],\n template: `\n @switch (state().tag) {\n @case ('Editing') {\n

Stap {{ step() }} van 2

\n
\n @if (step() === 1) {\n \n \n \n Volgende\n } @else {\n \n \n \n
\n Vorige\n Herregistratie aanvragen\n
\n }\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n }\n @case ('Failed') {\n Indienen mislukt: {{ failedError() }}\n
\n Opnieuw proberen\n
\n }\n }\n `,\n})\nexport class HerregistratieWizardComponent {\n private profile = inject(BigProfileStore);\n private store = createStore(initial, reduce);\n\n /** Optional seed so Storybook / the showcase can mount any state directly. */\n seed = input(initial);\n\n protected state = this.store.model;\n protected dispatch = this.store.dispatch;\n\n private editing = computed(() => (this.state().tag === 'Editing' ? (this.state() as Extract) : null));\n protected step = computed(() => this.editing()?.step ?? 1);\n protected draft = computed(() => this.editing()?.draft ?? { uren: '', punten: '' });\n protected errUren = computed(() => this.editing()?.errors.uren ?? '');\n protected errPunten = computed(() => this.editing()?.errors.punten ?? '');\n protected failedError = computed(() => (this.state().tag === 'Failed' ? (this.state() as Extract).error : ''));\n\n constructor() {\n queueMicrotask(() => this.dispatch({ tag: 'Seed', state: this.seed() }));\n }\n\n onPrimary() {\n const s = this.state();\n if (s.tag !== 'Editing') return;\n this.dispatch(s.step === 1 ? { tag: 'Next' } : { tag: 'Submit' });\n this.runIfSubmitting();\n }\n\n onRetry() {\n this.dispatch({ tag: 'Retry' });\n this.runIfSubmitting();\n }\n\n /** The effect: when we entered Submitting, call the backend command, flip the\n optimistic cross-page flag, then dispatch the result (and commit/rollback). */\n private async runIfSubmitting() {\n const s = this.state();\n if (s.tag !== 'Submitting') return;\n this.profile.beginHerregistratie();\n const r = await submitHerregistratie(s.data);\n if (r.ok) {\n this.dispatch({ tag: 'SubmitConfirmed' });\n this.profile.confirmHerregistratie();\n } else {\n this.dispatch({ tag: 'SubmitFailed', error: r.error });\n this.profile.rollbackHerregistratie();\n }\n }\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -1828,14 +2442,14 @@ "deprecated": false, "deprecationMessage": "", "args": [], - "line": 67 + "line": 75 }, "extends": [] }, { "name": "LinkComponent", "id": "component-LinkComponent-81d97782386b884f56c43d583cbf6d1005dda96974f169366a9af853aeb8b8f4e99f9cce2f9a7ef0fdafce32f51e6049483a44ab35614c486f82535b65ce2818", - "file": "src/app/atoms/link/link.component.ts", + "file": "src/app/shared/ui/link/link.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1885,8 +2499,8 @@ }, { "name": "LoginFormComponent", - "id": "component-LoginFormComponent-09b4fa4e3820031285372fc93c6a18697b3099647bf79b1869c466189d5041da442eaac2e437ae8efa5cd2546d72bbdbd9c001c24e8b1d26f363e763d0382e3d", - "file": "src/app/organisms/login-form/login-form.component.ts", + "id": "component-LoginFormComponent-5a1ea0dfad0a47819d0884389f35e026337bfc135ed3cb5f34d5874d22b773b812864e1dfdcebf784acf886a77e9f59d3f244c7f3bbe1edafb38f0d8fb866a43", + "file": "src/app/auth/ui/login-form/login-form.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1895,7 +2509,7 @@ "selector": "app-login-form", "styleUrls": [], "styles": [], - "template": "
\n \n \n \n\n \n \n \n\n
\n Inloggen met DigiD\n
\n
\n", + "template": "
\n \n \n \n\n \n \n \n\n
\n Inloggen met DigiD\n
\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -1905,7 +2519,7 @@ "name": "submit", "deprecated": false, "deprecationMessage": "", - "type": "void", + "type": "string", "indexKey": "", "optional": false, "description": "", @@ -1964,7 +2578,7 @@ "description": "

Organism: DigiD-style mock login. No real auth — just composes atoms/molecules.

\n", "rawdescription": "\nOrganism: DigiD-style mock login. No real auth — just composes atoms/molecules.", "type": "component", - "sourceCode": "import { Component, output } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/** Organism: DigiD-style mock login. No real auth — just composes atoms/molecules. */\n@Component({\n selector: 'app-login-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent],\n template: `\n
\n \n \n \n\n \n \n \n\n
\n Inloggen met DigiD\n
\n
\n `,\n})\nexport class LoginFormComponent {\n bsn = '';\n password = '';\n submit = output();\n}\n", + "sourceCode": "import { Component, output } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '@shared/ui/form-field/form-field.component';\nimport { TextInputComponent } from '@shared/ui/text-input/text-input.component';\nimport { ButtonComponent } from '@shared/ui/button/button.component';\n\n/** Organism: DigiD-style mock login. No real auth — just composes atoms/molecules. */\n@Component({\n selector: 'app-login-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent],\n template: `\n
\n \n \n \n\n \n \n \n\n
\n Inloggen met DigiD\n
\n
\n `,\n})\nexport class LoginFormComponent {\n bsn = '';\n password = '';\n submit = output();\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -1972,8 +2586,8 @@ }, { "name": "LoginPage", - "id": "component-LoginPage-e8a9fcb0309464188c46b54292e1b26314533e424703d8d6d84a1cffe31832e80d7ae8fe256da4b42d053ec775301d70502a9022e092ee0b2d10c7777baf1913", - "file": "src/app/pages/login/login.page.ts", + "id": "component-LoginPage-9e33c7b584edbd0642e3db190f47b540bad1779cd292050761380e92715a150b03eb5d869d57c6678b4d73523803c07e2d70d6e70183b34461ac31c15cafa95b", + "file": "src/app/auth/ui/login.page.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -1982,13 +2596,24 @@ "selector": "app-login-page", "styleUrls": [], "styles": [], - "template": "\n \n\n", + "template": "\n @if (error()) { {{ error() }} }\n \n\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], "inputsClass": [], "outputsClass": [], "propertiesClass": [ + { + "name": "error", + "defaultValue": "signal('')", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 22 + }, { "name": "router", "defaultValue": "inject(Router)", @@ -1998,7 +2623,21 @@ "indexKey": "", "optional": false, "description": "", - "line": 17, + "line": 21, + "modifierKind": [ + 123 + ] + }, + { + "name": "store", + "defaultValue": "inject(SessionStore)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 20, "modifierKind": [ 123 ] @@ -2007,13 +2646,38 @@ "methodsClass": [ { "name": "login", - "args": [], + "args": [ + { + "name": "bsn", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + } + ], "optional": false, - "returnType": "void", + "returnType": "any", "typeParameters": [], - "line": 18, + "line": 24, "deprecated": false, - "deprecationMessage": "" + "deprecationMessage": "", + "modifierKind": [ + 134 + ], + "jsdoctags": [ + { + "name": "bsn", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] } ], "deprecated": false, @@ -2026,6 +2690,10 @@ "name": "PageShellComponent", "type": "component" }, + { + "name": "AlertComponent", + "type": "component" + }, { "name": "LoginFormComponent", "type": "component" @@ -2034,7 +2702,7 @@ "description": "", "rawdescription": "\n", "type": "component", - "sourceCode": "import { Component, inject } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { LoginFormComponent } from '../../organisms/login-form/login-form.component';\n\n@Component({\n selector: 'app-login-page',\n imports: [PageShellComponent, LoginFormComponent],\n template: `\n \n \n \n `,\n})\nexport class LoginPage {\n private router = inject(Router);\n login() { this.router.navigate(['/dashboard']); }\n}\n", + "sourceCode": "import { Component, inject, signal } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { LoginFormComponent } from '@auth/ui/login-form/login-form.component';\nimport { SessionStore } from '@auth/application/session.store';\n\n@Component({\n selector: 'app-login-page',\n imports: [PageShellComponent, AlertComponent, LoginFormComponent],\n template: `\n \n @if (error()) { {{ error() }} }\n \n \n `,\n})\nexport class LoginPage {\n private store = inject(SessionStore);\n private router = inject(Router);\n error = signal('');\n\n async login(bsn: string) {\n const r = await this.store.login(bsn);\n if (r.ok) this.router.navigate(['/dashboard']);\n else this.error.set(r.error);\n }\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2042,8 +2710,8 @@ }, { "name": "PageShellComponent", - "id": "component-PageShellComponent-f2d3224d1fdc66ba96a373ea16cc259b086450cd1700c9f2b59b9f3193d96b4cd69bc9218497aa73519a3545157d691f273fb410f78d9f7fe9373e5921be9c1b", - "file": "src/app/templates/page-shell/page-shell.component.ts", + "id": "component-PageShellComponent-e203d646c83aa0fbb8c86fd2fd55c90f86a13538dcec381ed765f73d2d49424819da915fe6cc8a91700f890997d9d2407b4376630d984875e90277fe97679453", + "file": "src/app/shared/layout/page-shell/page-shell.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -2137,7 +2805,7 @@ "description": "

Template: standard page body — optional back-link, a heading, optional intro,\nand projected content. Rendered inside the persistent ShellComponent via the\nrouter outlet, so it owns only the content (not the header/footer chrome).

\n", "rawdescription": "\nTemplate: standard page body — optional back-link, a heading, optional intro,\nand projected content. Rendered inside the persistent ShellComponent via the\nrouter outlet, so it owns only the content (not the header/footer chrome).", "type": "component", - "sourceCode": "import { Component, input } from '@angular/core';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { LinkComponent } from '../../atoms/link/link.component';\n\n/** Template: standard page body — optional back-link, a heading, optional intro,\n and projected content. Rendered inside the persistent ShellComponent via the\n router outlet, so it owns only the content (not the header/footer chrome). */\n@Component({\n selector: 'app-page-shell',\n imports: [HeadingComponent, LinkComponent],\n styles: [':host{display:block}'],\n template: `\n
\n @if (backLink()) {\n

← {{ backLabel() }}

\n }\n {{ heading() }}\n @if (intro()) {\n

{{ intro() }}

\n }\n \n
\n `,\n})\nexport class PageShellComponent {\n heading = input.required();\n intro = input();\n backLink = input();\n backLabel = input('Terug naar overzicht');\n width = input<'default' | 'narrow'>('default');\n}\n", + "sourceCode": "import { Component, input } from '@angular/core';\nimport { HeadingComponent } from '@shared/ui/heading/heading.component';\nimport { LinkComponent } from '@shared/ui/link/link.component';\n\n/** Template: standard page body — optional back-link, a heading, optional intro,\n and projected content. Rendered inside the persistent ShellComponent via the\n router outlet, so it owns only the content (not the header/footer chrome). */\n@Component({\n selector: 'app-page-shell',\n imports: [HeadingComponent, LinkComponent],\n styles: [':host{display:block}'],\n template: `\n
\n @if (backLink()) {\n

← {{ backLabel() }}

\n }\n {{ heading() }}\n @if (intro()) {\n

{{ intro() }}

\n }\n \n
\n `,\n})\nexport class PageShellComponent {\n heading = input.required();\n intro = input();\n backLink = input();\n backLabel = input('Terug naar overzicht');\n width = input<'default' | 'narrow'>('default');\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": ":host{display:block}\n", @@ -2145,8 +2813,8 @@ }, { "name": "RegistrationDetailPage", - "id": "component-RegistrationDetailPage-efb92f879b64c25c80211fcc675636fad2b1709613792834a68cec3b82982d290d7f04e97dd3186900b77a5922cd219f565fbf9a9957ec78e2321ad7e2eb30dc", - "file": "src/app/pages/registration-detail/registration-detail.page.ts", + "id": "component-RegistrationDetailPage-5aa270b47adfa8bd0334225a51df3a1656468e109d14ca9967660455a734884f4fb358b0f2a6c97e19b4df859f4c5e9773d5dd9c728dbcd785a3d43a60472811", + "file": "src/app/registratie/ui/registration-detail.page.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -2155,7 +2823,7 @@ "selector": "app-registration-detail-page", "styleUrls": [], "styles": [], - "template": "\n \n \n \n \n \n \n \n \n\n
\n @if (submitted()) {\n Uw adreswijziging is ontvangen. U ontvangt binnen 5 werkdagen bericht.\n } @else {\n \n }\n
\n
\n", + "template": "\n \n \n \n \n \n \n \n \n\n
\n @if (submitted()) {\n Uw adreswijziging is ontvangen. U ontvangt binnen 5 werkdagen bericht.\n } @else {\n \n }\n
\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -2163,26 +2831,18 @@ "outputsClass": [], "propertiesClass": [ { - "name": "reg", - "defaultValue": "this.svc.registrationResource()", + "name": "store", + "defaultValue": "inject(BigProfileStore)", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 39 - }, - { - "name": "regEmpty", - "defaultValue": "() => {...}", - "deprecated": false, - "deprecationMessage": "", - "type": "unknown", - "indexKey": "", - "optional": false, - "description": "", - "line": 40 + "line": 38, + "modifierKind": [ + 124 + ] }, { "name": "submitted", @@ -2193,21 +2853,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 41 - }, - { - "name": "svc", - "defaultValue": "inject(RegistrationService)", - "deprecated": false, - "deprecationMessage": "", - "type": "unknown", - "indexKey": "", - "optional": false, - "description": "", - "line": 38, - "modifierKind": [ - 123 - ] + "line": 39 } ], "methodsClass": [], @@ -2244,7 +2890,7 @@ "description": "", "rawdescription": "\n", "type": "component", - "sourceCode": "import { Component, inject, signal } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { SkeletonComponent } from '../../atoms/skeleton/skeleton.component';\nimport { ASYNC } from '../../molecules/async/async.component';\nimport { RegistrationSummaryComponent } from '../../organisms/registration-summary/registration-summary.component';\nimport { ChangeRequestFormComponent } from '../../organisms/change-request-form/change-request-form.component';\nimport { RegistrationService } from '../../core/registration.service';\n\n@Component({\n selector: 'app-registration-detail-page',\n imports: [\n PageShellComponent, AlertComponent, SkeletonComponent, ...ASYNC,\n RegistrationSummaryComponent, ChangeRequestFormComponent,\n ],\n template: `\n \n \n \n \n \n \n \n \n \n\n
\n @if (submitted()) {\n Uw adreswijziging is ontvangen. U ontvangt binnen 5 werkdagen bericht.\n } @else {\n \n }\n
\n
\n `,\n})\nexport class RegistrationDetailPage {\n private svc = inject(RegistrationService);\n reg = this.svc.registrationResource();\n regEmpty = (v: unknown) => !v || Object.keys(v).length === 0;\n submitted = signal(false);\n}\n", + "sourceCode": "import { Component, inject, signal } from '@angular/core';\nimport { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';\nimport { AlertComponent } from '@shared/ui/alert/alert.component';\nimport { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';\nimport { ASYNC } from '@shared/ui/async/async.component';\nimport { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';\nimport { ChangeRequestFormComponent } from '@registratie/ui/change-request-form/change-request-form.component';\nimport { BigProfileStore } from '@registratie/application/big-profile.store';\n\n@Component({\n selector: 'app-registration-detail-page',\n imports: [\n PageShellComponent, AlertComponent, SkeletonComponent, ...ASYNC,\n RegistrationSummaryComponent, ChangeRequestFormComponent,\n ],\n template: `\n \n \n \n \n \n \n \n \n \n\n
\n @if (submitted()) {\n Uw adreswijziging is ontvangen. U ontvangt binnen 5 werkdagen bericht.\n } @else {\n \n }\n
\n
\n `,\n})\nexport class RegistrationDetailPage {\n protected store = inject(BigProfileStore);\n submitted = signal(false);\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2252,8 +2898,8 @@ }, { "name": "RegistrationSummaryComponent", - "id": "component-RegistrationSummaryComponent-cad630ad4fd52e8743ba17ebf8f1cf8362c4f8677937ac6e41fadc055438d0c35c6756d7f2d39b67239ceb2af09e0a11c141fa9cd398f80e5d18a8e6960d90ee", - "file": "src/app/organisms/registration-summary/registration-summary.component.ts", + "id": "component-RegistrationSummaryComponent-15a446478708503b850ebbf635068398874034b3824504dc3a05802f6743029578088280540988dacfb6c768b92a2ee0371368369f455edc6dda03cf5f179e17", + "file": "src/app/registratie/ui/registration-summary/registration-summary.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -2262,7 +2908,7 @@ "selector": "app-registration-summary", "styleUrls": [], "styles": [], - "template": "
\n
\n \n \n \n \n \n \n \n \n @switch (reg().status.tag) {\n @case ('Geregistreerd') {\n \n }\n @case ('Geschorst') {\n \n \n }\n @case ('Doorgehaald') {\n \n \n }\n }\n
\n
\n", + "template": "
\n
\n \n \n \n \n \n \n \n \n @switch (reg().status.tag) {\n @case ('Geregistreerd') {\n \n }\n @case ('Geschorst') {\n \n \n }\n @case ('Doorgehaald') {\n \n \n }\n }\n
\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -2275,12 +2921,41 @@ "indexKey": "", "optional": false, "description": "", - "line": 40, + "line": 41, "required": true } ], "outputsClass": [], - "propertiesClass": [], + "propertiesClass": [ + { + "name": "color", + "defaultValue": "() => {...}", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 43, + "modifierKind": [ + 124 + ] + }, + { + "name": "label", + "defaultValue": "() => {...}", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 42, + "modifierKind": [ + 124 + ] + } + ], "methodsClass": [], "deprecated": false, "deprecationMessage": "", @@ -2304,7 +2979,7 @@ "description": "

Organism: registration summary card. Composes data-row molecules + status-badge atom.

\n", "rawdescription": "\nOrganism: registration summary card. Composes data-row molecules + status-badge atom.", "type": "component", - "sourceCode": "import { Component, input } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { Registration } from '../../core/models';\nimport { DataRowComponent } from '../../molecules/data-row/data-row.component';\nimport { StatusBadgeComponent } from '../../atoms/status-badge/status-badge.component';\n\n/** Organism: registration summary card. Composes data-row molecules + status-badge atom. */\n@Component({\n selector: 'app-registration-summary',\n imports: [DatePipe, DataRowComponent, StatusBadgeComponent],\n template: `\n
\n
\n \n \n \n \n \n \n \n \n @switch (reg().status.tag) {\n @case ('Geregistreerd') {\n \n }\n @case ('Geschorst') {\n \n \n }\n @case ('Doorgehaald') {\n \n \n }\n }\n
\n
\n `,\n})\nexport class RegistrationSummaryComponent {\n reg = input.required();\n}\n", + "sourceCode": "import { Component, input } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { Registration } from '@registratie/domain/registration';\nimport { statusColor, statusLabel } from '@registratie/domain/registration.policy';\nimport { DataRowComponent } from '@shared/ui/data-row/data-row.component';\nimport { StatusBadgeComponent } from '@shared/ui/status-badge/status-badge.component';\n\n/** Organism: registration summary card. Composes data-row molecules + status-badge atom. */\n@Component({\n selector: 'app-registration-summary',\n imports: [DatePipe, DataRowComponent, StatusBadgeComponent],\n template: `\n
\n
\n \n \n \n \n \n \n \n \n @switch (reg().status.tag) {\n @case ('Geregistreerd') {\n \n }\n @case ('Geschorst') {\n \n \n }\n @case ('Doorgehaald') {\n \n \n }\n }\n
\n
\n `,\n})\nexport class RegistrationSummaryComponent {\n reg = input.required();\n protected label = () => statusLabel(this.reg().status.tag);\n protected color = () => statusColor(this.reg().status.tag);\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2312,8 +2987,8 @@ }, { "name": "RegistrationTableComponent", - "id": "component-RegistrationTableComponent-53e1c5109b067c2b72ccd183e6350c59ea4bbd34a8f40a0f4e50c59d6165f5f8040177f74a592be42b8e571c4530ad2bf60c4a3a712689c82460997cb91ed770", - "file": "src/app/organisms/registration-table/registration-table.component.ts", + "id": "component-RegistrationTableComponent-d1fcc222260a33b20e1562a1bf325269cd07ebaed4838e1d74e883be3fbc1ef521d9250ba478316f93f8e133c0c5576cde367822159d6dff36d7b9da2c4d0a79", + "file": "src/app/registratie/ui/registration-table/registration-table.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -2356,7 +3031,7 @@ "description": "

Organism: table of specialismen/aantekeningen.

\n", "rawdescription": "\nOrganism: table of specialismen/aantekeningen.", "type": "component", - "sourceCode": "import { Component, input } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { Aantekening } from '../../core/models';\n\n/** Organism: table of specialismen/aantekeningen. */\n@Component({\n selector: 'app-registration-table',\n imports: [DatePipe],\n template: `\n
\n \n \n \n \n \n \n \n \n \n @for (row of rows(); track row.omschrijving) {\n \n \n \n \n \n }\n \n
TypeOmschrijvingDatum
{{ row.type }}{{ row.omschrijving }}{{ row.datum | date:'mediumDate' }}
\n
\n `,\n})\nexport class RegistrationTableComponent {\n rows = input.required();\n}\n", + "sourceCode": "import { Component, input } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { Aantekening } from '@registratie/domain/registration';\n\n/** Organism: table of specialismen/aantekeningen. */\n@Component({\n selector: 'app-registration-table',\n imports: [DatePipe],\n template: `\n
\n \n \n \n \n \n \n \n \n \n @for (row of rows(); track row.omschrijving) {\n \n \n \n \n \n }\n \n
TypeOmschrijvingDatum
{{ row.type }}{{ row.omschrijving }}{{ row.datum | date:'mediumDate' }}
\n
\n `,\n})\nexport class RegistrationTableComponent {\n rows = input.required();\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2364,8 +3039,8 @@ }, { "name": "ShellComponent", - "id": "component-ShellComponent-a435ec99fc0234f16b0f1ab3b0860d9ec513acbf7cb2cefcaec3d7e36fd4c943f9b5e91c76481402563c62d8a63dd1363b29ac130c4d9a731839eccc4bbd12c2", - "file": "src/app/templates/shell/shell.component.ts", + "id": "component-ShellComponent-a66788510e6c68711bf080318372cd2c0d790797488b8ff39c17623325904a91e1ac98f1d49d6f6f81f416cc64206126a4d0f1ce50d66403bdd3fb6b6f924279", + "file": "src/app/shared/layout/shell/shell.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -2405,7 +3080,7 @@ "description": "

Template: persistent app chrome. Header + footer mount once; only the routed\ncontent inside changes (and cross-fades — see styles.scss).

\n", "rawdescription": "\nTemplate: persistent app chrome. Header + footer mount once; only the routed\ncontent inside changes (and cross-fades — see styles.scss).", "type": "component", - "sourceCode": "import { Component } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\nimport { SiteHeaderComponent } from '../../organisms/site-header/site-header.component';\nimport { SiteFooterComponent } from '../../organisms/site-footer/site-footer.component';\n\n/** Template: persistent app chrome. Header + footer mount once; only the routed\n content inside changes (and cross-fades — see styles.scss). */\n@Component({\n selector: 'app-shell',\n imports: [RouterOutlet, SiteHeaderComponent, SiteFooterComponent],\n styles: [':host{display:block}'],\n template: `\n Naar de inhoud\n
\n \n
\n
\n \n
\n
\n \n
\n `,\n})\nexport class ShellComponent {}\n", + "sourceCode": "import { Component } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\nimport { SiteHeaderComponent } from '@shared/layout/site-header/site-header.component';\nimport { SiteFooterComponent } from '@shared/layout/site-footer/site-footer.component';\n\n/** Template: persistent app chrome. Header + footer mount once; only the routed\n content inside changes (and cross-fades — see styles.scss). */\n@Component({\n selector: 'app-shell',\n imports: [RouterOutlet, SiteHeaderComponent, SiteFooterComponent],\n styles: [':host{display:block}'],\n template: `\n Naar de inhoud\n
\n \n
\n
\n \n
\n
\n \n
\n `,\n})\nexport class ShellComponent {}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": ":host{display:block}\n", @@ -2414,7 +3089,7 @@ { "name": "SiteFooterComponent", "id": "component-SiteFooterComponent-90391287c6e8f3ad80c034a7a6fdabdd0514fd9c0dcf549a8e4a2d3bc3d885fcd5a45a2eeeb24fa8a4bbcf2cfe865093d712765d9d1ed11b4bfca26d1fb35153", - "file": "src/app/organisms/site-footer/site-footer.component.ts", + "file": "src/app/shared/layout/site-footer/site-footer.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -2451,7 +3126,7 @@ { "name": "SiteHeaderComponent", "id": "component-SiteHeaderComponent-710f1c109f26ae5f1888aa77ac682c58c9534fbba74aeae04b903e169f674c1ce04b8ae9e379a936c8ddef16e53c500e93d132021bcdd4ef743a601e05808f66", - "file": "src/app/organisms/site-header/site-header.component.ts", + "file": "src/app/shared/layout/site-header/site-header.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -2504,7 +3179,7 @@ { "name": "SkeletonComponent", "id": "component-SkeletonComponent-608d7384a7cd5705153c042fd961a63d69e4f61409276c2aec354b33db4199192d161fa81305090c262e18ab0a019913f469e85603ccefa6fba5c0dbc40d4f72", - "file": "src/app/atoms/skeleton/skeleton.component.ts", + "file": "src/app/shared/ui/skeleton/skeleton.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -2653,7 +3328,7 @@ { "name": "SpinnerComponent", "id": "component-SpinnerComponent-4ac83777af1737425f1eec0d4c22a830297cca57c3140ef014842789163b2c2873312a9ae4e9dc7554241ac3b3dc339b7bab22e02fd8a4fe748a985264126ca4", - "file": "src/app/atoms/spinner/spinner.component.ts", + "file": "src/app/shared/ui/spinner/spinner.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -2754,8 +3429,8 @@ }, { "name": "StatusBadgeComponent", - "id": "component-StatusBadgeComponent-5cb9f831687f564e86bbc72b1276fd5b10bc1bc877bf15a2dfff580724e2795f7abdc861ad6ccce41846084a022c11d4c570924d3f7ec1388a1cd5e31b9fd8a2", - "file": "src/app/atoms/status-badge/status-badge.component.ts", + "id": "component-StatusBadgeComponent-4f1df6eb29e4abe21602ed3d03fe37881d253c2a9653306c7c95978022565da00b7c482db530f0eea482adf500359371cc588e7abcc8378d7e422cd7a18f00ad", + "file": "src/app/shared/ui/status-badge/status-badge.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -2764,37 +3439,36 @@ "selector": "app-status-badge", "styleUrls": [], "styles": [], - "template": "\n \n {{ status() }}\n\n", + "template": "\n \n {{ label() }}\n\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], "inputsClass": [ { - "name": "status", + "name": "color", "deprecated": false, "deprecationMessage": "", - "type": "StatusTag", + "type": "string", "indexKey": "", "optional": false, "description": "", - "line": 18, + "line": 17, + "required": true + }, + { + "name": "label", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 16, "required": true } ], "outputsClass": [], - "propertiesClass": [ - { - "name": "color", - "defaultValue": "computed(() => {\n const tag = this.status();\n switch (tag) {\n case 'Geregistreerd':\n return 'var(--rhc-color-groen-500)';\n case 'Doorgehaald':\n return 'var(--rhc-color-rood-500)';\n case 'Geschorst':\n return 'var(--rhc-color-oranje-500)';\n default:\n return assertNever(tag);\n }\n })", - "deprecated": false, - "deprecationMessage": "", - "type": "unknown", - "indexKey": "", - "optional": false, - "description": "", - "line": 19 - } - ], + "propertiesClass": [], "methodsClass": [], "deprecated": false, "deprecationMessage": "", @@ -2802,10 +3476,10 @@ "hostListeners": [], "standalone": false, "imports": [], - "description": "

Atom: status badge = colored RHC dot-badge + label. The color is a total\nfunction of the status tag — assertNever makes a new status fail to compile\nuntil a color is chosen for it.

\n", - "rawdescription": "\nAtom: status badge = colored RHC dot-badge + label. The color is a total\nfunction of the status tag — assertNever makes a new status fail to compile\nuntil a color is chosen for it.", + "description": "

Atom: a coloured dot + label. Purely presentational and domain-free — the\ncaller decides what colour and label mean (e.g. via registration.policy).\nThis keeps the shared UI kernel free of any domain knowledge.

\n", + "rawdescription": "\nAtom: a coloured dot + label. Purely presentational and domain-free — the\ncaller decides what colour and label mean (e.g. via registration.policy).\nThis keeps the shared UI kernel free of any domain knowledge.", "type": "component", - "sourceCode": "import { Component, computed, input } from '@angular/core';\nimport { StatusTag } from '../../core/models';\nimport { assertNever } from '../../core/fp';\n\n/** Atom: status badge = colored RHC dot-badge + label. The color is a total\n function of the status tag — assertNever makes a new status fail to compile\n until a color is chosen for it. */\n@Component({\n selector: 'app-status-badge',\n template: `\n \n \n {{ status() }}\n \n `,\n})\nexport class StatusBadgeComponent {\n status = input.required();\n color = computed(() => {\n const tag = this.status();\n switch (tag) {\n case 'Geregistreerd':\n return 'var(--rhc-color-groen-500)';\n case 'Doorgehaald':\n return 'var(--rhc-color-rood-500)';\n case 'Geschorst':\n return 'var(--rhc-color-oranje-500)';\n default:\n return assertNever(tag);\n }\n });\n}\n", + "sourceCode": "import { Component, input } from '@angular/core';\n\n/** Atom: a coloured dot + label. Purely presentational and domain-free — the\n caller decides what colour and label mean (e.g. via registration.policy).\n This keeps the shared UI kernel free of any domain knowledge. */\n@Component({\n selector: 'app-status-badge',\n template: `\n \n \n {{ label() }}\n \n `,\n})\nexport class StatusBadgeComponent {\n label = input.required();\n color = input.required();\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2814,7 +3488,7 @@ { "name": "TextInputComponent", "id": "component-TextInputComponent-9d0cd6c2d7892958e9a016b570670f0186d410ea16a22925126f9eeea1aa178f081a4e5647af633f8a0bc1c458422a9f30169f159f930e9048702995fbb827e6", - "file": "src/app/atoms/text-input/text-input.component.ts", + "file": "src/app/shared/ui/text-input/text-input.component.ts", "encapsulation": [], "entryComponents": [], "inputs": [], @@ -3145,7 +3819,7 @@ "name": "ASYNC", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/molecules/async/async.component.ts", + "file": "src/app/shared/ui/async/async.component.ts", "deprecated": false, "deprecationMessage": "", "type": "unknown", @@ -3153,11 +3827,23 @@ "rawdescription": "Convenience: import this array to get the wrapper + all slot directives.", "description": "

Convenience: import this array to get the wrapper + all slot directives.

\n" }, + { + "name": "authGuard", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/auth/auth.guard.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "CanActivateFn", + "defaultValue": "() => {\n const store = inject(SessionStore);\n const router = inject(Router);\n return store.isAuthenticated() ? true : router.createUrlTree(['/login']);\n}", + "rawdescription": "Route guard: only let authenticated users in; otherwise redirect to /login.", + "description": "

Route guard: only let authenticated users in; otherwise redirect to /login.

\n" + }, { "name": "err", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/core/fp.ts", + "file": "src/app/shared/kernel/fp.ts", "deprecated": false, "deprecationMessage": "", "type": "unknown", @@ -3167,7 +3853,7 @@ "name": "initial", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "deprecated": false, "deprecationMessage": "", "type": "WizardState", @@ -3177,7 +3863,7 @@ "name": "ok", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/core/fp.ts", + "file": "src/app/shared/kernel/fp.ts", "deprecated": false, "deprecationMessage": "", "type": "unknown", @@ -3191,13 +3877,13 @@ "deprecated": false, "deprecationMessage": "", "type": "Routes", - "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./pages/concepts/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" + "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('@auth/ui/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', canActivate: [authGuard], loadComponent: () => \"import('@herregistratie/ui/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./showcase/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" }, { "name": "scenarioInterceptor", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/core/scenario.interceptor.ts", + "file": "src/app/shared/infrastructure/scenario.interceptor.ts", "deprecated": false, "deprecationMessage": "", "type": "HttpInterceptorFn", @@ -3209,7 +3895,7 @@ "name": "VALID", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/core/scenario.ts", + "file": "src/app/shared/infrastructure/scenario.ts", "deprecated": false, "deprecationMessage": "", "type": "Scenario[]", @@ -3217,9 +3903,51 @@ } ], "functions": [ + { + "name": "andThen", + "file": "src/app/shared/application/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Chain a second source that depends on the first one's value.

\n", + "args": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "assertNever", - "file": "src/app/core/fp.ts", + "file": "src/app/shared/kernel/fp.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3248,7 +3976,7 @@ }, { "name": "back", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3275,9 +4003,51 @@ } ] }, + { + "name": "createStore", + "file": "src/app/shared/application/store.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "init", + "type": "Model", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "update", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Store", + "jsdoctags": [ + { + "name": "init", + "type": "Model", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "update", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "currentScenario", - "file": "src/app/core/scenario.ts", + "file": "src/app/shared/infrastructure/scenario.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3288,7 +4058,7 @@ }, { "name": "fakeResource", - "file": "src/app/pages/concepts/concepts.page.ts", + "file": "src/app/showcase/concepts.page.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3351,7 +4121,7 @@ }, { "name": "foldRemote", - "file": "src/app/core/remote-data.ts", + "file": "src/app/shared/application/remote-data.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3393,7 +4163,7 @@ }, { "name": "fromResource", - "file": "src/app/core/remote-data.ts", + "file": "src/app/shared/application/remote-data.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3435,9 +4205,326 @@ } ] }, + { + "name": "herregistratieDeadline", + "file": "src/app/registratie/domain/registration.policy.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

The herregistratie deadline, if the status has one (only the active state does).

\n", + "args": [ + { + "name": "reg", + "type": "Registration", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Date | null", + "jsdoctags": [ + { + "name": "reg", + "type": "Registration", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "isAuthenticated", + "file": "src/app/auth/domain/session.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Session", + "jsdoctags": [ + { + "name": "s", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "isHerregistratieEligible", + "file": "src/app/registratie/domain/registration.policy.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

A registration may apply for herregistratie only while active and within the\nwindow before its deadline. A struck-off or suspended registration may not.

\n", + "args": [ + { + "name": "reg", + "type": "Registration", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "today", + "type": "Date", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "windowMonths", + "type": "number", + "deprecated": false, + "deprecationMessage": "", + "defaultValue": "12" + } + ], + "returnType": "boolean", + "jsdoctags": [ + { + "name": "reg", + "type": "Registration", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "today", + "type": "Date", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "windowMonths", + "type": "number", + "deprecated": false, + "deprecationMessage": "", + "defaultValue": "12", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "isStatusConsistent", + "file": "src/app/registratie/domain/registration.policy.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Invariant check used in tests/demos: a non-active status must not carry a\nherregistratie date. The union already enforces this structurally; this is\nthe runtime statement of the same rule.

\n", + "args": [ + { + "name": "status", + "type": "RegistrationStatus", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "boolean", + "jsdoctags": [ + { + "name": "status", + "type": "RegistrationStatus", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "map", + "file": "src/app/shared/application/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Transform the value inside a Success; pass other states through unchanged.

\n", + "args": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "map2", + "file": "src/app/shared/application/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Combine two sources into one. Use this to merge e.g. a BIG-register call\nand a BRP call into a single state the page can render.

\n", + "args": [ + { + "name": "a", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "b", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "a", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "b", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "map3", + "file": "src/app/shared/application/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Combine three sources (built on map2).

\n", + "args": [ + { + "name": "a", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "b", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "c", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "a", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "b", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "c", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "next", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3464,9 +4551,38 @@ } ] }, + { + "name": "parseBigNummer", + "file": "src/app/registratie/domain/value-objects/big-nummer.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result", + "jsdoctags": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "parsePostcode", - "file": "src/app/core/parse.ts", + "file": "src/app/registratie/domain/value-objects/postcode.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3495,7 +4611,7 @@ }, { "name": "parseUren", - "file": "src/app/core/parse.ts", + "file": "src/app/registratie/domain/value-objects/uren.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3522,9 +4638,53 @@ } ] }, + { + "name": "reduce", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "m", + "type": "WizardMsg", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "m", + "type": "WizardMsg", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "resolve", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3566,9 +4726,124 @@ } ] }, + { + "name": "setField", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Update one draft field while editing; ignored in any other state.

\n", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "key", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "value", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "key", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "value", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "statusColor", + "file": "src/app/registratie/domain/registration.policy.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Brand colour token for a status. assertNever forces a colour for every new\nstatus variant at compile time.

\n", + "args": [ + { + "name": "tag", + "type": "StatusTag", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "string", + "jsdoctags": [ + { + "name": "tag", + "type": "StatusTag", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "statusLabel", + "file": "src/app/registratie/domain/registration.policy.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Domain logic for a registration — pure functions, NO Angular. This is where\n"what the business rules say" lives, separate from "how it looks" (UI) and\n"where the data comes from" (infrastructure). Keeping it framework-free means\nit is trivial to read and unit-test.

\n", + "args": [ + { + "name": "tag", + "type": "StatusTag", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "string", + "jsdoctags": [ + { + "name": "tag", + "type": "StatusTag", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "submit", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3595,9 +4870,38 @@ } ] }, + { + "name": "submitHerregistratie", + "file": "src/app/herregistratie/application/submit-herregistratie.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

The mutation/command: send a herregistratie application to the backend.\nReturns a Result so the caller can branch on success/failure without\ntry/catch. ponytail: faked with a timer; swap for a real POST when there's\nan API. The "uren must be > 0" rule lets the demo show a failure path.

\n", + "args": [ + { + "name": "data", + "type": "Valid", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Promise>", + "jsdoctags": [ + { + "name": "data", + "type": "Valid", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "validate", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3631,32 +4935,54 @@ "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "\"info\" | \"ok\" | \"warning\" | \"error\"", - "file": "src/app/atoms/alert/alert.component.ts", + "file": "src/app/shared/ui/alert/alert.component.ts", "deprecated": false, "deprecationMessage": "", "description": "", "kind": 193 }, + { + "name": "BigNummer", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "Brand", + "file": "src/app/registratie/domain/value-objects/big-nummer.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Value object: a BIG registration number — 11 digits.

\n", + "kind": 184 + }, { "name": "Brand", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "unknown", - "file": "src/app/core/fp.ts", + "file": "src/app/shared/kernel/fp.ts", "deprecated": false, "deprecationMessage": "", "description": "

Nominal typing: Brand<string, 'Postcode'> is assignable from a plain string\nonly through an explicit cast — so a smart constructor is the only minter.

\n", "kind": 194 }, + { + "name": "Err", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "Error | undefined", + "file": "src/app/registratie/application/big-profile.store.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "kind": 193 + }, { "name": "Postcode", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "Brand", - "file": "src/app/core/parse.ts", + "file": "src/app/registratie/domain/value-objects/postcode.ts", "deprecated": false, "deprecationMessage": "", - "description": "

"Parse, don't validate." A validated value gets its own branded type, and the\nsmart constructor is the ONLY way to mint one. So once you hold a Postcode you\nknow it's well-formed — validity is carried in the type, not re-checked\neverywhere or tracked in a parallel error flag.

\n", + "description": "

Value object: a Dutch postcode. "Parse, don't validate" — a Postcode is a\ndistinct type from a raw string, mintable only via parsePostcode, so holding\none is proof it is well-formed.

\n", "kind": 184 }, { @@ -3664,7 +4990,7 @@ "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "literal type | literal type | literal type", - "file": "src/app/core/models.ts", + "file": "src/app/registratie/domain/registration.ts", "deprecated": false, "deprecationMessage": "", "description": "

Registration status as a discriminated union: each variant owns exactly the\ndata that makes sense for it. Only an active (Geregistreerd) registration has\na herregistratie date; a struck-off (Doorgehaald) one cannot carry one. The\nold flat interface allowed that impossible combination — this makes it\nunrepresentable.

\n", @@ -3675,7 +5001,7 @@ "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "literal type | literal type | literal type | literal type", - "file": "src/app/core/remote-data.ts", + "file": "src/app/shared/application/remote-data.ts", "deprecated": false, "deprecationMessage": "", "description": "

The four mutually-exclusive states of an async fetch, as a tagged union.\nCrucially the data lives ON the state: only Failure has an error, only\nSuccess has a value. "Loaded but no value" or "error with stale value"\nare unrepresentable — Richard Feldman's RemoteData.

\n", @@ -3686,7 +5012,7 @@ "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "literal type | literal type", - "file": "src/app/core/fp.ts", + "file": "src/app/shared/kernel/fp.ts", "deprecated": false, "deprecationMessage": "", "description": "

A computation that either succeeded with a value or failed with an error.\nPlain objects (no classes) to match the signal/httpResource ergonomics.

\n", @@ -3697,7 +5023,7 @@ "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "\"default\" | \"slow\" | \"loading\" | \"empty\" | \"error\"", - "file": "src/app/core/scenario.ts", + "file": "src/app/shared/infrastructure/scenario.ts", "deprecated": false, "deprecationMessage": "", "description": "", @@ -3708,7 +5034,7 @@ "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "RegistrationStatus", - "file": "src/app/core/models.ts", + "file": "src/app/registratie/domain/registration.ts", "deprecated": false, "deprecationMessage": "", "description": "

Just the discriminant — for atoms that only need the label/color.

\n", @@ -3719,10 +5045,10 @@ "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "Brand", - "file": "src/app/core/parse.ts", + "file": "src/app/registratie/domain/value-objects/uren.ts", "deprecated": false, "deprecationMessage": "", - "description": "", + "description": "

Value object: a non-negative whole number of hours.

\n", "kind": 184 }, { @@ -3730,18 +5056,29 @@ "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "\"primary\" | \"secondary\" | \"subtle\" | \"danger\"", - "file": "src/app/atoms/button/button.component.ts", + "file": "src/app/shared/ui/button/button.component.ts", "deprecated": false, "deprecationMessage": "", "description": "", "kind": 193 }, + { + "name": "WizardMsg", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type | literal type | literal type | literal type | literal type | literal type", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Every event that can happen to the wizard, as one message type. The component\nsends a WizardMsg; reduce decides the next state. This is the Elm\nModel+Msg+update pattern: ONE pure function describes all state changes.

\n", + "kind": 193 + }, { "name": "WizardState", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "literal type | literal type | literal type | literal type", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "deprecated": false, "deprecationMessage": "", "description": "

The whole wizard as one tagged union. step and errors exist ONLY while\nEditing; Submitting/Submitted/Failed carry a Valid payload and nothing else.\nSo "submitting while a field is invalid" or "showing the success screen with\nerrors set" are unrepresentable — the bug class is gone by construction.

\n", @@ -3762,12 +5099,12 @@ "defaultValue": "{\n providers: [\n provideBrowserGlobalErrorListeners(),\n provideRouter(routes, withViewTransitions()),\n provideHttpClient(withInterceptors([scenarioInterceptor])),\n { provide: LOCALE_ID, useValue: 'nl' },\n ]\n}" } ], - "src/app/molecules/async/async.component.ts": [ + "src/app/shared/ui/async/async.component.ts": [ { "name": "ASYNC", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/molecules/async/async.component.ts", + "file": "src/app/shared/ui/async/async.component.ts", "deprecated": false, "deprecationMessage": "", "type": "unknown", @@ -3776,12 +5113,26 @@ "description": "

Convenience: import this array to get the wrapper + all slot directives.

\n" } ], - "src/app/core/fp.ts": [ + "src/app/auth/auth.guard.ts": [ + { + "name": "authGuard", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/auth/auth.guard.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "CanActivateFn", + "defaultValue": "() => {\n const store = inject(SessionStore);\n const router = inject(Router);\n return store.isAuthenticated() ? true : router.createUrlTree(['/login']);\n}", + "rawdescription": "Route guard: only let authenticated users in; otherwise redirect to /login.", + "description": "

Route guard: only let authenticated users in; otherwise redirect to /login.

\n" + } + ], + "src/app/shared/kernel/fp.ts": [ { "name": "err", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/core/fp.ts", + "file": "src/app/shared/kernel/fp.ts", "deprecated": false, "deprecationMessage": "", "type": "unknown", @@ -3791,19 +5142,19 @@ "name": "ok", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/core/fp.ts", + "file": "src/app/shared/kernel/fp.ts", "deprecated": false, "deprecationMessage": "", "type": "unknown", "defaultValue": "(value: T): Result => ({ ok: true, value })" } ], - "src/app/organisms/herregistratie-wizard/wizard.machine.ts": [ + "src/app/herregistratie/domain/herregistratie.machine.ts": [ { "name": "initial", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "deprecated": false, "deprecationMessage": "", "type": "WizardState", @@ -3819,15 +5170,15 @@ "deprecated": false, "deprecationMessage": "", "type": "Routes", - "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./pages/concepts/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" + "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('@auth/ui/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', canActivate: [authGuard], loadComponent: () => \"import('@registratie/ui/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', canActivate: [authGuard], loadComponent: () => \"import('@herregistratie/ui/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./showcase/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" } ], - "src/app/core/scenario.interceptor.ts": [ + "src/app/shared/infrastructure/scenario.interceptor.ts": [ { "name": "scenarioInterceptor", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/core/scenario.interceptor.ts", + "file": "src/app/shared/infrastructure/scenario.interceptor.ts", "deprecated": false, "deprecationMessage": "", "type": "HttpInterceptorFn", @@ -3836,12 +5187,12 @@ "description": "

Demo-only: rewrites the timing/outcome of mock data requests based on\n?scenario= so loading / empty / error states can be shown on demand.\nReal requests are untouched.

\n" } ], - "src/app/core/scenario.ts": [ + "src/app/shared/infrastructure/scenario.ts": [ { "name": "VALID", "ctype": "miscellaneous", "subtype": "variable", - "file": "src/app/core/scenario.ts", + "file": "src/app/shared/infrastructure/scenario.ts", "deprecated": false, "deprecationMessage": "", "type": "Scenario[]", @@ -3850,10 +5201,311 @@ ] }, "groupedFunctions": { - "src/app/core/fp.ts": [ + "src/app/shared/application/remote-data.ts": [ + { + "name": "andThen", + "file": "src/app/shared/application/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Chain a second source that depends on the first one's value.

\n", + "args": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "foldRemote", + "file": "src/app/shared/application/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Exhaustive fold: you must handle every case, checked at compile time.

\n", + "args": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "h", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "R", + "jsdoctags": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "h", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "fromResource", + "file": "src/app/shared/application/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Project Angular's loosely-typed Resource into a RemoteData value.

\n", + "args": [ + { + "name": "r", + "type": "Resource", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "isEmpty", + "deprecated": false, + "deprecationMessage": "", + "defaultValue": "() => false" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "r", + "type": "Resource", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "isEmpty", + "deprecated": false, + "deprecationMessage": "", + "defaultValue": "() => false", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "map", + "file": "src/app/shared/application/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Transform the value inside a Success; pass other states through unchanged.

\n", + "args": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "map2", + "file": "src/app/shared/application/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Combine two sources into one. Use this to merge e.g. a BIG-register call\nand a BRP call into a single state the page can render.

\n", + "args": [ + { + "name": "a", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "b", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "a", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "b", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "map3", + "file": "src/app/shared/application/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Combine three sources (built on map2).

\n", + "args": [ + { + "name": "a", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "b", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "c", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "a", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "b", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "c", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "f", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } + ], + "src/app/shared/kernel/fp.ts": [ { "name": "assertNever", - "file": "src/app/core/fp.ts", + "file": "src/app/shared/kernel/fp.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3881,10 +5533,10 @@ ] } ], - "src/app/organisms/herregistratie-wizard/wizard.machine.ts": [ + "src/app/herregistratie/domain/herregistratie.machine.ts": [ { "name": "back", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3913,7 +5565,7 @@ }, { "name": "next", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3940,9 +5592,53 @@ } ] }, + { + "name": "reduce", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "m", + "type": "WizardMsg", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "m", + "type": "WizardMsg", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "resolve", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -3984,9 +5680,66 @@ } ] }, + { + "name": "setField", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Update one draft field while editing; ignored in any other state.

\n", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "key", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "value", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "key", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "value", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "submit", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -4015,7 +5768,7 @@ }, { "name": "validate", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -4043,10 +5796,54 @@ ] } ], - "src/app/core/scenario.ts": [ + "src/app/shared/application/store.ts": [ + { + "name": "createStore", + "file": "src/app/shared/application/store.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "init", + "type": "Model", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "update", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Store", + "jsdoctags": [ + { + "name": "init", + "type": "Model", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "update", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } + ], + "src/app/shared/infrastructure/scenario.ts": [ { "name": "currentScenario", - "file": "src/app/core/scenario.ts", + "file": "src/app/shared/infrastructure/scenario.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -4056,10 +5853,10 @@ "returnType": "Scenario" } ], - "src/app/pages/concepts/concepts.page.ts": [ + "src/app/showcase/concepts.page.ts": [ { "name": "fakeResource", - "file": "src/app/pages/concepts/concepts.page.ts", + "file": "src/app/showcase/concepts.page.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -4121,41 +5918,28 @@ ] } ], - "src/app/core/remote-data.ts": [ + "src/app/registratie/domain/registration.policy.ts": [ { - "name": "foldRemote", - "file": "src/app/core/remote-data.ts", + "name": "herregistratieDeadline", + "file": "src/app/registratie/domain/registration.policy.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, "deprecationMessage": "", - "description": "

Exhaustive fold: you must handle every case, checked at compile time.

\n", + "description": "

The herregistratie deadline, if the status has one (only the active state does).

\n", "args": [ { - "name": "rd", - "type": "RemoteData", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "h", + "name": "reg", + "type": "Registration", "deprecated": false, "deprecationMessage": "" } ], - "returnType": "R", + "returnType": "Date | null", "jsdoctags": [ { - "name": "rd", - "type": "RemoteData", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "h", + "name": "reg", + "type": "Registration", "deprecated": false, "deprecationMessage": "", "tagName": { @@ -4165,32 +5949,39 @@ ] }, { - "name": "fromResource", - "file": "src/app/core/remote-data.ts", + "name": "isHerregistratieEligible", + "file": "src/app/registratie/domain/registration.policy.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, "deprecationMessage": "", - "description": "

Project Angular's loosely-typed Resource into a RemoteData value.

\n", + "description": "

A registration may apply for herregistratie only while active and within the\nwindow before its deadline. A struck-off or suspended registration may not.

\n", "args": [ { - "name": "r", - "type": "Resource", + "name": "reg", + "type": "Registration", "deprecated": false, "deprecationMessage": "" }, { - "name": "isEmpty", + "name": "today", + "type": "Date", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "windowMonths", + "type": "number", "deprecated": false, "deprecationMessage": "", - "defaultValue": "() => false" + "defaultValue": "12" } ], - "returnType": "RemoteData", + "returnType": "boolean", "jsdoctags": [ { - "name": "r", - "type": "Resource", + "name": "reg", + "type": "Registration", "deprecated": false, "deprecationMessage": "", "tagName": { @@ -4198,10 +5989,107 @@ } }, { - "name": "isEmpty", + "name": "today", + "type": "Date", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "windowMonths", + "type": "number", + "deprecated": false, + "deprecationMessage": "", + "defaultValue": "12", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "isStatusConsistent", + "file": "src/app/registratie/domain/registration.policy.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Invariant check used in tests/demos: a non-active status must not carry a\nherregistratie date. The union already enforces this structurally; this is\nthe runtime statement of the same rule.

\n", + "args": [ + { + "name": "status", + "type": "RegistrationStatus", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "boolean", + "jsdoctags": [ + { + "name": "status", + "type": "RegistrationStatus", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "statusColor", + "file": "src/app/registratie/domain/registration.policy.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Brand colour token for a status. assertNever forces a colour for every new\nstatus variant at compile time.

\n", + "args": [ + { + "name": "tag", + "type": "StatusTag", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "string", + "jsdoctags": [ + { + "name": "tag", + "type": "StatusTag", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "statusLabel", + "file": "src/app/registratie/domain/registration.policy.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Domain logic for a registration — pure functions, NO Angular. This is where\n"what the business rules say" lives, separate from "how it looks" (UI) and\n"where the data comes from" (infrastructure). Keeping it framework-free means\nit is trivial to read and unit-test.

\n", + "args": [ + { + "name": "tag", + "type": "StatusTag", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "string", + "jsdoctags": [ + { + "name": "tag", + "type": "StatusTag", "deprecated": false, "deprecationMessage": "", - "defaultValue": "() => false", "tagName": { "text": "param" } @@ -4209,10 +6097,70 @@ ] } ], - "src/app/core/parse.ts": [ + "src/app/auth/domain/session.ts": [ + { + "name": "isAuthenticated", + "file": "src/app/auth/domain/session.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Session", + "jsdoctags": [ + { + "name": "s", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } + ], + "src/app/registratie/domain/value-objects/big-nummer.ts": [ + { + "name": "parseBigNummer", + "file": "src/app/registratie/domain/value-objects/big-nummer.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result", + "jsdoctags": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } + ], + "src/app/registratie/domain/value-objects/postcode.ts": [ { "name": "parsePostcode", - "file": "src/app/core/parse.ts", + "file": "src/app/registratie/domain/value-objects/postcode.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -4238,10 +6186,12 @@ } } ] - }, + } + ], + "src/app/registratie/domain/value-objects/uren.ts": [ { "name": "parseUren", - "file": "src/app/core/parse.ts", + "file": "src/app/registratie/domain/value-objects/uren.ts", "ctype": "miscellaneous", "subtype": "function", "deprecated": false, @@ -4268,30 +6218,74 @@ } ] } + ], + "src/app/herregistratie/application/submit-herregistratie.ts": [ + { + "name": "submitHerregistratie", + "file": "src/app/herregistratie/application/submit-herregistratie.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

The mutation/command: send a herregistratie application to the backend.\nReturns a Result so the caller can branch on success/failure without\ntry/catch. ponytail: faked with a timer; swap for a real POST when there's\nan API. The "uren must be > 0" rule lets the demo show a failure path.

\n", + "args": [ + { + "name": "data", + "type": "Valid", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Promise>", + "jsdoctags": [ + { + "name": "data", + "type": "Valid", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } ] }, "groupedEnumerations": {}, "groupedTypeAliases": { - "src/app/atoms/alert/alert.component.ts": [ + "src/app/shared/ui/alert/alert.component.ts": [ { "name": "AlertType", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "\"info\" | \"ok\" | \"warning\" | \"error\"", - "file": "src/app/atoms/alert/alert.component.ts", + "file": "src/app/shared/ui/alert/alert.component.ts", "deprecated": false, "deprecationMessage": "", "description": "", "kind": 193 } ], - "src/app/core/fp.ts": [ + "src/app/registratie/domain/value-objects/big-nummer.ts": [ + { + "name": "BigNummer", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "Brand", + "file": "src/app/registratie/domain/value-objects/big-nummer.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Value object: a BIG registration number — 11 digits.

\n", + "kind": 184 + } + ], + "src/app/shared/kernel/fp.ts": [ { "name": "Brand", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "unknown", - "file": "src/app/core/fp.ts", + "file": "src/app/shared/kernel/fp.ts", "deprecated": false, "deprecationMessage": "", "description": "

Nominal typing: Brand<string, 'Postcode'> is assignable from a plain string\nonly through an explicit cast — so a smart constructor is the only minter.

\n", @@ -4302,44 +6296,46 @@ "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "literal type | literal type", - "file": "src/app/core/fp.ts", + "file": "src/app/shared/kernel/fp.ts", "deprecated": false, "deprecationMessage": "", "description": "

A computation that either succeeded with a value or failed with an error.\nPlain objects (no classes) to match the signal/httpResource ergonomics.

\n", "kind": 193 } ], - "src/app/core/parse.ts": [ + "src/app/registratie/application/big-profile.store.ts": [ + { + "name": "Err", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "Error | undefined", + "file": "src/app/registratie/application/big-profile.store.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "kind": 193 + } + ], + "src/app/registratie/domain/value-objects/postcode.ts": [ { "name": "Postcode", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "Brand", - "file": "src/app/core/parse.ts", + "file": "src/app/registratie/domain/value-objects/postcode.ts", "deprecated": false, "deprecationMessage": "", - "description": "

"Parse, don't validate." A validated value gets its own branded type, and the\nsmart constructor is the ONLY way to mint one. So once you hold a Postcode you\nknow it's well-formed — validity is carried in the type, not re-checked\neverywhere or tracked in a parallel error flag.

\n", - "kind": 184 - }, - { - "name": "Uren", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "Brand", - "file": "src/app/core/parse.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", + "description": "

Value object: a Dutch postcode. "Parse, don't validate" — a Postcode is a\ndistinct type from a raw string, mintable only via parsePostcode, so holding\none is proof it is well-formed.

\n", "kind": 184 } ], - "src/app/core/models.ts": [ + "src/app/registratie/domain/registration.ts": [ { "name": "RegistrationStatus", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "literal type | literal type | literal type", - "file": "src/app/core/models.ts", + "file": "src/app/registratie/domain/registration.ts", "deprecated": false, "deprecationMessage": "", "description": "

Registration status as a discriminated union: each variant owns exactly the\ndata that makes sense for it. Only an active (Geregistreerd) registration has\na herregistratie date; a struck-off (Doorgehaald) one cannot carry one. The\nold flat interface allowed that impossible combination — this makes it\nunrepresentable.

\n", @@ -4350,59 +6346,83 @@ "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "RegistrationStatus", - "file": "src/app/core/models.ts", + "file": "src/app/registratie/domain/registration.ts", "deprecated": false, "deprecationMessage": "", "description": "

Just the discriminant — for atoms that only need the label/color.

\n", "kind": 200 } ], - "src/app/core/remote-data.ts": [ + "src/app/shared/application/remote-data.ts": [ { "name": "RemoteData", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "literal type | literal type | literal type | literal type", - "file": "src/app/core/remote-data.ts", + "file": "src/app/shared/application/remote-data.ts", "deprecated": false, "deprecationMessage": "", "description": "

The four mutually-exclusive states of an async fetch, as a tagged union.\nCrucially the data lives ON the state: only Failure has an error, only\nSuccess has a value. "Loaded but no value" or "error with stale value"\nare unrepresentable — Richard Feldman's RemoteData.

\n", "kind": 193 } ], - "src/app/core/scenario.ts": [ + "src/app/shared/infrastructure/scenario.ts": [ { "name": "Scenario", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "\"default\" | \"slow\" | \"loading\" | \"empty\" | \"error\"", - "file": "src/app/core/scenario.ts", + "file": "src/app/shared/infrastructure/scenario.ts", "deprecated": false, "deprecationMessage": "", "description": "", "kind": 193 } ], - "src/app/atoms/button/button.component.ts": [ + "src/app/registratie/domain/value-objects/uren.ts": [ + { + "name": "Uren", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "Brand", + "file": "src/app/registratie/domain/value-objects/uren.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Value object: a non-negative whole number of hours.

\n", + "kind": 184 + } + ], + "src/app/shared/ui/button/button.component.ts": [ { "name": "Variant", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "\"primary\" | \"secondary\" | \"subtle\" | \"danger\"", - "file": "src/app/atoms/button/button.component.ts", + "file": "src/app/shared/ui/button/button.component.ts", "deprecated": false, "deprecationMessage": "", "description": "", "kind": 193 } ], - "src/app/organisms/herregistratie-wizard/wizard.machine.ts": [ + "src/app/herregistratie/domain/herregistratie.machine.ts": [ + { + "name": "WizardMsg", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type | literal type | literal type | literal type | literal type | literal type", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Every event that can happen to the wizard, as one message type. The component\nsends a WizardMsg; reduce decides the next state. This is the Elm\nModel+Msg+update pattern: ONE pure function describes all state changes.

\n", + "kind": 193 + }, { "name": "WizardState", "ctype": "miscellaneous", "subtype": "typealias", "rawtype": "literal type | literal type | literal type | literal type", - "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "file": "src/app/herregistratie/domain/herregistratie.machine.ts", "deprecated": false, "deprecationMessage": "", "description": "

The whole wizard as one tagged union. step and errors exist ONLY while\nEditing; Submitting/Submitted/Failed carry a Valid payload and nothing else.\nSo "submitting while a field is invalid" or "showing the success screen with\nerrors set" are unrepresentable — the bug class is gone by construction.

\n", @@ -4463,7 +6483,7 @@ ] }, "coverage": { - "count": 41, + "count": 47, "status": "medium", "files": [ { @@ -4496,406 +6516,82 @@ "status": "low" }, { - "filePath": "src/app/atoms/alert/alert.component.ts", - "type": "component", - "linktype": "component", - "name": "AlertComponent", - "coveragePercent": 50, - "coverageCount": "1/2", - "status": "medium" - }, - { - "filePath": "src/app/atoms/alert/alert.component.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "AlertType", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/atoms/button/button.component.ts", - "type": "component", - "linktype": "component", - "name": "ButtonComponent", - "coveragePercent": 25, - "coverageCount": "1/4", - "status": "low" - }, - { - "filePath": "src/app/atoms/button/button.component.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "Variant", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/atoms/heading/heading.component.ts", - "type": "component", - "linktype": "component", - "name": "HeadingComponent", - "coveragePercent": 50, - "coverageCount": "1/2", - "status": "medium" - }, - { - "filePath": "src/app/atoms/link/link.component.ts", - "type": "component", - "linktype": "component", - "name": "LinkComponent", - "coveragePercent": 50, - "coverageCount": "1/2", - "status": "medium" - }, - { - "filePath": "src/app/atoms/skeleton/skeleton.component.ts", - "type": "component", - "linktype": "component", - "name": "SkeletonComponent", - "coveragePercent": 10, - "coverageCount": "1/10", - "status": "low" - }, - { - "filePath": "src/app/atoms/spinner/spinner.component.ts", - "type": "component", - "linktype": "component", - "name": "SpinnerComponent", - "coveragePercent": 16, - "coverageCount": "1/6", - "status": "low" - }, - { - "filePath": "src/app/atoms/status-badge/status-badge.component.ts", - "type": "component", - "linktype": "component", - "name": "StatusBadgeComponent", - "coveragePercent": 33, - "coverageCount": "1/3", - "status": "medium" - }, - { - "filePath": "src/app/atoms/text-input/text-input.component.ts", - "type": "component", - "linktype": "component", - "name": "TextInputComponent", - "coveragePercent": 7, - "coverageCount": "1/14", - "status": "low" - }, - { - "filePath": "src/app/core/fp.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "assertNever", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/core/fp.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "err", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/core/fp.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "ok", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/core/fp.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "Brand", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/core/fp.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "Result", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/core/models.ts", - "type": "interface", - "linktype": "interface", - "name": "Aantekening", - "coveragePercent": 0, - "coverageCount": "0/4", - "status": "low" - }, - { - "filePath": "src/app/core/models.ts", - "type": "interface", - "linktype": "interface", - "name": "Registration", - "coveragePercent": 0, - "coverageCount": "0/7", - "status": "low" - }, - { - "filePath": "src/app/core/models.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "RegistrationStatus", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/core/models.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "StatusTag", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/core/parse.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "parsePostcode", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/core/parse.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "parseUren", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/core/parse.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "Postcode", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/core/parse.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "Uren", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/core/registration.service.ts", + "filePath": "src/app/auth/application/session.store.ts", "type": "injectable", "linktype": "injectable", - "name": "RegistrationService", - "coveragePercent": 33, - "coverageCount": "1/3", + "name": "SessionStore", + "coveragePercent": 28, + "coverageCount": "2/7", "status": "medium" }, { - "filePath": "src/app/core/remote-data.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "foldRemote", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/core/remote-data.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "fromResource", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/core/remote-data.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "RemoteData", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/core/scenario.interceptor.ts", + "filePath": "src/app/auth/auth.guard.ts", "type": "variable", "linktype": "miscellaneous", "linksubtype": "variable", - "name": "scenarioInterceptor", + "name": "authGuard", "coveragePercent": 100, "coverageCount": "1/1", "status": "very-good" }, { - "filePath": "src/app/core/scenario.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "currentScenario", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/core/scenario.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "VALID", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/core/scenario.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "Scenario", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/molecules/async/async.component.ts", - "type": "component", - "linktype": "component", - "name": "AsyncComponent", - "coveragePercent": 9, - "coverageCount": "1/11", - "status": "low" - }, - { - "filePath": "src/app/molecules/async/async.component.ts", - "type": "directive", - "linktype": "directive", - "name": "AsyncEmptyDirective", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/molecules/async/async.component.ts", - "type": "directive", - "linktype": "directive", - "name": "AsyncErrorDirective", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/molecules/async/async.component.ts", - "type": "directive", - "linktype": "directive", - "name": "AsyncLoadedDirective", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/molecules/async/async.component.ts", - "type": "directive", - "linktype": "directive", - "name": "AsyncLoadingDirective", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/molecules/async/async.component.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "ASYNC", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/molecules/data-row/data-row.component.ts", - "type": "component", - "linktype": "component", - "name": "DataRowComponent", - "coveragePercent": 33, - "coverageCount": "1/3", - "status": "medium" - }, - { - "filePath": "src/app/molecules/form-field/form-field.component.ts", - "type": "component", - "linktype": "component", - "name": "FormFieldComponent", - "coveragePercent": 20, - "coverageCount": "1/5", - "status": "low" - }, - { - "filePath": "src/app/organisms/change-request-form/change-request-form.component.ts", - "type": "component", - "linktype": "component", - "name": "ChangeRequestFormComponent", - "coveragePercent": 12, - "coverageCount": "1/8", - "status": "low" - }, - { - "filePath": "src/app/organisms/change-request-form/change-request-form.component.ts", + "filePath": "src/app/auth/domain/session.ts", "type": "interface", "linktype": "interface", - "name": "ChangeRequest", + "name": "Session", + "coveragePercent": 33, + "coverageCount": "1/3", + "status": "medium" + }, + { + "filePath": "src/app/auth/domain/session.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "isAuthenticated", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/auth/infrastructure/digid.adapter.ts", + "type": "injectable", + "linktype": "injectable", + "name": "DigidAdapter", + "coveragePercent": 50, + "coverageCount": "1/2", + "status": "medium" + }, + { + "filePath": "src/app/auth/ui/login-form/login-form.component.ts", + "type": "component", + "linktype": "component", + "name": "LoginFormComponent", "coveragePercent": 25, "coverageCount": "1/4", "status": "low" }, { - "filePath": "src/app/organisms/herregistratie-wizard/herregistratie-wizard.component.ts", + "filePath": "src/app/auth/ui/login.page.ts", "type": "component", "linktype": "component", - "name": "HerregistratieWizardComponent", - "coveragePercent": 14, - "coverageCount": "2/14", + "name": "LoginPage", + "coveragePercent": 0, + "coverageCount": "0/5", "status": "low" }, { - "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "filePath": "src/app/herregistratie/application/submit-herregistratie.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "submitHerregistratie", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "interface", "linktype": "interface", "name": "Draft", @@ -4904,7 +6600,7 @@ "status": "medium" }, { - "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "interface", "linktype": "interface", "name": "Valid", @@ -4913,7 +6609,7 @@ "status": "medium" }, { - "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "function", "linktype": "miscellaneous", "linksubtype": "function", @@ -4923,7 +6619,7 @@ "status": "low" }, { - "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "function", "linktype": "miscellaneous", "linksubtype": "function", @@ -4933,7 +6629,17 @@ "status": "very-good" }, { - "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "reduce", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "function", "linktype": "miscellaneous", "linksubtype": "function", @@ -4943,7 +6649,17 @@ "status": "very-good" }, { - "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "setField", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "function", "linktype": "miscellaneous", "linksubtype": "function", @@ -4953,7 +6669,7 @@ "status": "very-good" }, { - "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "function", "linktype": "miscellaneous", "linksubtype": "function", @@ -4963,7 +6679,7 @@ "status": "very-good" }, { - "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "variable", "linktype": "miscellaneous", "linksubtype": "variable", @@ -4973,7 +6689,17 @@ "status": "low" }, { - "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "WizardMsg", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/herregistratie/domain/herregistratie.machine.ts", "type": "type alias", "linktype": "miscellaneous", "linksubtype": "typealias", @@ -4983,25 +6709,282 @@ "status": "very-good" }, { - "filePath": "src/app/organisms/login-form/login-form.component.ts", + "filePath": "src/app/herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component.ts", "type": "component", "linktype": "component", - "name": "LoginFormComponent", + "name": "HerregistratieWizardComponent", + "coveragePercent": 18, + "coverageCount": "3/16", + "status": "low" + }, + { + "filePath": "src/app/herregistratie/ui/herregistratie.page.ts", + "type": "component", + "linktype": "component", + "name": "HerregistratiePage", + "coveragePercent": 33, + "coverageCount": "1/3", + "status": "medium" + }, + { + "filePath": "src/app/registratie/application/big-profile.store.ts", + "type": "injectable", + "linktype": "injectable", + "name": "BigProfileStore", + "coveragePercent": 30, + "coverageCount": "4/13", + "status": "medium" + }, + { + "filePath": "src/app/registratie/application/big-profile.store.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Err", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/registratie/domain/big-profile.ts", + "type": "interface", + "linktype": "interface", + "name": "BigProfile", + "coveragePercent": 33, + "coverageCount": "1/3", + "status": "medium" + }, + { + "filePath": "src/app/registratie/domain/person.ts", + "type": "interface", + "linktype": "interface", + "name": "Adres", "coveragePercent": 25, "coverageCount": "1/4", "status": "low" }, { - "filePath": "src/app/organisms/registration-summary/registration-summary.component.ts", - "type": "component", - "linktype": "component", - "name": "RegistrationSummaryComponent", + "filePath": "src/app/registratie/domain/person.ts", + "type": "interface", + "linktype": "interface", + "name": "Person", + "coveragePercent": 0, + "coverageCount": "0/4", + "status": "low" + }, + { + "filePath": "src/app/registratie/domain/registration.policy.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "herregistratieDeadline", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/registratie/domain/registration.policy.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "isHerregistratieEligible", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/registratie/domain/registration.policy.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "isStatusConsistent", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/registratie/domain/registration.policy.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "statusColor", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/registratie/domain/registration.policy.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "statusLabel", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/registratie/domain/registration.ts", + "type": "interface", + "linktype": "interface", + "name": "Aantekening", + "coveragePercent": 0, + "coverageCount": "0/4", + "status": "low" + }, + { + "filePath": "src/app/registratie/domain/registration.ts", + "type": "interface", + "linktype": "interface", + "name": "Registration", + "coveragePercent": 0, + "coverageCount": "0/7", + "status": "low" + }, + { + "filePath": "src/app/registratie/domain/registration.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "RegistrationStatus", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/registratie/domain/registration.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "StatusTag", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/registratie/domain/value-objects/big-nummer.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "parseBigNummer", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/registratie/domain/value-objects/big-nummer.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "BigNummer", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/registratie/domain/value-objects/postcode.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "parsePostcode", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/registratie/domain/value-objects/postcode.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Postcode", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/registratie/domain/value-objects/uren.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "parseUren", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/registratie/domain/value-objects/uren.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Uren", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/registratie/infrastructure/big-register.adapter.ts", + "type": "injectable", + "linktype": "injectable", + "name": "BigRegisterAdapter", + "coveragePercent": 33, + "coverageCount": "1/3", + "status": "medium" + }, + { + "filePath": "src/app/registratie/infrastructure/brp.adapter.ts", + "type": "injectable", + "linktype": "injectable", + "name": "BrpAdapter", "coveragePercent": 50, "coverageCount": "1/2", "status": "medium" }, { - "filePath": "src/app/organisms/registration-table/registration-table.component.ts", + "filePath": "src/app/registratie/ui/change-request-form/change-request-form.component.ts", + "type": "component", + "linktype": "component", + "name": "ChangeRequestFormComponent", + "coveragePercent": 12, + "coverageCount": "1/8", + "status": "low" + }, + { + "filePath": "src/app/registratie/ui/change-request-form/change-request-form.component.ts", + "type": "interface", + "linktype": "interface", + "name": "ChangeRequest", + "coveragePercent": 25, + "coverageCount": "1/4", + "status": "low" + }, + { + "filePath": "src/app/registratie/ui/dashboard.page.ts", + "type": "component", + "linktype": "component", + "name": "DashboardPage", + "coveragePercent": 0, + "coverageCount": "0/2", + "status": "low" + }, + { + "filePath": "src/app/registratie/ui/registration-detail.page.ts", + "type": "component", + "linktype": "component", + "name": "RegistrationDetailPage", + "coveragePercent": 0, + "coverageCount": "0/3", + "status": "low" + }, + { + "filePath": "src/app/registratie/ui/registration-summary/registration-summary.component.ts", + "type": "component", + "linktype": "component", + "name": "RegistrationSummaryComponent", + "coveragePercent": 25, + "coverageCount": "1/4", + "status": "low" + }, + { + "filePath": "src/app/registratie/ui/registration-table/registration-table.component.ts", "type": "component", "linktype": "component", "name": "RegistrationTableComponent", @@ -5010,80 +6993,186 @@ "status": "medium" }, { - "filePath": "src/app/organisms/site-footer/site-footer.component.ts", - "type": "component", - "linktype": "component", - "name": "SiteFooterComponent", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/organisms/site-header/site-header.component.ts", - "type": "component", - "linktype": "component", - "name": "SiteHeaderComponent", - "coveragePercent": 50, - "coverageCount": "1/2", - "status": "medium" - }, - { - "filePath": "src/app/pages/concepts/concepts.page.ts", - "type": "component", - "linktype": "component", - "name": "ConceptsPage", - "coveragePercent": 8, - "coverageCount": "1/12", - "status": "low" - }, - { - "filePath": "src/app/pages/concepts/concepts.page.ts", + "filePath": "src/app/shared/application/remote-data.ts", "type": "function", "linktype": "miscellaneous", "linksubtype": "function", - "name": "fakeResource", + "name": "andThen", "coveragePercent": 100, "coverageCount": "1/1", "status": "very-good" }, { - "filePath": "src/app/pages/dashboard/dashboard.page.ts", - "type": "component", - "linktype": "component", - "name": "DashboardPage", - "coveragePercent": 0, - "coverageCount": "0/6", - "status": "low" - }, - { - "filePath": "src/app/pages/herregistratie/herregistratie.page.ts", - "type": "component", - "linktype": "component", - "name": "HerregistratiePage", + "filePath": "src/app/shared/application/remote-data.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "foldRemote", "coveragePercent": 100, "coverageCount": "1/1", "status": "very-good" }, { - "filePath": "src/app/pages/login/login.page.ts", - "type": "component", - "linktype": "component", - "name": "LoginPage", + "filePath": "src/app/shared/application/remote-data.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "fromResource", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/application/remote-data.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "map", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/application/remote-data.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "map2", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/application/remote-data.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "map3", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/application/remote-data.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "RemoteData", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/application/store.ts", + "type": "interface", + "linktype": "interface", + "name": "Store", + "coveragePercent": 100, + "coverageCount": "3/3", + "status": "very-good" + }, + { + "filePath": "src/app/shared/application/store.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "createStore", "coveragePercent": 0, - "coverageCount": "0/3", + "coverageCount": "0/1", "status": "low" }, { - "filePath": "src/app/pages/registration-detail/registration-detail.page.ts", - "type": "component", - "linktype": "component", - "name": "RegistrationDetailPage", + "filePath": "src/app/shared/infrastructure/scenario.interceptor.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "scenarioInterceptor", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/infrastructure/scenario.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "currentScenario", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/infrastructure/scenario.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "VALID", "coveragePercent": 0, - "coverageCount": "0/5", + "coverageCount": "0/1", "status": "low" }, { - "filePath": "src/app/templates/page-shell/page-shell.component.ts", + "filePath": "src/app/shared/infrastructure/scenario.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Scenario", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/shared/kernel/fp.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "assertNever", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/kernel/fp.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "err", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/shared/kernel/fp.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "ok", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/shared/kernel/fp.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Brand", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/kernel/fp.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Result", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/layout/page-shell/page-shell.component.ts", "type": "component", "linktype": "component", "name": "PageShellComponent", @@ -5092,13 +7181,215 @@ "status": "low" }, { - "filePath": "src/app/templates/shell/shell.component.ts", + "filePath": "src/app/shared/layout/shell/shell.component.ts", "type": "component", "linktype": "component", "name": "ShellComponent", "coveragePercent": 100, "coverageCount": "1/1", "status": "very-good" + }, + { + "filePath": "src/app/shared/layout/site-footer/site-footer.component.ts", + "type": "component", + "linktype": "component", + "name": "SiteFooterComponent", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/layout/site-header/site-header.component.ts", + "type": "component", + "linktype": "component", + "name": "SiteHeaderComponent", + "coveragePercent": 50, + "coverageCount": "1/2", + "status": "medium" + }, + { + "filePath": "src/app/shared/ui/alert/alert.component.ts", + "type": "component", + "linktype": "component", + "name": "AlertComponent", + "coveragePercent": 50, + "coverageCount": "1/2", + "status": "medium" + }, + { + "filePath": "src/app/shared/ui/alert/alert.component.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "AlertType", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/async/async.component.ts", + "type": "component", + "linktype": "component", + "name": "AsyncComponent", + "coveragePercent": 8, + "coverageCount": "1/12", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/async/async.component.ts", + "type": "directive", + "linktype": "directive", + "name": "AsyncEmptyDirective", + "coveragePercent": 0, + "coverageCount": "0/3", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/async/async.component.ts", + "type": "directive", + "linktype": "directive", + "name": "AsyncErrorDirective", + "coveragePercent": 0, + "coverageCount": "0/3", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/async/async.component.ts", + "type": "directive", + "linktype": "directive", + "name": "AsyncLoadedDirective", + "coveragePercent": 0, + "coverageCount": "0/3", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/async/async.component.ts", + "type": "directive", + "linktype": "directive", + "name": "AsyncLoadingDirective", + "coveragePercent": 0, + "coverageCount": "0/3", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/async/async.component.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "ASYNC", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/shared/ui/button/button.component.ts", + "type": "component", + "linktype": "component", + "name": "ButtonComponent", + "coveragePercent": 25, + "coverageCount": "1/4", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/button/button.component.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Variant", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/data-row/data-row.component.ts", + "type": "component", + "linktype": "component", + "name": "DataRowComponent", + "coveragePercent": 33, + "coverageCount": "1/3", + "status": "medium" + }, + { + "filePath": "src/app/shared/ui/form-field/form-field.component.ts", + "type": "component", + "linktype": "component", + "name": "FormFieldComponent", + "coveragePercent": 20, + "coverageCount": "1/5", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/heading/heading.component.ts", + "type": "component", + "linktype": "component", + "name": "HeadingComponent", + "coveragePercent": 50, + "coverageCount": "1/2", + "status": "medium" + }, + { + "filePath": "src/app/shared/ui/link/link.component.ts", + "type": "component", + "linktype": "component", + "name": "LinkComponent", + "coveragePercent": 50, + "coverageCount": "1/2", + "status": "medium" + }, + { + "filePath": "src/app/shared/ui/skeleton/skeleton.component.ts", + "type": "component", + "linktype": "component", + "name": "SkeletonComponent", + "coveragePercent": 10, + "coverageCount": "1/10", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/spinner/spinner.component.ts", + "type": "component", + "linktype": "component", + "name": "SpinnerComponent", + "coveragePercent": 16, + "coverageCount": "1/6", + "status": "low" + }, + { + "filePath": "src/app/shared/ui/status-badge/status-badge.component.ts", + "type": "component", + "linktype": "component", + "name": "StatusBadgeComponent", + "coveragePercent": 33, + "coverageCount": "1/3", + "status": "medium" + }, + { + "filePath": "src/app/shared/ui/text-input/text-input.component.ts", + "type": "component", + "linktype": "component", + "name": "TextInputComponent", + "coveragePercent": 7, + "coverageCount": "1/14", + "status": "low" + }, + { + "filePath": "src/app/showcase/concepts.page.ts", + "type": "component", + "linktype": "component", + "name": "ConceptsPage", + "coveragePercent": 8, + "coverageCount": "1/12", + "status": "low" + }, + { + "filePath": "src/app/showcase/concepts.page.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "fakeResource", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" } ] }