Skip to content

Feedback Widget

A centralized feedback system built into the @anglinai/ui CorporateFooter component. Users on any AnglinAI project can submit feedback, which is analyzed by Claude for sentiment and category, turned into a HelpDesk ticket, and confirmed via email.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Any AnglinAI Frontend β”‚ β”‚ feedback-api (CF Worker) β”‚
β”‚ β”‚POST β”‚ β”‚
β”‚ CorporateFooter │────▢│ 1. Validate input (Zod) β”‚
β”‚ └─ FeedbackWidget β”‚ β”‚ 2. Claude Sonnet β†’ analyze β”‚
β”‚ (auth-aware) │◀────│ 3. HelpDesk MCP β†’ ticket β”‚
β”‚ β”‚ JSONβ”‚ 4. Resend β†’ confirm email β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  1. User clicks Send Feedback in the footer
  2. A compact dialog appears with a comment field (and email field if not logged in)
  3. On submit, the widget POSTs to the feedback-api Cloudflare Worker
  4. The Worker runs Claude Sonnet to classify sentiment, category, and priority
  5. A HelpDesk ticket is created via MCP JSON-RPC with the analysis metadata
  6. If the user provided an email, a confirmation email is sent via Resend (non-blocking)

Adding the Feedback Widget to an App

Step 1: Install @anglinai/ui v0.3.0+

Terminal window
npm install @anglinai/ui@^0.3.0

Ensure your .npmrc has:

@anglinai:registry=https://npm.pkg.github.com

Step 2: Set the environment variable

Add to .env.local:

Terminal window
NEXT_PUBLIC_FEEDBACK_API_URL=https://feedback-api.anglin.com

Add to .env.local.example for documentation:

Terminal window
# ─── Feedback ───────────────────────────────────────────────────────────────
NEXT_PUBLIC_FEEDBACK_API_URL=https://feedback-api.anglin.com

Step 3: Create an AppFooter component

Create a client component that reads the auth context and passes the user info to the footer:

src/components/layout/AppFooter.tsx
'use client';
import { CorporateFooter, type FeedbackUser } from '@anglinai/ui';
import { useAuth } from '@/lib/auth-context';
const FEEDBACK_API_URL = process.env.NEXT_PUBLIC_FEEDBACK_API_URL || '';
export function AppFooter() {
const { user } = useAuth();
const feedbackUser: FeedbackUser | undefined = user?.email
? { name: user.name, email: user.email }
: undefined;
return (
<CorporateFooter
feedbackEndpoint={FEEDBACK_API_URL ? `${FEEDBACK_API_URL}/api/feedback` : undefined}
feedbackProjectName="My Project Name"
feedbackUser={feedbackUser}
copyrightHolder="TheAccessibleOrg"
docsHref="https://help.myproject.com"
/>
);
}

Step 4: Add to the root layout

Place <AppFooter /> inside your AuthProvider so it has access to the user context. Make the body a flex column so the footer sticks to the bottom:

src/app/layout.tsx
import { AppFooter } from '@/components/layout/AppFooter';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className="flex min-h-screen flex-col">
<ThemeProvider defaultTheme="system">
<AuthProvider>
<main id="main-content" className="flex-1">
{children}
</main>
<AppFooter />
</AuthProvider>
</ThemeProvider>
</body>
</html>
);
}

Key points:

  • flex min-h-screen flex-col on <body> ensures the footer sticks to the bottom
  • flex-1 on <main> makes the content area fill available space
  • <AppFooter /> must be inside <AuthProvider> so useAuth() works
  • The CorporateFooter has mt-auto built in, so it naturally sits at the bottom

If you previously had a CorporateFooter in a marketing layout or a custom footer component, remove it β€” the root layout footer now appears on every page.

CorporateFooter Props

PropTypeDefaultDescription
feedbackEndpointstringβ€”URL for the feedback API. Widget only renders when set.
feedbackProjectNamestringβ€”Identifies the source project in tickets. Falls back to page hostname.
feedbackUserFeedbackUserβ€”Pre-fills email and shows β€œSubmitting as {name}”. See below.
copyrightHolderstring"AnglinAI"Name shown in the copyright line. Use "TheAccessibleOrg" for TAO products.
docsHrefstringβ€”When set, appends a β€œDocumentation” link to the footer nav.
productNamestringβ€”When set, shows β€œPart of the AnglinAI family” text.
linksFooterLink[]Default setOverride the footer navigation links entirely.
showVersionbooleantrueShow/hide the VersionInfo component.
versionPropsVersionInfoPropsβ€”Build version data for the VersionInfo modal.
childrenReactNodeβ€”Project-specific content rendered above the corporate section.
classNamestringβ€”Additional CSS classes on the <footer> element.

FeedbackUser Interface

interface FeedbackUser {
name?: string; // Displayed as "Submitting as {name}"
email: string; // Pre-filled, hidden from the form
}

When feedbackUser is provided:

  • The email input is hidden (email is sent automatically)
  • A line reading β€œSubmitting as {name}” appears below the comment field (with aria-live="polite")
  • The userName field is included in the POST payload

When feedbackUser is omitted:

  • The email input is shown as optional (same as before v0.3.0)
  • No userName is sent

Using the FeedbackWidget Standalone

The widget is also exported on its own if you want to place it somewhere other than the footer:

