Skip to content

ETQ super-admin, je peux publier un bandeau d'en-tête sur le site depuis le manager#13237

Merged
colinux merged 4 commits into
demarche-numerique:mainfrom
thibpoullain:feature/banner-system
Jun 15, 2026
Merged

ETQ super-admin, je peux publier un bandeau d'en-tête sur le site depuis le manager#13237
colinux merged 4 commits into
demarche-numerique:mainfrom
thibpoullain:feature/banner-system

Conversation

@thibpoullain

Copy link
Copy Markdown

Summary

Replaces the three BANNER_MESSAGE / ADMINISTRATION_BANNER_MESSAGE / USAGER_BANNER_MESSAGE env-variable banners with a database-backed system editable at /manager/banners by super admins.

Five banner targets, each rendered via DSFR NoticeComponent with a custom .banner-notice class (pastel orange via DSFR --background-contrast-warning / --text-default-warning variables):

Target Visible on
global every page
instructeurs_only pages whose controller exposes nav_bar_profile == :instructeur
usagers_only every other navigation context (anon, user, administrateur, expert, gestionnaire)
login_page /users/sign_in + /users/sign_up (rendered inside the fieldset, below the subtitle)
manager_login /super_admins/sign_in (rendered inside the form container, below Espace Manager)

Banners are published by filling content and unpublished by clearing it. Redis-cached per target with 1-minute TTL and after_commit invalidation. Sanitization restricted to [a strong em b i br u abbr sub sup] inline whitelist (verified against javascript:, onerror, onclick, <script>, <iframe>, <style> injection vectors).

Admin UI /manager/banners rebuilt with a self-contained bn-* design (516 lines SCSS): card grid, Heroicons SVG via ManagerBannerIconsHelper, status badges Active/Inactive, modern typography, gray background extending through main-content padding, guide encart documenting allowed HTML tags with a full test message.

Commits

  • feat(banners): add database-backed banners with admin UI (model, controller, view, helper, 3 migrations, 2 SCSS files, 3 doc files, layout integrations on _pre_maintenance, super_admins/sessions/new, users/sessions/new, users/registrations/new, plus NoticeComponent extension with extra_class_names).
  • fix(banners): use nav_bar_profile and banner-notice class in pre_maintenance — uses navigation context (nav_bar_profile) instead of role check (instructeur_signed_in?) so a user with multiple roles sees usagers_only when browsing user/admin context (not just instructeurs_only), and applies the banner-notice styling class. Both fixes were lost when the partial was recreated in ERB during the cross-repo port (the upstream chore: port layout to herb commit had migrated it from HAML to ERB after the BDD-backed banners commit).

Test plan

