Skip to main content

User Feedback — Technical Reference

← Back to User Feedback

Where this lives

Backend

Frontend

  • apps/frontend-nextjs/src/page-components/settings/FeedbackPage.tsx (routed at apps/frontend-nextjs/src/app/settings/feedback/page.tsx, linked from Settings → Send feedback) — the submit form, the "Your submissions" history (myFeedback), and the "Community feature requests" votable board (publicFeedback + upvoteFeedback/downvoteFeedback). GraphQL operations are declared inline in the component (no generated hooks yet, same pattern as SocialLinksPage.tsx).
  • apps/frontend-admin/src/app/feedback/page.tsx + FeedbackContent.tsx — the admin triage queue (/feedback, requires MANAGE_FEEDBACK). See User Feedback (admin) for the full admin GraphQL surface and permission details.

Technical implementation checklist

  • submitFeedback — wired: SubmitFeedbackInput → resolver → user-feedback.manager.js#submitFeedback; used by FeedbackPage.tsx
  • myFeedback — wired, authenticated; used by FeedbackPage.tsx's "Your submissions" list
  • feedbackById — wired, authenticated, owner-only (resolves to null if the caller doesn't own the item)
  • publicFeedback — wired; votable board, defaults to feedbackType: feature_request; used by FeedbackPage.tsx's "Community feature requests"
  • upvoteFeedback / downvoteFeedback — wired, deduped per user via user_feedback_vote; used by FeedbackPage.tsx
  • submitBugReport / submitFeatureRequest shorthand — exist only as manager methods; not exposed as GraphQL mutations
  • getMostUpvoted — exists only as a manager method; not exposed as a GraphQL query (the publicFeedback query already orders by upvotes and serves as the public leaderboard)
  • Admin review (adminGetFeedback, adminGetFeedbackStats, adminUpdateFeedbackStatus, adminSetFeedbackPriority, adminRespondToFeedback) — wired on the admin schema, gated by MANAGE_FEEDBACK, with a real /feedback admin frontend. See User Feedback (admin). markAsResolved/getMostUpvoted/getByStatus/getByPriority remain manager-only (unused by any resolver - the latter two are dead code left over from before getAllFeedback/getAll grew filter support).

Feedback types

TypeDescription
bugSomething is broken or not working as expected
feature_requestRequest for a new feature
generalGeneral feedback
complaintComplaint about the platform or another user
suggestionImprovement idea

Submitting feedback

submitFeedback creates a new feedback item. feedbackType determines how it's routed. title is the one-line summary; description is the full text. The response includes the initial status (pending).

mutation SubmitFeedback($input: SubmitFeedbackInput!) {
submitFeedback(input: $input) {
id feedbackType title status createdAt
}
}

SubmitFeedbackInput fields: feedbackType (required — bug | feature_request | general | complaint | suggestion), title (required), description (required), category (optional, e.g. ui_ux | performance | feature | content | security | privacy | other; defaults to other), platform (optional; defaults to web), satisfactionRating (optional 1-5 int).

The manager also exposes submitBugReport(userId, title, description) / submitFeatureRequest(userId, title, description) convenience wrappers that set feedbackType automatically, but they are not exposed as GraphQL mutations — the client sets feedbackType directly in SubmitFeedbackInput instead (see FeedbackPage.tsx's type selector).

User queries

myFeedback shows the current user's submission history with live status updates, including any adminResponse.

query MyFeedback($limit: Int, $offset: Int) {
myFeedback(limit: $limit, offset: $offset) {
id feedbackType title description status priority upvotes adminResponse createdAt
}
}

feedbackById fetches a single item by id — the caller must own it, otherwise it resolves to null.

query FeedbackById($id: ID!) {
feedbackById(id: $id) {
id feedbackType title description status priority adminResponse createdAt
}
}

Voting

Community members can signal which feature requests or bug reports matter most. upvoteFeedback / downvoteFeedback are deduped per user via the user_feedback_vote table (one row per user per feedback item): voting again toggles the vote off, and voting the opposite way switches it. The denormalized upvotes/downvotes counts live on user_feedback, and UserFeedback.myVote reflects the caller's current vote ('up' | 'down' | null).

publicFeedback is the public, votable board — it orders by upvotes descending, defaults to feedbackType: feature_request, and accepts 'all' to browse every type. There is no separate mostUpvotedFeedback query; publicFeedback's ordering already serves as the leaderboard.

mutation UpvoteFeedback($feedbackId: ID!) { upvoteFeedback(feedbackId: $feedbackId) { id upvotes downvotes myVote } }
mutation DownvoteFeedback($feedbackId: ID!) { downvoteFeedback(feedbackId: $feedbackId) { id upvotes downvotes myVote } }

query PublicFeedback($feedbackType: String, $limit: Int, $offset: Int) {
publicFeedback(feedbackType: $feedbackType, limit: $limit, offset: $offset) {
id title description feedbackType status upvotes downvotes myVote createdAt
user { id username }
}
}

Status lifecycle

pending → in_review → in_progress → resolved
↘ rejected
↘ duplicate

This is the set validated by the manager's updateStatus method, exposed as adminUpdateFeedbackStatus on the admin schema (see "Admin management" below).

Admin management

graphql/resolvers/admin/user-feedback-admin.resolver.js wires up the admin-facing operations, gated by context.admin + the MANAGE_FEEDBACK permission (see User Feedback (admin) for the full checklist, permission details, and GraphQL reference):

  • adminGetFeedbackuser-feedback.manager.js#getAllFeedback — list feedback with optional status/priority/feedbackType filters, paginated
  • adminUpdateFeedbackStatusupdateStatus — moves an item through the lifecycle above
  • adminSetFeedbackPrioritysetPriority — assigns urgency (low / medium / high / critical)
  • adminRespondToFeedback → the new respondToFeedback manager method — sets adminResponse, reviewedBy (the calling admin's id), and reviewedAt together in one action
  • adminGetFeedbackStatsgetFeedbackStats — breakdown by type, status, and priority, plus vote totals

markAsResolved (sets status: 'resolved', resolvedAt, and a free-text resolution field that isn't actually a column on UserFeedback - a pre-existing no-op) has no resolver and isn't used by the admin UI, which drives status entirely through adminUpdateFeedbackStatus.

adminResponse is exposed in myFeedback / feedbackById / publicFeedback as well as the admin schema, so a response written via adminRespondToFeedback shows up in FeedbackPage.tsx immediately.

Priority levels

PriorityDescription
lowMinor issue, no urgency
mediumDefault priority
highSignificant impact
criticalBlocking / urgent