feat/mvp-frontend #7

Merged
eliott merged 32 commits from feat/mvp-frontend into develop 2026-05-11 22:22:57 +02:00
Owner
No description provided.
Add a Zod .refine() guard to the mediaUrl field in resourceFormSchema so that
javascript:, data:, vbscript:, and other non-http(s) schemes are rejected at
form-submission time with a clear French error message.

Add a defence-in-depth ALLOWED_PROTOCOLS check in classify() (ResourceMedia.tsx)
so that URLs already stored in the database with a forbidden scheme are silently
dropped (component returns null) rather than rendered as a clickable <a href>.
new URL() inside the refine cannot throw because z.string().url() runs first
and rejects malformed URLs. The catch branch was dead code that suggested a
failure path that does not exist.
- forwardAuthCookies: force secure:true in production regardless of what
  the upstream Set-Cookie header advertised; preserve upstream value in dev
  so local HTTP flows still work.
- clearAuthCookies: align secure flag with production environment so the
  browser honours the clearance when the live cookie was set with Secure.
- jwt.ts / session.ts: add load-bearing comments explaining that the JWT
  payload is base64-decoded only (no signature verification), that the
  .NET API is the real authorization gate, and that hasPermission() /
  permission checks are cosmetic UI gating — every Server Action must
  independently re-check authz before forwarding to the API.
Aligns forwardAuthCookies with clearAuthCookies on the production check and
collapses the ternary into a single OR expression. Same semantics: Secure in
prod regardless, plus honour upstream Secure attribute in dev.
Add existingDocumentId to resourceFormSchema as an optional union of
empty string or a permissive GUID regex, matching the same pattern used
by guidSchema. Malformed values are now rejected with a field error
("Identifiant de document invalide.") before the payload reaches the API.
resolveDocumentId previously re-read existingDocumentId straight from
FormData after schema validation, sidestepping the GUID guard. Pass the
validated parsed.data.existingDocumentId through so the value that reaches
the API is always the one Zod approved.
Every apiFetch call forwards user cookies, so responses must not be
cached across users. The cache: "no-store" default already prevented
next: { tags, revalidate } from having any effect, and updateTag(...)
calls had nothing to invalidate. Remove all dead next:/updateTag
plumbing and add a comment to apiFetch documenting the no-store intent.
Add router.refresh() after each successful mutation in TaxonomyManager,
AdminUserRow, PendingResourceRow, and MyResourceRow so parent Server
Components re-fetch and the page reflects the new state without a manual
reload.
The status tag and the visibility of the Soumettre button both depend on
resource.awaitingValidation, which only flips on the server. Without a
refresh, a successful submit leaves the card stale until the user reloads.
Same staleness pattern as the four admin/my-resources rows: the comment list
above CommentForm and the bucket pages around UserActionResourceCard would
otherwise show the pre-mutation snapshot until the user manually reloaded.
The GET /resources API endpoint only accepts PaginationParams (Page/ItemsCount/GetAll)
and provides no server-side filtering, search, or sorting. Replace the unbounded
getAll:true fetch with a MAX_CATALOG_ITEMS=500 cap and add a TODO documenting
the API gap so the fix is visible when server-side filtering is eventually added.
- Remove dead /api/users/logout route handler and its SKIP_REFRESH_PREFIXES entry
- Tighten middleware prefix check to match exact path or prefix+"/" boundary
- Hoist API_BASE_URL into lib/api/env.ts; update four call sites (next.config, middleware, lib/api/client, lib/auth/api)
- Remove redundant `dynamic = "force-dynamic"` from reset-password and verify-email pages (apiFetch is no-store)
- Inline trivial gateTaxonomyMutation helper in lib/admin/actions.ts
- Factor duplicated base64url decode into lib/auth/jwt-edge.ts (atob-based, edge-safe); import from middleware and jwt.ts
The new boundary check (prefix + '/') turned '/api/password/' into the
unmatchable '/api/password//'. Drop the trailing slash so the existing logic
covers /api/password/reset and similar correctly.
Fetch per-resource user state server-side in the detail page (3 parallel
calls to mine/favorites, mine/sidelined, mine/completed with getAll=true)
and pass the flags to ResourceActionsBar as props. The bar now shows
"Retirer des favoris / de côté / non terminée" with primary styling when
active, calls the un-actions on click, and uses optimistic local state
with router.refresh() for post-action sync.
Three small followups to the toggle change:
- disable all three buttons while any action is pending (was: only active one)
- sync local optimistic state to props via effect, so router.refresh() landings
  reflect server truth without diverging
