Sources de données
Endpoint, store ou entièrement personnalisée — comment l'inbox parle à vos données.
L'inbox lit et écrit à travers une petite abstraction, que vous pouvez fournir de trois façons. Choisissez-en exactement une — source, store ou endpoint. Elles s'excluent mutuellement dans le type : aucune priorité à retenir, et aucun moyen d'en passer deux en se demandant laquelle l'emporte.
Mode endpoint (le défaut en production)
<SitepingInbox projects="mon-projet" endpoint="/api/siteping" apiKey={KEY} />Parle à votre endpoint SitePing : GET pour les listes, PATCH pour les statuts, DELETE pour les suppressions, toujours avec cache: "no-store". Les erreurs arrivent typées par code (AUTH pour 401/403 et VALIDATION pour les autres 4xx, chacun avec le status HTTP, SERVER pour les 5xx, NETWORK pour les échecs de fetch). Il n'y a volontairement aucun réessai — une interface de tri doit vous dire les choses tout de suite.
Derrière la connexion de votre application, laissez apiKey de côté : montée sur l'origine de l'endpoint, l'inbox envoie votre cookie de session à chaque requête, et une politique access côté serveur le lit — voir la recette Better Auth. Depuis une autre origine, envoyez un jeton avec headers.
Mode store
<SitepingInbox projects="ma-demo" store={new LocalStorageStore()} />Fonctionne avec n'importe quel SitepingStore en mémoire du processus — les mêmes stores que le mode côté client du widget. Les démos et prototypes n'ont jamais besoin de serveur.
Source personnalisée
Pour tRPC, GraphQL ou les server actions, implémentez le contrat InboxSource à trois méthodes et passez-le en source :
import type { InboxSource } from "@siteping/dashboard";
const source: InboxSource = {
async list(query) {
// query : projectName, page, limit, status?, type?, search?, url?, urlPattern?
const { feedbacks, total } = await trpc.siteping.list.query(query);
// Important : createdAt / updatedAt / resolvedAt doivent être de vrais objets Date
return { feedbacks: feedbacks.map(reviveDates), total };
},
async setStatus(id, projectName, status) {
return reviveDates(await trpc.siteping.setStatus.mutate({ id, projectName, status }));
},
async remove(id, projectName) {
await trpc.siteping.remove.mutate({ id, projectName });
},
};Fils de discussion
Les fils sont facultatifs dans une source personnalisée. Les feedbacks portent le leur dans comments (avec createdAt en Date, comme le feedback) ; pour que l'inbox poste et supprime des réponses, ajoutez les deux méthodes de fil :
const source: InboxSource = {
// …list, setStatus, remove comme plus haut
async addComment(feedbackId, projectName, input) {
// input : body, authorName, authorEmail, authorRole ("team"), clientId
return reviveDates(await trpc.siteping.addComment.mutate({ feedbackId, projectName, ...input }));
},
async removeComment(feedbackId, projectName, commentId) {
await trpc.siteping.removeComment.mutate({ feedbackId, projectName, commentId });
},
};list() peut aussi renvoyer capabilities: { comments: false } pour mettre les fils en lecture seule face à un backend qui n'en garde pas, ou deleteComments: false pour masquer les boutons de suppression d'un backend qui ne sait pas supprimer les réponses ; en leur absence, addComment et removeComment seuls décident. Une source sans removeComment poste des réponses mais n'en supprime aucune. Quand la réponse n'existe déjà plus, faites lever à removeComment une erreur dont le status vaut 404 : l'inbox la retire alors au lieu d'échouer. L'inbox répond au nom de l'équipe, dont les réponses n'atteignent jamais le plafond d'un fil : un 409 renvoyé par addComment signifie que le serveur n'a pas pris la réponse pour celle de l'équipe (voir le rôle équipe), et le champ de réponse indique l'échec. Les erreurs de la source endpoint portent le status de la réponse, et celles de la source store sont les erreurs du store lui-même. La source endpoint reprend les capabilities annoncées par le serveur (et passe en lecture seule face à un serveur qui n'annonce rien) ; la source store ne définit ces méthodes que si le store implémente addComment / deleteComment.
Permissions
Un enregistrement peut porter permissions — { canChangeStatus, canDelete, canComment, canDeleteComment }, ce que l'appelant peut en faire — depuis list() comme depuis setStatus(). L'inbox masque alors ce qu'elles refusent (voir Permissions) ; un enregistrement sans elles ne refuse rien. Quand setStatus() renvoie un enregistrement sans elles, il garde celles de list(). La source endpoint transmet celles du serveur.
Les deux briques qui sous-tendent les modes intégrés sont également exportées — createEndpointSource({ endpoint, apiKey, headers }) et createStoreSource(store) — pour que vous puissiez les envelopper, les composer ou les décorer (par exemple ajouter de la journalisation autour du client HTTP) plutôt que de repartir de zéro.
Des types sans dépendance supplémentaire
@siteping/dashboard réexporte tout ce qu'il faut pour typer un composant qui l'enrobe — vous n'installez jamais rien d'autre :
| Ce que vous typez | Exports |
|---|---|
| Les données de feedback | FeedbackRecord, FeedbackStatus, FeedbackType, SitepingStore, ainsi que les constantes FEEDBACK_STATUSES / FEEDBACK_TYPES et isClosedStatus() |
| Les permissions | FeedbackPermissions (ce que l'appelant peut faire d'un enregistrement), InboxRecord (un enregistrement avec ses permissions optionnelles), InboxPage (ce que résout list()) |
| Les fils | CommentRecord (une réponse enregistrée), CommentCreateInput (ce que reçoit addComment), SitepingCapabilities (ce que list() peut annoncer) |
| Les options du hook | UseSitepingInboxOptions et ses trois membres — InboxEndpointOptions, InboxStoreOptions, InboxCustomSourceOptions — plus le socle commun InboxSharedOptions |
| Le retour du hook | InboxState, InboxStatusFilter, InboxTypeFilter |
| Les sources | InboxSource, EndpointSourceOptions (l'argument de createEndpointSource) |
| Les props du composant | SitepingInboxProps, ou SitepingInboxPresentationProps pour la seule moitié présentation |
| Le thème | InboxTheme ("light" | "dark" | "auto") et ResolvedTheme ("light" | "dark" — ce que auto résout) |