@danainnovations/feedback-widget
v0.2.6
Published
A draggable feedback widget with screenshot capture for React applications
Readme
Feedback Widget
A customizable feedback widget for Next.js applications with element selection capability. Users can submit feedback with optional visual element references, making it easy to provide context about specific UI components.
Features
- Shadow DOM isolation - Widget styles don't conflict with your application
- Draggable positioning - Users can reposition the widget and position persists across sessions
- Element selection mode - Users can visually select up to 5 UI elements to reference in their feedback
- JWT-based user detection - Automatically extracts user ID from JWT tokens in cookies or localStorage
- Supabase backend - Feedback is stored in a Supabase database with Row Level Security
Installation
git clone <repository-url>
cd feedback-widget
npm installHosted Widget (Central Script)
Goal: one hosted widget.js file with instant rollouts.
Build the script:
npm run build:widgetDeploy the app to Vercel. It will serve public/widget.js at:
https://feedback-widget-alpha-ten.vercel.app/widget.jsThe widget posts to /api/feedback and /api/screenshot on the same domain.
Remote config endpoint (optional):
https://feedback-widget-alpha-ten.vercel.app/api/config?env=alpha&app=my-appConfig env vars (optional):
WIDGET_KILL_SWITCH=true
WIDGET_API_BASE_URL=https://feedback-widget-alpha-ten.vercel.app
WIDGET_VERSION=0.1.0
WIDGET_FLAGS={"screenshots":true}Host App Quickstart (Copy/Paste)
Drop-in usage (Next.js App Router):
import Script from "next/script";
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Script
src="https://feedback-widget-alpha-ten.vercel.app/widget.js"
strategy="afterInteractive"
data-app-id="my-app"
data-position="bottom-left"
/>
</body>
</html>
);
}Plain HTML (any app):
<script
src="https://feedback-widget-alpha-ten.vercel.app/widget.js"
data-app-id="my-app"
data-position="bottom-left"
></script>Minimum required: data-app-id.
Optional data attributes:
data-position(bottom-right,bottom-left,top-right,top-left)data-env(alpha,beta,dev,stable)data-api-base(override API base URL)
Optional: add data-env="alpha" (or beta, dev, stable) to enable remote config.
Manual control (optional):
window.FeedbackWidget?.init({ appId: "my-app" });
window.FeedbackWidget?.mount();CSP note (only if strict):
- Allow
script-srcandconnect-srctohttps://feedback-widget-alpha-ten.vercel.app - Allow
img-srcblob:(screenshot previews) - Allow
style-src 'unsafe-inline'(Shadow DOM styles are injected)
Environment Setup
Create a .env.local file in the project root with your Supabase credentials:
cp .env.example .env.localEdit .env.local with your Supabase project details:
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-service-role-keyYou can find these values in your Supabase project dashboard under Settings > API.
Supabase Setup
1. Create a Supabase Project
If you don't have one already, create a project at supabase.com.
2. Run the Database Migration
The migration file is located at supabase/migrations/001_feedback_table.sql. You can run it in one of two ways:
Option A: Using the Supabase Dashboard
- Go to your Supabase project dashboard
- Navigate to SQL Editor
- Copy the contents of
supabase/migrations/001_feedback_table.sql - Paste and run the SQL
Option B: Using the Supabase CLI
# Install Supabase CLI if not already installed
npm install -g supabase
# Link your project
supabase link --project-ref your-project-ref
# Run migrations
supabase db pushMigration Details
The migration creates a feedback table with:
| Column | Type | Description |
|--------|------|-------------|
| id | UUID | Primary key |
| app_id | TEXT | Application identifier (required) |
| type | TEXT | Feedback type (Bug, Feature, General) |
| message | TEXT | Feedback message (required) |
| elements | JSONB | Selected element data (optional) |
| metadata | JSONB | Auto-captured metadata (URL, userAgent, timestamp, userId) |
| created_at | TIMESTAMPTZ | Submission timestamp |
Row Level Security policies:
- Public INSERT: Anyone can submit feedback
- Authenticated SELECT: Only authenticated users can read feedback
Configuration Options
Initialize the widget before rendering:
import { FeedbackWidget } from '@/components/FeedbackWidget';
// Initialize with required appId
FeedbackWidget.init({
appId: 'your-app-id',
});Configuration Reference
| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| appId | string | Yes | - | Unique identifier for your application |
| position | string | No | 'bottom-right' | Initial widget position |
| jwtConfig | object | No | See below | JWT detection settings |
| env | string | No | - | Environment label (alpha, beta, dev, stable) |
| apiBaseUrl | string | No | Script origin | Base URL for the hosted API |
Position Options
'bottom-right'(default)'bottom-left''top-right''top-left'
JWT Configuration
Customize how the widget detects user identity:
FeedbackWidget.init({
appId: 'your-app-id',
jwtConfig: {
cookieKeys: ['my_auth_token', 'session_token'],
localStorageKeys: ['auth.token', 'user_session'],
userIdClaim: 'user_id', // JWT claim containing the user ID
},
});Default JWT detection keys:
- Cookies:
token,jwt,access_token,auth_token,sb-access-token - localStorage:
token,jwt,access_token,auth_token,supabase.auth.token - User ID claim:
sub(with fallback touser_id)
Example Integration
'use client';
import { FeedbackWidget } from '@/components/FeedbackWidget';
import { useSyncExternalStore } from 'react';
// Initialize before rendering
FeedbackWidget.init({
appId: 'my-app',
position: 'bottom-right',
jwtConfig: {
cookieKeys: ['my_token'],
userIdClaim: 'user_id',
},
});
// Hook to ensure client-side rendering
function useIsClient() {
return useSyncExternalStore(
() => () => {},
() => true,
() => false
);
}
export default function MyPage() {
const isClient = useIsClient();
return (
<div>
{/* Your page content */}
{isClient && <FeedbackWidget />}
</div>
);
}Development
# Start development server
npm run dev
# Run unit tests
npm run test
# Run E2E tests
npm run test:e2e
# Build for production
npm run build
# Run linting
npm run lintVisit the demo page at http://localhost:3000/demo to test the widget.
Project Structure
src/
├── app/
│ ├── demo/ # Demo page for testing
│ └── ...
├── components/
│ └── FeedbackWidget/
│ ├── index.tsx # Main widget component
│ ├── FeedbackForm.tsx # Form UI
│ ├── SelectionMode.tsx # Element selection overlay
│ ├── ElementList.tsx # Selected elements display
│ ├── styles.ts # Shadow DOM styles
│ └── utils/
│ ├── config.ts # Configuration management
│ ├── storage.ts # localStorage helpers
│ ├── positionStore.ts # Position state
│ ├── jwt.ts # JWT detection
│ └── elements.ts # Element detection
└── lib/
└── supabase.ts # Supabase client
supabase/
└── migrations/
└── 001_feedback_table.sql
tests/
├── unit/ # Vitest unit tests
└── e2e/ # Playwright E2E testsLicense
MIT