- annotate findResourceUserState with a TODO pointing to the API-side fix
set-state-in-effect lint rule rejects the prop->state sync I added.
useOptimistic is the React 19 native primitive for this exact case:
optimistic flip during the transition, automatic resync from the
authoritative prop when the transition ends and the parent RSC refreshes.
Both lockfiles were committed but the Dockerfile uses npm ci. Deleting the
pnpm lockfile eliminates drift risk.
- CommentItem.onDelete now calls router.refresh() so the comment thread
  reflects the deletion immediately (was relying on the now-removed
  updateTag in deleteCommentAction).
- ResourceActionsBar restores severity:'info' for the 'Action déjà
  effectuée.' 409 case instead of treating it as an error.
- .env.example documents the production-HTTPS requirement introduced
  when Secure was hardcoded for NODE_ENV=production.
Performance:
- Cache getSession() with React cache() so layout/header/page/action
  callers share one JWT decode per request
- Forward only access_token + refresh_token to the API instead of the
  full cookie jar; share buildAuthCookieHeader between apiFetch and
  apiFetchRaw
- Parallelise independent fetches in /ressources/[id] and the resource
  edit page
- Tag-cache taxonomy queries with updateTag("taxonomy") in admin
  mutations
- Lower MAX_CATALOG_ITEMS from 500 to 120 and add a truncation banner
- Tighten middleware matcher to skip static images and /api/* proxy

Bug fixes:
- AdminUserRow: stop mirroring userRoleIds into useState (which froze
  the display after router.refresh) — use a draft pattern instead
- Use /connexion?changed=email|password after email/password updates so
  the success copy reflects what actually changed (was reusing the
  registration banner)

Deduplication:
- passwordSchema + PASSWORD_RULES_HINT shared between auth and account
- One labeledTaxonomyDtoSchema factory for category, resource type, and
  relation type DTOs
- Move ACCEPTED_DOCUMENT_TYPES to a server-neutral constants module so
  forms can reuse it
- Centralise date formatters in lib/ui/formatters.ts (6 call sites)
- Collapse 6 resource bucket actions into setResourceBucketAction(id,
  bucket, on)
- requirePermission(mask) helper replaces 8 hand-rolled UI authz checks
  in admin actions
- useFlash hook with cleanup-on-unmount replaces 3 ad-hoc setTimeout
  toasters
- buildQuery URL helper replaces duplicated URLSearchParams logic in
  Pagination and CatalogToolbar
- applySort -> comparator lookup; verify-email cascade -> view table

Cleanup:
- Delete dead code: CategoryFilter, updateCommentAction, throwIfError,
  PermissionMask type
- Strip narrative comments that explained WHAT instead of WHY
Drops the @tiptap/extension-image dependency, the image toolbar button
and file upload handler in RichTextEditor, the uploadInlineImageAction
server action and its shared inline-image constants, and the <img> tag
allow-list in sanitize-html. The Document attachment field on the
resource form is unaffected — only inline images embedded inside rich
text are gone.

Run `npm uninstall @tiptap/extension-image` to sync package-lock.json.
eliott merged commit 7c87ede1e5 into develop 2026-05-11 22:22:57 +02:00
eliott deleted branch feat/mvp-frontend 2026-05-11 22:22:57 +02:00
Commenting is not possible because the repository is archived.
No reviewers
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
cda-solutions/resources-relationnelles-website!7
No description provided.