@groo.dev/feedback-sdk
v0.5.0
Published
React widget components and Hono proxy middleware for integrating the feedback system.
Readme
@groo.dev/feedback-sdk
Embed a feedback widget into any React application. Users can submit bug reports, feature requests, and general feedback — then track their submissions and see admin responses.
Pairs with a Hono route handler that keeps your API key server-side, handles auth, and injects user identity automatically.
Install
npm install @groo.dev/feedback-sdkQuick Start
1. Add the proxy route to your Hono API worker
import { feedbackRoute } from '@groo.dev/feedback-sdk/hono'
import { createAuth } from './auth' // your auth
app.route('/api/@groo.dev/feedback', feedbackRoute({
apiBase: 'https://feedback.miranet.work/v1/sdk',
getApiKey: (env) => env.FEEDBACK_API_KEY,
getUser: async (c) => {
const session = await getSession(c) // your auth logic
if (!session) return null
return { id: session.user.id, name: session.user.name, email: session.user.email }
},
}))Add FEEDBACK_API_KEY to your .dev.vars (local) and worker secrets (production).
2. Add a widget to your React app
import { FeedbackFloating } from '@groo.dev/feedback-sdk/react'
function App() {
return (
<>
{/* your app */}
<FeedbackFloating apiBase="/api/@groo.dev/feedback" />
</>
)
}That's it. A floating button appears in the bottom-right corner. Users can submit feedback and view their past submissions.
Proxy Route
feedbackRoute() creates a complete Hono sub-app that handles auth, user identity injection, and request forwarding. You provide three things:
import { feedbackRoute } from '@groo.dev/feedback-sdk/hono'
app.route('/api/@groo.dev/feedback', feedbackRoute({
apiBase: 'https://feedback.miranet.work/v1/sdk',
getApiKey: (env) => env.FEEDBACK_API_KEY,
getUser: async (c) => { ... },
}))| Option | Type | Description |
|--------|------|-------------|
| apiBase | string | Feedback API URL |
| getApiKey | (env) => string | Extracts API key from worker env |
| getUser | (c) => Promise<User \| null> | Extracts authenticated user from request. Return null to reject. |
The User object must have id, name, and email fields.
Routes handled
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /health | No | Health check — verifies proxy is mounted |
| POST | /feedback | Yes | Submit feedback |
| GET | /feedback | Yes | List user's own feedback |
| GET | /feedback/:id | Yes | Feedback detail with comments |
| POST | /attachments | Yes | Upload file attachment |
| GET | /attachments/:id | Yes | Download attachment |
How it works
getUseris called on every request (except/health)- If it returns
null, the request is rejected with 401 - The user's identity is injected into the request (body or query param)
- The request is forwarded to the feedback API with your API key
- The React components never see user identity or the API key
React Components
All components accept a shared apiBase prop (the proxy URL) and an optional prefill for pre-populating the form.
type FeedbackProps = {
apiBase: string
prefill?: {
type?: 'bug' | 'feature' | 'general'
title?: string
description?: string
}
}<FeedbackFloating />
Fixed floating button in the bottom-right corner. Opens a modal on click. Drop it in your root layout — visible on every page.
<FeedbackFloating apiBase="/api/@groo.dev/feedback" />
// With prefill
<FeedbackFloating
apiBase="/api/@groo.dev/feedback"
prefill={{ type: 'bug' }}
/><FeedbackButton />
An inline button you place anywhere — nav bar, help menu, footer. Renders children as the button content.
<FeedbackButton
apiBase="/api/@groo.dev/feedback"
className="btn btn-secondary"
>
Report an Issue
</FeedbackButton><FeedbackContextual />
Controlled modal for programmatic triggers. Useful in error boundaries or catch blocks.
const [open, setOpen] = useState(false)
<FeedbackContextual
apiBase="/api/@groo.dev/feedback"
prefill={{
type: 'bug',
title: `Error on ${window.location.pathname}`,
description: error.message,
}}
open={open}
onClose={() => setOpen(false)}
/><FeedbackModal />
Headless controlled modal. You manage the open/close state entirely.
const [open, setOpen] = useState(false)
<button onClick={() => setOpen(true)}>Feedback</button>
<FeedbackModal
apiBase="/api/@groo.dev/feedback"
open={open}
onOpenChange={setOpen}
/>Auth Examples
Clerk
import { getAuth, clerkClient } from '@clerk/backend'
app.route('/api/@groo.dev/feedback', feedbackRoute({
apiBase: 'https://feedback.miranet.work/v1/sdk',
getApiKey: (env) => env.FEEDBACK_API_KEY,
getUser: async (c) => {
const auth = getAuth(c)
if (!auth.userId) return null
const user = await clerkClient(c.env).users.getUser(auth.userId)
return {
id: auth.userId,
name: [user.firstName, user.lastName].filter(Boolean).join(' '),
email: user.emailAddresses[0]?.emailAddress ?? '',
}
},
}))Better Auth
import { createAuth } from './auth'
app.route('/api/@groo.dev/feedback', feedbackRoute({
apiBase: 'https://feedback.miranet.work/v1/sdk',
getApiKey: (env) => env.FEEDBACK_API_KEY,
getUser: async (c) => {
const auth = createAuth(c.env)
const session = await auth.api.getSession({ headers: c.req.raw.headers })
if (!session) return null
return { id: session.user.id, name: session.user.name, email: session.user.email }
},
}))Theming
The widget uses CSS custom properties with a --fbk- prefix. Override any variable in your app's CSS to customize the look:
:root {
--fbk-accent: #6366f1; /* primary color (buttons, active states) */
--fbk-accentHover: #818cf8; /* primary hover */
--fbk-bg: #0f0f12; /* panel background */
--fbk-surface: #1a1a1e; /* elevated surface (comments, chips) */
--fbk-text: #f0f0f0; /* primary text */
--fbk-muted: #888; /* secondary text */
--fbk-dim: #555; /* placeholder text */
--fbk-border: rgba(255,255,255,0.1);
--fbk-error: #ef4444;
--fbk-success: #22c55e;
--fbk-radius: 4px; /* border radius */
--fbk-fontBody: 'Inter', sans-serif;
--fbk-fontMono: 'JetBrains Mono', monospace;
}All variables have sensible defaults (warm dark theme). You only need to override what you want to change.
Status colors
:root {
--fbk-statusNew: #e8a849;
--fbk-statusAcknowledged: #5b9bd5;
--fbk-statusInProgress: #9b7ed8;
--fbk-statusResolved: #5cb870;
--fbk-statusClosed: #4a4e56;
}License
MIT