Runtime-validated locally (puma + browser MCP + psql) — 9 pages × 5 banner targets = 45 visibility checks, all PASS.

  • Cold state: all 5 banners empty → no notice on any page

  • Admin UI /manager/banners: cards rendered with Heroicons + badges, guide encart with allowed tags + full example, form submit posts PATCH, flash notice 'Bannière mise à jour', badge toggles ACTIVE/INACTIVE, stats counter N/5 actives accurate

  • Activation via admin form → banner visible on front (orange pastel .banner-notice class)

  • Deactivation by emptying textarea → banner disappears on front

  • Targeting matrix runtime (all 45 combinations verified via browser MCP):

    Page nav_profile Expected & observed
    / anon/auth :guest G + U
    /users/sign_in :guest G + U + L (L inside fieldset)
    /users/sign_up :guest G + U + L (L inside fieldset)
    /super_admins/sign_in :superadmin G + U + M (M inside container)
    /admin/procedures :guest G + U
    /procedures :instructeur G + I
    /dossiers :guest G + U
    /faq :guest G + U
  • Sanitization XSS: 6 attack vectors (javascript:href, onerror, onclick, <script>, <iframe>, <style>) all neutralized; 10 whitelist tags all preserved with href https and title attrs intact

  • Cache after_commit: update!Rails.cache.delete(\"banner/{target}\") (validated via rails runner 4 scenarios T1→T4 on MemoryStore)

  • Edge cases: empty string, NULL, whitespace-only content all treated as inactive (Rails String#present? blank-aware); 5000 chars rendered without truncation

  • Route surface restricted to GET /manager/banners + PATCH /manager/banners/:id (no :show, :create, :destroy, :new)

  • Authorization: unauth GET → 302 sign_in; unauth PATCH → 422 CSRF; PATCH from logged super_admin → success

  • Logs: no errors, no NoMethodError, no StatementInvalid during full test pass

Notes

  • Migration timestamps 20260602100001..03 are after the latest upstream migration 20260521105001, will run on db:migrate.
  • Banner.cached_for(target) rescues ActiveRecord::StatementInvalid so the partial does not break during the window between code deploy and migration completion.
  • NoticeComponent extended with optional extra_class_names: parameter merged via class_names into the existing options helper; backward-compatible with all existing call sites (state, data_attributes).

🤖 Generated with Claude Code

@colinux

colinux commented Jun 4, 2026

Copy link
Copy Markdown
Member

[ j'ai commencé à reprendre la PR pour signer les commits et rajouter 2-3 petits trucs ]

@colinux
colinux force-pushed the feature/banner-system branch from 30be4d9 to 0e7b282 Compare June 8, 2026 12:47
@colinux

colinux commented Jun 8, 2026

Copy link
Copy Markdown
Member

Donc j'ai amendé la PR avec ces modifications :

  • conversion en .erb de la nouvelle vue .haml (on est en déphasage d'haml)
  • cache redis minimal sur les banners plutôt que hit X fois la base pour trouver une banner à afficher
  • j'ai retiré le joli style tailwind du manager car l'archi actuelle fait que tout le css sous stylesheets/ est sur toutes les pages du site. (un jour on cleanera ça). Si jamais pour ton instance tu as besoin de garder la personnalisation il "suffirait" d'écraser le template et réinjecter le css.
  • confirmation OTP à l'enregistrement d'une bannière : on commence à déployer ça dans le manager pour quelques actions "sensibles"
  • nettoyage de la doc

@colinux colinux changed the title feat(banners): admin-editable database-backed banners (5 targets) ETQ super-admin, je peux publier un bandeau d'en-tête sur le site depuis le manager Jun 8, 2026
@colinux
colinux enabled auto-merge June 9, 2026 09:37
@colinux
colinux force-pushed the feature/banner-system branch from 8ed25ad to 9beac6e Compare June 10, 2026 09:03
ThibautPoullain and others added 4 commits June 15, 2026 14:46
Replace BANNER_MESSAGE, ADMINISTRATION_BANNER_MESSAGE, and
USAGER_BANNER_MESSAGE environment variables with database-backed
banners editable at /manager/banners by super admins.

Five banner types: global, instructeurs-only, usagers-only,
login page (/users/sign_in + /users/sign_up), and manager login
(/super_admins/sign_in). Each uses DSFR NoticeComponent with a
custom .banner-notice CSS class (pastel orange via DSFR vars).

Banners are published by filling content and unpublished by clearing
it. Redis-cached per target with 1-minute TTL and after_commit
invalidation. Sanitization restricted to safe inline tags.

Admin /manager/banners rebuilt with self-contained design
(bn-* classes in manager_banners.scss), card grid, Heroicons SVG
via ManagerBannerIconsHelper, status badges. Cards have green
border when active, red when inactive.

NoticeComponent extended with extra_class_names, coexisting with
the existing data_attributes parameter.

Ported cross-repo from demat-social@2.5.93 (commits 2e17d846b8,
34f88f5001, 906582d58e, e35069fb97, 845bd9a53e), squashed.
CLAUDE.md hunk dropped (file not present in this repo).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tenance

Restore the two fixes from the polish commit (845bd9a53e) that were lost
when this partial was recreated in ERB during the cross-repo port (the
upstream chore: port layout to herb commit had migrated it from HAML to
ERB after the BDD-backed banners commit).

- Use nav_bar_profile (context) instead of instructeur_signed_in? (role)
  so that a user with both an instructeur record and a user/admin record
  sees usagers_only when browsing in usager/admin context, not just
  instructeurs_only.

- Pass extra_class_names: 'banner-notice' so the custom orange pastel
  styling (banner_notice.scss) is actually applied. Without it, public
  pages render banners in DSFR blue (fr-notice--info) instead of the
  warning-variable orange intended.

Runtime-validated on /, /users/sign_in, /admin/procedures (admin nav),
/procedures (instructeur nav), /dossiers (user nav) with the same
multi-role user.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The manager banners page shipped a dedicated 516-line stylesheet
(manager_banners.scss). Because app/assets/stylesheets is bundled through
`require_tree`, that file ended up in application.css — the bundle loaded
on every public page — so all front-end visitors downloaded ~27 KB of
manager-only styling they never use.

Rebuild the page with Administrate's existing classes (main-content,
field-unit, flash-*, button) so no extra stylesheet is needed. Remove the
custom manager_banners.scss, its import, the icons helper and
Banner::TARGET_ICONS.
Gate Manager::BannersController#update behind RequiresFreshSuperAdminOtp,
like the dossier transfer and user email overwrite actions: the super
admin must enter a fresh TOTP code to change a banner.

Each inline banner form renders Manager::OtpAttemptInputComponent; the
component now accepts an `id` so the five forms don't share a DOM id
(the submitted param stays `otp_attempt`).
@colinux
colinux force-pushed the feature/banner-system branch from 9beac6e to 99000c0 Compare June 15, 2026 12:46
@colinux
colinux added this pull request to the merge queue Jun 15, 2026
Merged via the queue into demarche-numerique:main with commit ca67337 Jun 15, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants