@cohiva/support-widget
v1.4.2
Published
Reusable Support Ticket widget with diagnostics capture and typed payload generation
Maintainers
Readme
Cohiva Support Widget
Reusable React support and feedback widget for Cohiva apps. The package owns the widget UI, validation, diagnostics capture, typed feedback payload generation, browser submission adapters, and shared server helpers for thin POST /api/feedback/github-issue host routes.
Features
- Floating bottom-right Support Ticket launcher.
- Two-step flow for
bug,feature,feedback, andimprovementsubmissions. - Built-in validation, structured description generation, and typed
SubmitFeedbackRequestpayloads. - Diagnostics capture for route visits, user interactions, browser metadata, and runtime warnings/errors.
- Diagnostics redaction hooks and configurable clear policies.
- Default browser submission to
POST /api/feedback. - Browser-side GitHub issue proxy submission to
POST /api/feedback/github-issue. - Shared server helpers that turn the existing widget payload into a GitHub issue without moving secrets into the browser.
- Optional Open Issues/Resolved viewer with GitHub issue comments, visible customer updates, closed-ticket reopen, and open-ticket close-with-reason.
- Light, dark, and system theme modes with label and class-name override surface.
- CI-gated release process with 100% source coverage thresholds and Playwright example-host e2e coverage.
Submission Modes
When onSubmit is not provided, the widget supports two built-in submission paths:
central_api(default): POSTs to${apiBaseUrl}/api/feedbackgithub_issue_proxy: POSTs to${apiBaseUrl}/api/feedback/github-issue
Use a custom onSubmit when the host app needs custom transport, auth behavior, retries, or a different backend route.
Install
npm install @cohiva/support-widgetAlso include package CSS once in your app:
import '@cohiva/support-widget/styles.css';Quick Start
import { SupportWidget } from '@cohiva/support-widget';
export function App() {
return (
<SupportWidget
config={{
appName: 'Cohiva Admin',
environment: 'production',
repoSlug: 'cohiva/cohiva-admin',
apiBaseUrl: 'https://api.cohiva.app',
getAuthToken: () => localStorage.getItem('token'),
getCurrentRoute: () => ({
pathname: window.location.pathname,
href: window.location.href,
title: document.title,
}),
getCurrentUserContext: () => ({
user_id: 'u-123',
business_id: 'b-123',
role: 'admin',
}),
}}
/>
);
}Custom Submit Handling
import { SupportWidget, createCentralFeedbackClient } from '@cohiva/support-widget';
const submitFeedback = createCentralFeedbackClient({
apiBaseUrl: 'https://api.cohiva.app',
getAuthToken: () => window.sessionStorage.getItem('token'),
});
export function AppSupport() {
return (
<SupportWidget
config={{
appName: 'Cohiva Admin',
environment: 'production',
repoSlug: 'cohiva/cohiva-admin',
apiBaseUrl: 'https://api.cohiva.app',
getCurrentRoute: () => ({
pathname: window.location.pathname,
href: window.location.href,
title: document.title,
}),
getCurrentUserContext: () => ({
user_id: 'u-123',
business_id: 'b-456',
role: 'owner',
}),
diagnostics: {
captureConsoleWarnings: true,
clearPolicy: 'on_success',
redact: (entry) => {
if (entry.message.includes('password')) {
return null;
}
return {
...entry,
message: entry.message.replace(/token=[^\s]+/g, 'token=[redacted]'),
};
},
},
theme: {
mode: 'system',
launcherLabel: 'Support Ticket',
classNames: {
launcher: 'my-app-launcher',
dialog: 'my-app-dialog',
},
},
}}
onSubmit={submitFeedback}
onSuccess={(result) => {
console.info('Support ticket submitted', result.feedback_id);
}}
onError={(error) => {
console.error('Support ticket failed', error);
}}
/>
);
}Thin GitHub Issue Proxy Route
For submission.mode = 'github_issue_proxy', host apps keep a thin server route and let the shared package own the GitHub issue creation flow:
import { createGitHubIssueProxyHandler } from '@cohiva/support-widget';
export const POST = createGitHubIssueProxyHandler({
githubAppId: process.env.GITHUB_APP_ID!,
githubAppPrivateKey: process.env.GITHUB_APP_PRIVATE_KEY!,
defaultRepoSlug: 'cohiva/cohiva-crunch',
allowedRepoSlugs: process.env.FEEDBACK_GITHUB_REPOS,
});allowedRepoSlugs accepts a string[], a comma/newline-delimited string, or a JSON array string. The helper keeps GitHub App secrets on the server and owns repo resolution, installation token exchange, issue title/body rendering, GitHub issue creation, and normalized JSON errors.
Successful GitHub issue proxy responses use this shape:
{
"message": "GitHub issue created successfully",
"issue_number": 123,
"issue_url": "https://github.com/org/repo/issues/123",
"repo_slug": "org/repo"
}GitHub Issues Viewer Route
Enable config.issues.enabled to show Open Issues and Resolved tabs. Mount createGitHubIssueListHandler at /api/feedback/github-issues to support list/detail reads, user comments, and ticket closure:
import { createGitHubIssueListHandler } from '@cohiva/support-widget';
export const GET = createGitHubIssueListHandler({
githubAppId: process.env.GITHUB_APP_ID!,
githubAppPrivateKey: process.env.GITHUB_APP_PRIVATE_KEY!,
defaultRepoSlug: 'cohiva/cohiva-crunch',
allowedRepoSlugs: process.env.FEEDBACK_GITHUB_REPOS,
});
export const POST = GET;POST /api/feedback/github-issues/:issue_number/comments adds a GitHub issue comment. If the issue is closed, the shared handler reopens it after adding the comment. The widget also shows the submitted comment below the ticket description after GitHub accepts it.
POST /api/feedback/github-issues/:issue_number/close accepts { "reason": "Close reason" }. The shared handler saves the reason as a GitHub issue comment, then closes the issue. The widget only shows this action on open tickets and displays the close reason below the ticket description after GitHub accepts it.
Host Responsibilities
The package owns the shared feedback flow and shared GitHub issue creation logic. Each host app still owns:
- mounting the widget
- providing app, route, and auth context
- deciding whether the widget is visible
- wiring any app-specific auth checks on
POST /api/feedback/github-issue - wiring any app-specific auth checks on the issues list/detail/comment/close route
- injecting GitHub App secrets on the server
- any repo-specific issue title/body customization beyond the shared default
Development
npm install
npm run dev:example
npm run lint
npm run typecheck
npm run test:run
npm run test:coverage
npm run test:e2e
npm run build
npm run test:smoke
npm run checkTest Commands
npm run test:unit
npm run test:component
npm run test:run
npm run test:smoke
npm run test:coverage
npm run test:e2enpm run test:coverage enforces 100% line, statement, function, and branch coverage for the package source, excluding the public barrel file and type-only export file. npm run test:e2e runs the Playwright example-host flow.
Release Workflow Summary
- Bump
package.jsonto the version you want to publish. - Push that commit to
master. - GitHub Actions runs the CI gate:
npm run lintnpm run typechecknpm run test:coveragenpm run buildnpm run test:smokenpm run test:e2e
- Only after CI succeeds,
publish.ymlchecks whether that exact version is already on npm and publishes it if it is new.
Local preflight:
npm run check
npm run test:e2eDocs
docs/public-api.mddocs/configuration.mddocs/diagnostics.mddocs/theming.mddocs/accessibility.mddocs/development.mddocs/testing.mddocs/releasing.mddocs/github-actions.mddocs/repo-secrets-and-publishing.mddocs/consuming-the-package.mddocs/github-actions-setup-summary.md
Node Version Note
CI uses Node 20.x. Local development should use Node 20 for best alignment with CI validation and release pipelines.
