Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
958ab14
feat(news): staff can put a news item on one portal's public website,…
rubenvdlinde Oct 5, 2026
423627c
feat(site): task tiles, public news, a dated list and the sign-in car…
rubenvdlinde Oct 5, 2026
808456a
fix(news): phpcs and phpmd on the public news reader and the staff ne…
rubenvdlinde Oct 5, 2026
735f62a
fix(site): the news widgets' mounted hooks name their requirement
rubenvdlinde Oct 5, 2026
89f6c57
wip(site-chrome): portal schema and content API serve the header sear…
rubenvdlinde Oct 5, 2026
e2eec02
chore: untrack two local dependency links committed by mistake
rubenvdlinde Oct 5, 2026
91198b8
fix(site): the sign-in card's button keeps its own colours whatever t…
rubenvdlinde Oct 5, 2026
6135a95
fix(site): the inverse sign-in card's button is a plain button, so no…
rubenvdlinde Oct 5, 2026
7e33da5
wip(site-chrome): designed header (search, account button, person chi…
rubenvdlinde Oct 5, 2026
5ab4b20
Merge development into feat/site-school-blocks-home
rubenvdlinde Oct 5, 2026
7e89d90
wip(site-chrome): node tests for the header tools, footer, sign-in ca…
rubenvdlinde Oct 5, 2026
71da6f8
wip(site-chrome): one signed-out block, the person helper in the lazy…
rubenvdlinde Oct 5, 2026
3a42b1c
wip(site-chrome): designed footer columns, fonts, openspec change
rubenvdlinde Oct 5, 2026
a80f8e6
fix(site-chrome): no inline ifs in the shell projection
rubenvdlinde Oct 5, 2026
727b7c9
feat(site-chrome): a declared header search shows without enabled, wi…
rubenvdlinde Oct 5, 2026
663dfcf
fix(site-chrome): the brand column keeps the design's spacing
rubenvdlinde Oct 5, 2026
cce40cc
test(site-chrome): import order
rubenvdlinde Oct 5, 2026
e4c154a
chore: merge feat/site-chrome-follows-the-design, the header, footer …
rubenvdlinde Oct 5, 2026
6450631
refactor(site-chrome): the sign-in text in its own class, logo varian…
rubenvdlinde Oct 5, 2026
52b99cf
chore: merge feat/site-school-blocks-home, the home page blocks this …
rubenvdlinde Oct 5, 2026
1ce1f1f
fix(site-chrome): on a phone the motif runs under the masthead while …
rubenvdlinde Oct 5, 2026
f35170e
fix(site-chrome): controller test knows the logo variants; docblock l…
rubenvdlinde Oct 5, 2026
8f832d8
fix(site-chrome): the sign-in page fits a phone
rubenvdlinde Oct 5, 2026
524b939
fix(site-chrome): the breadcrumb starts under the motif
rubenvdlinde Oct 5, 2026
2e738e2
wip(example-site): the Zuiddrecht declaration, the installer with its…
rubenvdlinde Oct 5, 2026
009d417
wip(example-site): unit tests for the installer, store and commands; …
rubenvdlinde Oct 5, 2026
334c848
wip(example-site): the current menu item may sit in the line; the dec…
rubenvdlinde Oct 5, 2026
f7cafde
docs(site-chrome): live check done
rubenvdlinde Oct 5, 2026
62c0c75
wip(example-site): the hero heading shows; the site names a set's gre…
rubenvdlinde Oct 5, 2026
2ec6603
chore: merge development (home blocks #1195 and Mijn displays #1198 l…
rubenvdlinde Oct 5, 2026
c3faf49
chore: merge feat/site-chrome-follows-the-design at f7cafde6 (#1202);…
rubenvdlinde Oct 5, 2026
61ef996
wip(example-site): the designed site reads in the set's family, table…
rubenvdlinde Oct 5, 2026
6f29640
style(example-site): prettier
rubenvdlinde Oct 5, 2026
e0562fe
refactor(example-site): the installer, the remover, the record, the r…
rubenvdlinde Oct 5, 2026
c449eae
feat(site): Mijn Zuiddrecht: the sign-in cards of the design, and the…
rubenvdlinde Oct 5, 2026
73207e9
chore: merge development (#1200, #1205)
rubenvdlinde Oct 5, 2026
e45b18a
Merge branch 'feat/zuiddrecht-demo-site' into feat/zuiddrecht-mijn
rubenvdlinde Oct 5, 2026
defafc7
chore: merge development (#1202 and #1203 landed)
rubenvdlinde Oct 5, 2026
95c7736
Merge branch 'feat/zuiddrecht-demo-site' into feat/zuiddrecht-mijn
rubenvdlinde Oct 5, 2026
b734d90
chore: merge development (#1206 landed as a squash)
rubenvdlinde Oct 5, 2026
168033e
fix(site): a breadcrumb keeps its own room under the menu line; docs:…
rubenvdlinde Oct 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions css/site-theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,24 @@
padding-block-start: var(--cn-brand-stripe-height, 0);
}

/*
* A trail keeps the room the breadcrumb bar had of its own, under the motif
* (example-site-zuiddrecht). The motif's height alone put the trail against
* the line: seen live on the Zuiddrecht waste page and sign-in page. A page
* without a trail, such as the home page, keeps the rule above.
*/
.pq-site
.pq-site__header
.ac-header__navigation-breadcrumb:not(:has(.container:empty)) {
padding-block-start: calc(
var(--cn-brand-stripe-height, 0px) +
var(
--utrecht-breadcrumb-nav-padding-block-start,
var(--tilburg-space-block-mouse, 0px)
)
);
}

.pq-site .ac-footer .pq-footer__band--content::before {
--pq-stripe-first: var(--cn-brand-stripe-ratio-1, 1);
--pq-stripe-second: var(--cn-brand-stripe-ratio-2, 1);
Expand Down
7 changes: 6 additions & 1 deletion docs/Installation/example-site-zuiddrecht.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Zuiddrecht is a municipality that does not exist. Its website shows what a porta

- Portaliq and OpenRegister, both enabled.
- The theme app (thematiq) with the `zuiddrecht` set. Without it the site installs and shows without its house style. The command tells you when the set is missing.
- From a source checkout only: the built site bundle. `npm run dev` builds the admin screens and nothing else. The public site needs `npx webpack --config webpack.site.js` (and `webpack.traffic.js` for traffic measurement), or `npm run build` for everything. Without it the site is a blank page with one 404 on `portaliq-site.js`. A release from the app store has the bundle.

## Install

Expand Down Expand Up @@ -51,14 +52,18 @@ The site answers at `/index.php/apps/portaliq/site?portal=zuiddrecht`. To serve

## Signing in

The header shows the button "Mijn Zuiddrecht". The portal names DigiD and eHerkenning as its ways in, as the design does. They work once your instance has a sign-in broker for them: see [Signing in through integriq](../operations/signing-in-through-integriq.md). On a test instance you can switch on the test sign-in instead:
The header shows the button "Mijn Zuiddrecht". It opens the sign-in page with two cards: "Als inwoner" with DigiD and "Namens een bedrijf" with eHerkenning. They work once your instance has a sign-in broker for them: see [Signing in through integriq](../operations/signing-in-through-integriq.md). On a test instance you can switch on the test sign-in instead:

```bash
occ config:app:set portaliq dev_login_enabled --value=yes
```

Never switch that on for a site real residents use.

After signing in, a resident sees what the installed apps offer. With dossiq that is the overview, "Mijn zaken" and the page of one case with its steps, details and documents. The site calls this area "Mijn Zuiddrecht" everywhere, after the name on the button.

Did you install the site before the sign-in cards were added? The install never changes a portal that exists. Remove the site and install it again.

## What is and is not in the site

- The header, footer, home page and the page "Afval scheiden en ophalen" follow the Zuiddrecht design. The other pages are short, so that no link is dead.
Expand Down
21 changes: 20 additions & 1 deletion lib/Settings/sites/zuiddrecht.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,26 @@
"digid",
"eherkenning"
],
"minTrust": "low"
"minTrust": "low",
"modeLabels": {
"digid": {
"title": "Als inwoner",
"text": "Voor uw eigen zaken, berichten en dossiers.",
"button": "Inloggen met DigiD"
},
"eherkenning": {
"title": "Namens een bedrijf",
"text": "Voor vergunningen, meldingen en zaken van uw organisatie.",
"button": "Inloggen met eHerkenning"
}
},
"signInPage": {
"title": "Inloggen op Mijn Zuiddrecht",
"intro": "Kies hoe u inlogt. Als inwoner gebruikt u DigiD. Namens een bedrijf gebruikt u eHerkenning.",
"notice": {
"text": "Geen DigiD? Bel [telefoonnummer]. Wij helpen u op een andere manier."
}
}
},
"headerSearch": {
"label": "Zoeken op de website",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,28 @@ addresses MUST pass unchanged; anything else MUST NOT become a link.
- GIVEN the site at `/apps/portaliq/site?portal=zuiddrecht`
- WHEN a link list holds `/afval`
- THEN the link's address keeps `portal=zuiddrecht` and names the route `/afval`

### Requirement: The own area must carry the name the portal gives it

A portal that sets `accountLabel` MUST show that name wherever the site names the resident's own
area: the breadcrumb, the resident menu, the link in the header and the tab title. A portal
without one MUST keep "Mijn omgeving".

#### Scenario: Mijn Zuiddrecht
@e2e exclude Checked in node: tests/example-site.spec.mjs; rendered signed out in the lane's preview
- GIVEN a portal with `accountLabel` "Mijn Zuiddrecht"
- WHEN a visitor opens `/mijn`
- THEN the breadcrumb reads "Home", "Mijn Zuiddrecht"

### Requirement: The Zuiddrecht site must offer its two ways in as the design shows

The Zuiddrecht declaration MUST hold a sign-in card for each way in it names besides `public`:
DigiD for a resident, eHerkenning for a company, each with the title, text and button of the
Inloggen board, and the page's heading, introduction and notice.

#### Scenario: The sign-in page
@e2e exclude Checked in node: tests/example-site.spec.mjs; rendered in the lane's preview through the real shell projection
- GIVEN the installed site
- WHEN a visitor opens `/mijn` signed out
- THEN the page reads "Inloggen op Mijn Zuiddrecht" with the cards "Als inwoner" and "Namens een bedrijf"

3 changes: 2 additions & 1 deletion openspec/changes/example-site-zuiddrecht/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
- [x] 5. `nlLinkList` and `nlButtonLink` open pages of the site wherever it is served.
- [x] 6. Tests: `tests/example-site.spec.mjs` in `check:specs`, PHPUnit for the catalogue, installer and commands.
- [x] 7. Documentation: `docs/Installation/example-site-zuiddrecht.md`.
- [ ] 8. Live check on a fresh instance (coordinator).
- [x] 8. The sign-in cards of the Inloggen board in the declaration; the own area under the portal's name.
- [ ] 9. Live check on a fresh instance (coordinator).
9 changes: 8 additions & 1 deletion src/site/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ import {
ownAreaLink as ownAreaLinkFor,
residentMenuGroups,
showsResidentMenu,
withAreaName,
} from './lib/residentMenu.js'
import { isSharedDossierRoute, sharedDossierToken } from './lib/sharedDossier.js'
import {
Expand Down Expand Up @@ -1094,13 +1095,19 @@ export default {
/**
* The site translator: English source strings, Dutch and English
* bundles shared with `/portal` (src/shared/i18n).
* The resident's own area reads under the name the portal gives its
* account button, when it gives one.
*
* @return {(key: string, vars?: object) => string} The translator.
*
* @spec openspec/changes/portal-shared-runtime/specs/portal-shared-runtime/spec.md#requirement-the-portal-must-boot-the-shared-runtime-and-ship-no-react
* @spec openspec/changes/example-site-zuiddrecht/specs/example-site/spec.md#requirement-the-own-area-must-carry-the-name-the-portal-gives-it
*/
t() {
return createTranslator(this.locale)
return withAreaName(
createTranslator(this.locale),
this.site?.accountLabel,
)
},

/**
Expand Down
21 changes: 21 additions & 0 deletions src/site/lib/residentMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@ export function showsResidentMenu(session, route, nav) {
)
}

/**
* A translator that calls the resident's own area what the portal calls it.
*
* A portal that names its account button ("Mijn Zuiddrecht") means that name
* for the area behind it too: the breadcrumb, the menu and the link in the
* header would otherwise read "Mijn omgeving" under a button that says
* something else. Without a name the translator is handed back as it is.
*
* @param {(key: string, vars?: object) => string} t The translator.
* @param {string} name The portal's `accountLabel`, possibly empty.
* @return {(key: string, vars?: object) => string} The translator to use.
* @spec openspec/changes/example-site-zuiddrecht/specs/example-site/spec.md#requirement-the-own-area-must-carry-the-name-the-portal-gives-it
*/
export function withAreaName(t, name) {
const own = String(name ?? '').trim()
if (own === '') {
return t
}
return (key, vars) => (key === 'My area' ? own : t(key, vars))
}

/**
* The top right link to the resident's own area, or null when signed out.
*
Expand Down
76 changes: 76 additions & 0 deletions tests/example-site.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -610,3 +610,79 @@ test('the current menu item may sit in the line under the menu', () => {
/#[0-9a-f]{3,8}\b|rgb\(/i,
)
})

test('the Zuiddrecht site offers its two ways in as the Inloggen board shows', () => {
const site = sites.find((entry) => entry.site.id === 'zuiddrecht').site
const auth = site.portal.authentication
// A card for every way in the portal names, and for no other.
assert.deepEqual(
Object.keys(auth.modeLabels).sort(),
auth.modes.filter((mode) => mode !== 'public').sort(),
)
assert.deepEqual(auth.modeLabels.digid, {
title: 'Als inwoner',
text: 'Voor uw eigen zaken, berichten en dossiers.',
button: 'Inloggen met DigiD',
})
assert.equal(auth.modeLabels.eherkenning.title, 'Namens een bedrijf')
assert.equal(auth.modeLabels.eherkenning.button, 'Inloggen met eHerkenning')
assert.equal(auth.signInPage.title, 'Inloggen op Mijn Zuiddrecht')
assert.match(auth.signInPage.intro, /^Kies hoe u inlogt\./)
assert.match(auth.signInPage.notice.text, /^Geen DigiD\?/)
// The shell serves a card only with a text; each has its three.
for (const card of Object.values(auth.modeLabels)) {
assert.ok(card.title && card.text && card.button)
}
})

test('the own area carries the name the portal gives it', async () => {
const { ownAreaLink, withAreaName } = await import(
new URL('src/site/lib/residentMenu.js', root)
)
const { accountCrumbs } = await import(
new URL('src/site/lib/accountArea.js', root)
)
const nl = (key) => ({ 'My area': 'Mijn omgeving', Home: 'Home' })[key] ?? key
const named = withAreaName(nl, ' Mijn Zuiddrecht ')

assert.equal(named('My area'), 'Mijn Zuiddrecht')
assert.equal(named('Home'), 'Home', "every other string is the translator's")
assert.deepEqual(
accountCrumbs(null, named, (route) => route).map((crumb) => crumb.label),
['Home', 'Mijn Zuiddrecht'],
)
assert.equal(
ownAreaLink({ subjectRef: 'x' }, named, (route) => route).label,
'Mijn Zuiddrecht',
)

// No name: the translator itself, so nothing changes for other portals.
assert.equal(withAreaName(nl, ''), nl)
assert.equal(withAreaName(nl, undefined), nl)
assert.equal(withAreaName(nl, ' ')('My area'), 'Mijn omgeving')

// The site's one translator is the named one.
assert.match(
read('src/site/App.vue'),
/withAreaName\(\s*createTranslator\(this\.locale\),\s*this\.site\?\.accountLabel,?\s*\)/,
)
})

test('a trail keeps its own room under the menu line', () => {
const css = read('css/site-theme.css')
const start = css.indexOf('A trail keeps the room the breadcrumb bar had')
assert.ok(start > 0)
const rule = css.slice(start, css.indexOf('}', css.indexOf('{', start)) + 1)
// Only a bar that holds a trail: the home page keeps the motif's height.
assert.match(
rule,
/\.ac-header__navigation-breadcrumb:not\(:has\(\.container:empty\)\) \{/,
)
// The motif's height plus the bar's own padding, each with a length to fall back on.
assert.match(
rule,
/padding-block-start: calc\(\s*var\(--cn-brand-stripe-height, 0px\) \+\s*var\(\s*--utrecht-breadcrumb-nav-padding-block-start,\s*var\(--tilburg-space-block-mouse, 0px\)\s*\)\s*\)/,
)
// After the rule it adds to, so it wins at the same weight for a bar with a trail.
assert.ok(start > css.indexOf('the breadcrumb starts under it. */'))
})
Loading