Aller au contenu

Intégrer au site

Construire le formulaire et le suivi d'un rapport dans le design du site, avec les fonctions serveur, les appels du navigateur et les composants du module Rétroaction.

Les pages /za/feedback du module prennent le cadre neutre commun aux modules. Un site qui veut son formulaire dans son propre design, avec son en-tête, son pied de page et ses composants, construit ses pages lui-même. Le module lui donne les données et les actions, le site garde la main sur l'apparence.

La feuille de route, ses fiches et son vote sont ceux du module Projets. Pour les construire dans le design du site, voir Intégrer Projets au site.

1. Choisir l'adresse

FRAZA_MODULE_FEEDBACK_PUBLIC_PATH=/feuille-de-route

Les liens que le module écrit suivent cette adresse : le lien de suivi d'un rapport, dans le formulaire et dans les courriels. Le site doit donc servir deux pages :

PageAdresseLit
Le formulaire/feuille-de-route/nouveaule composant ReportWizard, ou fetchReportForm et sendReport
Le suivi d'un rapport/feuille-de-route/suivi/<jeton>getFollowedReport

La même adresse peut servir à la feuille de route du module Projets. Aucun projet de la rétroaction ne prend le slug nouveau ni suivi, et Projets ne les donne ni à une fiche ni à un projet.

Dès que la variable est réglée, les pages /za/feedback ne s'affichent plus : chacune redirige (307) vers la même adresse sous /feuille-de-route, paramètres compris. Un lien ancien ou un favori mène donc aux pages du site. Régler la variable une fois ces pages prêtes : pendant leur construction, la laisser vide garde les pages du module et les liens des courriels vers elles. Un chemin placé sous /za/feedback ne déclenche aucune redirection.

Seules les pages redirigent. Les routes /za/api/feedback/… restent servies : les pages du site s'en servent pour envoyer un rapport.

Avec plusieurs projets, le site sert le formulaire sous /feuille-de-route/<projet>/nouveau pour chaque projet autre que le principal. Le suivi reste /feuille-de-route/suivi/<jeton>. Voir Plusieurs projets.

2. Lire les données côté serveur

@fraza-pro/module-feedback/server se lit depuis un Server Component, pendant une requête : getMyReports lit la session de la personne, et ne la retient que pour un compte client.

// app/(public)/feuille-de-route/suivi/[jeton]/page.js
import { notFound } from 'next/navigation';
import { getFollowedReport } from '@fraza-pro/module-feedback/server';
import { AuthorReplyForm, ReportView } from '@fraza-pro/module-feedback/public';

export const metadata = { title: 'Suivi', robots: { index: false, follow: false } };

export default async function Page({ params }) {
  const { jeton } = await params;
  const report = await getFollowedReport(jeton);
  if (!report) notFound();
  return (
    <MaMiseEnPage titre="Suivi du message">
      <ReportView report={report} />
      {report.canReply && <AuthorReplyForm token={jeton} />}
    </MaMiseEnPage>
  );
}
FonctionRend
getFollowedReport(token)Le rapport d'un lien de suivi, tel que son auteur le lit, ou null pour un jeton faux ou effacé. Donner un titre générique à la page : elle ne doit pas dire si un jeton existe.
getMyReports()Les rapports du compte client connecté, les plus récents d'abord, sous la même forme. Vide sans compte client : un compte d'équipe, ou tout compte sur un site sans comptes clients, n'en a pas.
getPublicProjects()Les projets publics et non archivés, le principal d'abord, [{ slug, name, isDefault }].
getFeedbackPaths(project){ form, follow(token) }, d'après FRAZA_MODULE_FEEDBACK_PUBLIC_PATH. Avec un projet ({ slug, isDefault }), le formulaire de ce projet.

Un rapport, tel que son auteur le lit :

ChampContenu
project{ slug, name, isDefault } pour un rapport d'un autre projet que le principal, null pour le projet principal.
idLe numéro du rapport, que sendAccountMessage prend.
type, title, bodyCe que la personne a envoyé.
status, closeReasonnew, linked ou closed, et la raison d'une fermeture : resolved, duplicate ou declined. Un rapport fermé comme indésirable a closeReason: null et se lit « Fermé ».
canReplyL'auteur peut encore écrire à l'équipe : le rapport n'est pas fermé, et la conservation n'a pas effacé ses données.
createdAtLa date d'envoi.
itemAvec le module Projets, la fiche d'un rapport lié : { title, column: { name, done }, href }, ou null. href est sa page publique. Pour une fiche sans page publique, title et href valent null, et la colonne se nomme « En traitement » ou « Terminé ». Une colonne interne d'une fiche publique se nomme de la même façon.
messagesLes échanges avec l'équipe, dans l'ordre, chacun { fromAuthor, body, authorName, createdAt }. fromAuthor vaut true pour un message de l'auteur, dont authorName est null, et false pour une réponse publique de l'équipe.

Jamais l'adresse, le contexte, l'étiquette ni les notes internes.

3. Agir depuis le navigateur

@fraza-pro/module-feedback/public donne de quoi envoyer un rapport depuis un composant du site.

FonctionRôle
fetchReportForm({ project })Le jeton du délai minimum, attachments, signedIn (un compte client est connecté : le champ du courriel est inutile), similar (fetchSimilarItems répond pour ce projet), tags (les choix de « Ça concerne… », [{ id, name }]) et project. À appeler à l'ouverture du formulaire.
fetchSimilarItems({ text, project, signal })Les fiches publiques de la feuille de route proches de text : [{ title, path, voteCount, column: { name, isDone } }], la plus proche d'abord. Pour un « Déjà sur la feuille de route ? » pendant la saisie : attendre une pause de la frappe, et passer un signal pour annuler la demande précédente.
sendReport({ type, title, body, email, context, files, token, honeypot, project, tag })Envoie un rapport, au moins trois secondes après fetchReportForm. tag est l'id d'une des tags de fetchReportForm, ou rien. Rend { followUrl, signedIn, accountHref } : le lien de suivi d'un envoi sans compte client, ou la section de l'espace client d'un rapport lié à un compte client.
sendFollowMessage({ token, body })Écrit à l'équipe depuis la page de suivi : token est le dernier segment du lien. Rend le message, { fromAuthor, body, createdAt }.
sendAccountMessage({ reportId, body })Écrit à l'équipe sur un rapport du compte client connecté, reportId étant l'id que donne getMyReports. Même retour.

project est facultatif : le slug d'un projet, rien pour le principal. Quand le projet principal est privé, rien désigne le premier projet public et actif.

Un refus lève une Error dont message est en français et s'affiche tel quel, dont status est le statut HTTP, et dont code nomme le cas à traiter à part : form_expired (redemander un jeton et renvoyer), et pour un message de l'auteur report_closed (le rapport est fermé) ou report_anonymized.

Un formulaire écrit à la main garde les protections du module : un champ invisible passé en honeypot, et le jeton de fetchReportForm. context ne garde que les clés page, browser, system, screen, language et version.

4. Ou reprendre les composants du module

Les composants suivent les jetons du thème : ils prennent les couleurs du site sans rien régler.

ComposantRôle
ReportWizardLe formulaire sur un seul écran, avec « Déjà sur la feuille de route ? ». initialType l'ouvre avec ce type choisi, from est la page d'origine, project le slug du projet qui reçoit le rapport. roadmapHref et accountHref sont les liens montrés après l'envoi : la feuille de route, facultative, et la section de l'espace client. accountHref vaut null par défaut : le formulaire prend alors le lien que rend le serveur, qui suit FRAZA_ACCOUNT_PATH. Le bouton Mes messages et la phrase sur l'espace client ne paraissent qu'avec un lien, donc jamais sur un site sans comptes clients.
ReportViewUn rapport tel que son auteur le lit, avec ses échanges. Prend report seul : le lien vers la fiche est report.item.href.
AuthorReplyFormLe champ Écrire à l'équipe. Prend token sur la page de suivi, ou reportId dans un espace du compte. Relit la page après l'envoi (router.refresh()). L'afficher seulement quand report.canReply est vrai.

Les libellés sont exportés aussi : REPORT_TYPES, REPORT_TYPE_LABELS, REPORT_TYPE_CHOICES (les textes de chaque type dans le formulaire), progressLabel et progressVariant (l'état d'un rapport et la variante de son badge), formatDay.

// app/(public)/feuille-de-route/nouveau/page.js
import { ReportWizard } from '@fraza-pro/module-feedback/public';

export default async function Page({ searchParams }) {
  const { type, depuis } = await searchParams;
  return (
    <MaMiseEnPage titre="Écrire à l'équipe">
      <ReportWizard initialType={type ?? null} from={depuis ?? null} roadmapHref="/feuille-de-route" />
    </MaMiseEnPage>
  );
}

roadmapHref reste vide sans le module Projets. Avec lui, y mettre l'adresse de la feuille de route publique qui reçoit les rapports du projet.

Garder pour from un chemin du site, commençant par / : les pages du module ignorent tout le reste.

Un Server Component ne passe pas de fonction à ces composants, ni à aucun composant client : une icône ou un rappel déclenche une erreur de rendu. Passer des chaînes, des nombres et des booléens.

Plusieurs projets

Dès que le site a plus d'un projet, une page du site lit le projet dans son adresse et le passe au formulaire.

// app/(public)/feuille-de-route/[projet]/nouveau/page.js
import { notFound } from 'next/navigation';
import { getPublicProjects } from '@fraza-pro/module-feedback/server';
import { ReportWizard } from '@fraza-pro/module-feedback/public';

export default async function Page({ params }) {
  const { projet } = await params;
  const project = (await getPublicProjects()).find((p) => p.slug === projet && !p.isDefault);
  if (!project) notFound();
  return <ReportWizard project={project.slug} />;
}

Le suivi n'a pas de projet dans son adresse : le jeton désigne le rapport. report.project nomme le projet dans la vue de l'auteur, sauf pour le projet principal.

Un site sans rendu serveur

Les routes publiques suffisent pour le formulaire : GET /za/api/feedback/public/form, GET /za/api/feedback/public/similar et POST /za/api/feedback/public/reports, et pour écrire à l'équipe depuis le suivi : POST /za/api/feedback/public/follow/<jeton>/messages. Voir API. Le suivi, lui, se lit côté serveur par getFollowedReport.

Sur cette page