npm package: @goodcraft/feedback-widget-nextjs
Description: Next.js server utilities for the feedback widget (API routes, Linear integration)
Author: Goodcraft
Latest version: 1.2.0

# @goodcraft/feedback-widget-nextjs

Server-side handler for Next.js API routes. Validates feedback submissions, uploads screenshots to Bunny CDN, and creates Linear issues.

## Install

```bash
pnpm add @goodcraft/feedback-widget-nextjs
```

Peer dependency: `next >= 13`

## Quick Start

```ts
// app/api/feedback/route.ts
import { NextResponse } from 'next/server';
import {
  createFeedbackHandler,
  validateSubmission,
  type FeedbackSubmission,
} from '@goodcraft/feedback-widget-nextjs';

const handleFeedback = createFeedbackHandler({
  linear: {
    apiKey: process.env.LINEAR_API_KEY!,
    teamId: process.env.LINEAR_TEAM_ID!,
  },
});

export async function POST(request: Request) {
  const body = await request.json();

  if (!validateSubmission(body)) {
    return NextResponse.json({ success: false, message: 'Invalid submission' }, { status: 400 });
  }

  const result = await handleFeedback(body as FeedbackSubmission);
  return NextResponse.json(result, { status: result.success ? 200 : 500 });
}
```

## `createFeedbackHandler(config)`

Creates a handler function that processes feedback submissions. Call it once at module scope, then use the returned function in your route handler.

```ts
const handleFeedback = createFeedbackHandler(config);
const result = await handleFeedback(submission);
```

### `FeedbackHandlerConfig`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `linear` | `LinearConfig` | Yes | Linear API configuration |
| `bunny` | `BunnyConfig` | No | Bunny CDN configuration for screenshot uploads |
| `onBeforeCreate` | `(submission, issueInput) => issueInput` | No | Modify the Linear issue before creation |
| `onAfterCreate` | `(submission, issueUrl, issueId) => void` | No | Callback after successful issue creation |

### `LinearConfig`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `apiKey` | `string` | Yes | Linear API key |
| `teamId` | `string` | Yes | Team ID for issue creation |
| `bugLabelId` | `string` | No | Label applied to bug reports |
| `featureLabelId` | `string` | No | Label applied to feature requests |
| `widgetLabelId` | `string` | No | Label applied to all widget submissions |
| `defaultProjectId` | `string` | No | Default project ID |
| `projectMapping` | `Record<string, string>` | No | Map of `projectSlug` to Linear project IDs |

### `BunnyConfig`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `apiKey` | `string` | Yes | Bunny.net storage API key |
| `storageZone` | `string` | Yes | Storage zone name |
| `cdnHostname` | `string` | Yes | CDN hostname for public URLs |

## `validateSubmission(body)`

Type guard that checks whether a request body is a valid `FeedbackSubmission`. Returns `true` if:

- `type` is `'bug'` or `'feature'`
- `title` is a non-empty string
- `projectSlug` is a string
- `systemInfo` is an object
- `consoleErrors` is an array

```ts
if (!validateSubmission(body)) {
  return NextResponse.json({ success: false, message: 'Invalid' }, { status: 400 });
}
```

## Full Configuration Example

```ts
const handleFeedback = createFeedbackHandler({
  linear: {
    apiKey: process.env.LINEAR_API_KEY!,
    teamId: process.env.LINEAR_TEAM_ID!,
    bugLabelId: process.env.LINEAR_BUG_LABEL_ID,
    featureLabelId: process.env.LINEAR_FEATURE_LABEL_ID,
    widgetLabelId: process.env.LINEAR_WIDGET_LABEL_ID,
    defaultProjectId: process.env.LINEAR_PROJECT_ID,
    projectMapping: {
      'app': 'linear-project-id-1',
      'marketing': 'linear-project-id-2',
    },
  },
  bunny: {
    apiKey: process.env.BUNNY_API_KEY!,
    storageZone: process.env.BUNNY_STORAGE_ZONE!,
    cdnHostname: process.env.BUNNY_CDN_HOSTNAME!,
  },
  onBeforeCreate: (submission, issueInput) => ({
    ...issueInput,
    priority: submission.type === 'bug' ? 2 : 4,
  }),
  onAfterCreate: async (submission, issueUrl) => {
    console.log(`Created issue: ${issueUrl}`);
  },
});
```

## Exported Utilities

| Export | Description |
|--------|-------------|
| `createFeedbackHandler` | Creates the main handler function |
| `validateSubmission` | Type guard for request body validation |
| `createLinearIssue` | Low-level Linear issue creation |
| `buildIssueDescription` | Builds markdown description from submission data |
| `uploadToBunny` | Uploads a base64 image to Bunny CDN |

## Environment Variables

| Variable | Required | Description |
|----------|----------|-------------|
| `LINEAR_API_KEY` | Yes | Linear API key |
| `LINEAR_TEAM_ID` | Yes | Linear team ID |
| `LINEAR_BUG_LABEL_ID` | No | Label for bug reports |
| `LINEAR_FEATURE_LABEL_ID` | No | Label for feature requests |
| `LINEAR_WIDGET_LABEL_ID` | No | Label for all submissions |
| `LINEAR_PROJECT_ID` | No | Default project ID |
| `BUNNY_API_KEY` | No | Bunny.net API key |
| `BUNNY_STORAGE_ZONE` | No | Bunny.net storage zone |
| `BUNNY_CDN_HOSTNAME` | No | Bunny.net CDN hostname |

## License

MIT