Skip to Content
HelpHelp Improvement 2 — "Was This Helpful?" Ratings

Help Improvement 2 — “Was This Helpful?” Ratings

Status: [To Build]

Purpose: Collect per-topic thumbs-up / thumbs-down signals so we know which help topics need rewriting. Currently there is zero feedback mechanism — content quality is invisible.


User Experience

A small footer at the bottom of every HelpPage (both standalone /help/{slug} routes and inside the drawer):

────────────────────────────────────── Was this helpful? 👍 👎 ──────────────────────────────────────

After clicking either thumb:

────────────────────────────────────── Thanks for your feedback. ──────────────────────────────────────

State resets on slug change. One rating per page per session — clicking again replaces the previous rating.


Data Model

Add to packages/db/prisma/schema.prisma:

model HelpPageRating { id String @id @default(cuid()) slug String rating HelpRating tenantId String createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) @@index([slug]) @@index([tenantId]) @@map("help_page_rating") } enum HelpRating { helpful not_helpful }

API Endpoint

POST /tenant/v1/help/rating

// Request { slug: string; rating: "helpful" | "not_helpful" } // Response { ok: true }

Implementation: upsert on (tenantId, slug) — replaces the previous rating if one exists for this session.

// apps/api/src/routers/help.ts fastify.post("/tenant/v1/help/rating", { preHandler: [requireTenantUser], schema: { body: { type: "object", required: ["slug", "rating"], properties: { slug: { type: "string" }, rating: { type: "string", enum: ["helpful", "not_helpful"] }, }, }, response: { 200: { type: "object", additionalProperties: true } }, }, async handler(req, reply) { const { slug, rating } = req.body as { slug: string; rating: "helpful" | "not_helpful" }; const tenantId = req.user.tenantId; await db.helpPageRating.upsert({ where: { tenantId_slug: { tenantId, slug } }, update: { rating, createdAt: new Date() }, create: { slug, rating, tenantId }, }); reply.send({ ok: true }); }, });

Note: requires @@unique([tenantId, slug]) on the model for the upsert where clause.


UI Component (packages/ui/src/HelpRating.tsx)

"use client"; import { useState } from "react"; import { ThumbsUp, ThumbsDown } from "lucide-react"; interface HelpRatingProps { slug: string; onRate: (slug: string, rating: "helpful" | "not_helpful") => Promise<void>; } export function HelpRating({ slug, onRate }: HelpRatingProps) { const [submitted, setSubmitted] = useState(false); async function handleRate(rating: "helpful" | "not_helpful") { await onRate(slug, rating); setSubmitted(true); } if (submitted) { return ( <p className="text-sm text-muted-foreground text-center py-4"> Thanks for your feedback. </p> ); } return ( <div className="flex items-center gap-3 justify-center py-4 border-t"> <span className="text-sm text-muted-foreground">Was this helpful?</span> <button onClick={() => handleRate("helpful")} className="p-1.5 rounded hover:bg-green-50 dark:hover:bg-green-950 hover:text-green-600 transition-colors" aria-label="Yes, helpful" > <ThumbsUp className="w-4 h-4" /> </button> <button onClick={() => handleRate("not_helpful")} className="p-1.5 rounded hover:bg-red-50 dark:hover:bg-red-950 hover:text-red-600 transition-colors" aria-label="Not helpful" > <ThumbsDown className="w-4 h-4" /> </button> </div> ); }

The onRate prop keeps the component server-agnostic — the dashboard app passes an apiFetch-based handler, a future mobile app can pass its own.


Integration into HelpPage

// packages/ui/src/HelpPage.tsx // Add onRate optional prop — if omitted, rating footer is hidden interface HelpPageProps { data: HelpPageData; inDrawer?: boolean; onRate?: (slug: string, rating: "helpful" | "not_helpful") => Promise<void>; } // At the bottom of the page render, after all sections: {onRate && <HelpRating slug={data.slug} onRate={onRate} />}

In the dashboard, pass onRate from a client wrapper:

// apps/dashboard/src/app/(dashboard)/help/[slug]/page.tsx // becomes a client component wrapper that provides onRate

Admin Visibility (future)

The HelpPageRating table is queryable from manage portal analytics. A simple aggregate query gives the satisfaction rate per topic:

SELECT slug, COUNT(*) FILTER (WHERE rating = 'helpful') AS helpful_count, COUNT(*) FILTER (WHERE rating = 'not_helpful') AS not_helpful_count FROM help_page_rating GROUP BY slug ORDER BY not_helpful_count DESC;

No UI needed immediately — the data just needs to be collected now so it can be surfaced later.


Affected Files

FileChange
packages/db/prisma/schema.prismaAdd HelpPageRating model + HelpRating enum
packages/ui/src/HelpRating.tsxNew component
packages/ui/src/HelpPage.tsxAdd onRate prop; render <HelpRating> at bottom
packages/ui/src/index.tsExport HelpRating
apps/api/src/routers/help.tsNew router — POST /tenant/v1/help/rating
apps/api/src/app.tsRegister help router
apps/api/src/index.tsRegister help router
apps/dashboard/src/app/(dashboard)/help/[slug]/HelpPageClient.tsxNew — client wrapper providing onRate via apiFetch
apps/dashboard/src/app/(dashboard)/help/[slug]/page.tsxRender <HelpPageClient>

© 2026 Leadmetrics — Internal use only