import { FeedbackWidget } from '@anglinai/ui';
<FeedbackWidget
feedbackEndpoint="https://feedback-api.anglin.com/api/feedback"
projectName="my-project"
user={{ name: 'Jane Doe', email: 'jane@example.com' }}
/>

Feedback API

Base URL: https://feedback-api.anglin.com

Source: anglinai-monorepo/apps/feedback-api/

GET /health

Returns service health status.

{ "status": "ok", "version": "1.0.0", "uptime": 12345 }

POST /api/feedback

Submit feedback for analysis and ticket creation.

Request body:

{
"pageUrl": "https://orgchart.anglin.com/dashboard",
"comment": "The export button doesn't work on mobile",
"email": "user@example.com",
"userName": "Jane Doe",
"project": "Accessible Org Chart Generator",
"userAgent": "Mozilla/5.0 ..."
}
FieldTypeRequiredDescription
pageUrlstringYesFull URL of the page (auto-captured by widget)
commentstringYesFeedback text (1–5000 chars)
emailstringNoUser’s email for follow-up and confirmation
userNamestringNoUser’s display name (sent when logged in)
projectstringNoProject identifier for ticket tagging
userAgentstringNoBrowser user agent (auto-captured by widget)

Success response (201):

{
"data": {
"message": "Feedback submitted successfully",
"ticketId": "abc123"
},
"error": null
}

Error response (400):

{
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "Comment is required"
}
}

Processing Pipeline

  1. Validation β€” Zod schema checks all fields, returns 400 on failure

  2. Claude Analysis β€” Sends the comment to Claude Sonnet which returns:

    • sentiment: { label: "positive"|"negative"|"neutral", intensity: 0.0–1.0 }
    • category: bug_report, feature_request, complaint, praise, question, or general
    • title: Prefixed ticket title (e.g. [Bug] Export fails on mobile)
    • summary: 1–2 sentence description for the ticket
  3. Priority Mapping:

    ConditionPriority
    Negative + bug + intensity β‰₯ 0.7Urgent
    Negative + complaint + intensity β‰₯ 0.7High
    Bug reportMedium
    Feature requestMedium
    QuestionLow
    PraiseBackground
    Everything elseLow
  4. HelpDesk Ticket β€” Created via MCP JSON-RPC in the theaccessibleorg tenant

  5. Confirmation Email β€” Sent via Resend with reply-to: feedback@theaccessible.org (non-blocking via waitUntil)

CORS

  • Production: Only allows origins listed in the ALLOWED_ORIGINS secret (comma-separated)
  • Development: Also allows http://localhost:*

Secrets

Set via wrangler secret put:

SecretDescription
ANTHROPIC_API_KEYClaude API key for feedback analysis
RESEND_API_KEYResend API key for confirmation emails
HELPDESK_API_KEYHelpDesk MCP API key for ticket creation
HELPDESK_TENANT_IDHelpDesk tenant ID (theaccessibleorg)
ALLOWED_ORIGINSComma-separated list of allowed CORS origins

Cost Tracking

Every Claude API call logs a structured JSON entry to stdout with:

{
"type": "cost_tracking",
"operation": "claude_analysis",
"model": "claude-sonnet-4-20250514",
"inputTokens": 245,
"outputTokens": 89,
"estimatedCostUsd": 0.002070,
"project": "Accessible Org Chart Generator",
"timestamp": "2026-03-03T12:00:00.000Z"
}

HelpDesk Tenant

Feedback tickets are created in the TheAccessibleOrg HelpDesk tenant:

  • Slug: theaccessibleorg
  • Owner: larry@anglin.com
  • Settings: allowPublicSubmission: true, enableGuestAccess: true

Tickets include the AI-generated title, description with sentiment metadata, priority, contact info, and tags for the source project.

Email Notifications

When a user includes an email address, they receive a confirmation:

  • From: noreply@tagzen.ai
  • Reply-to: feedback@theaccessible.org
  • Subject: We received your feedback β€” Ticket #abc123
  • Body: Greeting, ticket ID, β€œOur team will review it shortly” message
  • Branding: White body, light gray wrapper, AnglinAI copyright footer

The email send is non-blocking β€” if Resend is down, the ticket is still created and the user still sees a success message.

Files

FileLocationPurpose
feedback-widget.tsxanglinai-monorepo/packages/ui/src/components/React widget component
corporate-footer.tsxanglinai-monorepo/packages/ui/src/components/Footer integration
index.tsanglinai-monorepo/apps/feedback-api/src/Worker entry point
routes/feedback.tsanglinai-monorepo/apps/feedback-api/src/POST handler
services/analyzer.tsanglinai-monorepo/apps/feedback-api/src/Claude analysis
services/helpdesk.tsanglinai-monorepo/apps/feedback-api/src/HelpDesk ticket creation
services/email.tsanglinai-monorepo/apps/feedback-api/src/Resend confirmation
AppFooter.tsxaccessible-org-chart/apps/web/src/components/layout/Org chart integration
AppFooter.tsxaccessible-pdf-converter/apps/web/src/components/layout/PDF converter integration

Legacy: Direct Supabase Endpoint

The original feedback system posted directly to a Supabase Edge Function at https://dktmitcptketnziahoho.supabase.co/functions/v1/feedback. This stored feedback in the public.feedback table without analysis or ticketing. The Edge Function and database table still exist and can be used as a fallback, but new integrations should use the feedback-api Worker described above.