react-linear-feedback 0.1.0 β†’ 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +91 -27
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,31 +1,42 @@
1
1
  # react-linear-feedback
2
2
 
3
- A drop-in feedback widget for any React app. A trusted user opens it with `?feedback`, **drags a box** over the page, picks **Bug / Improvement**, writes a note β€” and it captures an **annotated screenshot** and opens a **Linear issue**.
3
+ [![npm version](https://img.shields.io/npm/v/react-linear-feedback.svg)](https://www.npmjs.com/package/react-linear-feedback) [![license](https://img.shields.io/npm/l/react-linear-feedback.svg)](./LICENSE)
4
4
 
5
- - πŸͺΆ **Framework-agnostic** β€” Next.js, Vite, Remix, CRA… (no `next` dependency)
6
- - 🎨 **Self-contained styles** β€” injected at runtime, themeable with one prop; **no CSS import, no Tailwind, no design system** required
5
+ **A drop-in React feedback widget that turns a drawn box + note into a Linear issue with an annotated screenshot.**
6
+
7
+ A trusted user opens it with `?feedback`, **drags a box** over the page, picks a type (**Bug / Improvement**), writes a note β€” and it captures a screenshot and files a Linear issue. Framework-agnostic, self-contained styling, no design system required.
8
+
9
+ <!-- Demo: record a short GIF and drop it here β†’ ![demo](docs/demo.gif) -->
10
+ > _Draw a box anywhere β†’ type a note β†’ it lands in Linear with the annotated screenshot and page context._
11
+
12
+ - πŸͺΆ **Themeable in one prop** β€” no CSS import, no Tailwind, no design system; styles are injected at runtime
13
+ - 🌍 **Works in any React app** β€” Next.js, Vite, Remix, CRA… (no `next` dependency; `"use client"` is built in)
7
14
  - πŸ–ΌοΈ **Annotated screenshots** via [`modern-screenshot`](https://github.com/qq15725/modern-screenshot) (handles Tailwind v4 / `oklch()`)
8
- - 🏷️ **Labels resolved by name** at request time (recoloring/recreating a label in Linear won't break it), applied best-effort
9
- - πŸ”Œ Tiny server core + **Next.js & Node/Express adapters**
15
+ - 🏷️ **Labels by name, self-healing** β€” resolved at request time, so recoloring/recreating a label in Linear won't break it; applied best-effort
16
+ - πŸ”Œ **Tiny server core** + Next.js & Node/Express adapters
17
+
18
+ ## Contents
19
+
20
+ - [Install](#install) Β· [Quick start (Next.js)](#quick-start-nextjs) Β· [Vite / any backend](#use-with-vite--any-react-app) Β· [Linear setup](#linear-setup) Β· [Configuration](#configuration) Β· [Theming](#theming) Β· [Custom types](#custom-types) Β· [Security](#security) Β· [Troubleshooting](#troubleshooting) Β· [License](#license)
10
21
 
11
22
  ## Install
12
23
 
13
24
  ```bash
14
25
  npm i react-linear-feedback
15
- # the server entry needs the Linear SDK:
26
+ # the server entry needs the Linear SDK (optional peer):
16
27
  npm i @linear/sdk
17
28
  ```
18
29
 
19
- `react` / `react-dom` are peer dependencies. `@linear/sdk` is an optional peer β€” only needed where you run the server handler.
30
+ `react` / `react-dom` are peer dependencies. `@linear/sdk` is an **optional** peer β€” only needed wherever you run the server handler. Do [Linear setup](#linear-setup) first to get your API key, team, and labels.
20
31
 
21
- ## Quick start β€” Next.js (App Router)
32
+ ## Quick start (Next.js, App Router)
22
33
 
23
34
  **1. Server route** β€” `app/api/feedback/route.ts`:
24
35
 
25
36
  ```ts
26
37
  import { createNextRoute, cookieGate } from "react-linear-feedback/server";
27
38
 
28
- export const runtime = "nodejs"; // needs Buffer + the Linear SDK
39
+ export const runtime = "nodejs"; // REQUIRED β€” uses Buffer to process the screenshot; Edge is not supported
29
40
 
30
41
  export const POST = createNextRoute({
31
42
  apiKey: process.env.LINEAR_API_KEY!,
@@ -34,7 +45,7 @@ export const POST = createNextRoute({
34
45
  });
35
46
  ```
36
47
 
37
- **2. Mount the widget** once (e.g. in `app/layout.tsx`):
48
+ **2. Mount the widget once** (e.g. in `app/layout.tsx`):
38
49
 
39
50
  ```tsx
40
51
  import { FeedbackGate } from "react-linear-feedback/react";
@@ -49,9 +60,11 @@ export default function RootLayout({ children }) {
49
60
  }
50
61
  ```
51
62
 
52
- Visit any page with `?feedback` to turn it on (a cookie remembers it). `?feedback=0` turns it off.
63
+ No `"use client"` needed β€” the package ships it, so you can mount `<FeedbackGate>` straight from a Server Component layout.
64
+
65
+ **3. Turn it on.** The widget is **hidden by default**. Visit any page with **`?feedback`** (or `?feedback=1`) to enable it; a cookie remembers the choice for 90 days. `?feedback=0` turns it off.
53
66
 
54
- ## Quick start β€” Vite / SPA (separate backend)
67
+ ## Use with Vite / any React app
55
68
 
56
69
  Mount the widget and point it at your backend:
57
70
 
@@ -65,42 +78,76 @@ Run the handler on any Node server (Express shown):
65
78
 
66
79
  ```ts
67
80
  import express from "express";
68
- import { createNodeHandler } from "react-linear-feedback/server";
81
+ import cors from "cors";
82
+ import { createNodeHandler, cookieGate } from "react-linear-feedback/server";
69
83
 
70
84
  const app = express();
85
+ // If the app and API are on different origins, CORS is required or the fetch is blocked:
86
+ app.use("/feedback", cors({ origin: "https://your-site.com", credentials: true }));
71
87
  app.post("/feedback", createNodeHandler({
72
88
  apiKey: process.env.LINEAR_API_KEY!,
73
89
  teamId: process.env.LINEAR_TEAM_ID!,
90
+ authorize: cookieGate("wh_feedback"), // optional β€” omit to leave the endpoint open
74
91
  }));
75
92
  app.listen(8787);
76
93
  ```
77
94
 
78
- (Enable CORS for your site origin if the API is on a different host.)
95
+ The handler reads the raw body itself, so it works with or without `express.json()`.
96
+
97
+ ## Linear setup
98
+
99
+ You need three things from Linear:
100
+
101
+ 1. **API key** β€” Linear β†’ **Settings β†’ Security & access β†’ Personal API keys** β†’ create one. Keep it server-side as `LINEAR_API_KEY`.
102
+ 2. **Team UUID** β€” the team issues land in. Find it via the Linear API (`viewer`/`teams` query) or your team settings; set it as `LINEAR_TEAM_ID`.
103
+ 3. **Labels** β€” each feedback type is matched to a Linear label **by name** (case-insensitive). With the defaults, make sure labels named **`bug`** and **`improvement`** exist in that team (or workspace). Missing labels don't fail the issue β€” it's just created without one. Remap names with the server [`labels`](#configuration) option.
104
+
105
+ ```bash
106
+ # .env.local
107
+ LINEAR_API_KEY=lin_api_...
108
+ LINEAR_TEAM_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
109
+ ```
79
110
 
80
111
  ## Configuration
81
112
 
82
- **`<FeedbackGate>` / `<FeedbackWidget>` props**
113
+ ### Widget props (`<FeedbackGate>` / `<FeedbackWidget>`)
114
+
115
+ Most apps only set `brandColor` and maybe `endpoint`.
83
116
 
84
117
  | Prop | Default | Description |
85
118
  | --- | --- | --- |
86
119
  | `endpoint` | `/api/feedback` | Where the widget POSTs |
87
- | `brandColor` | indigo | FAB / active / focus color (sets `--lfb-brand`) |
120
+ | `brandColor` | `#6366f1` | FAB / active / focus color (sets the `--lfb-brand` CSS var) |
88
121
  | `position` | `bottom-right` | `bottom-right` \| `bottom-left` \| `top-right` \| `top-left` |
89
122
  | `types` | Bug, Improvement | `{ id, label, color, icon? }[]` shown in the composer |
90
- | `nameRequired` | `true` | Ask for a reporter name before the first submission |
123
+ | `nameRequired` | `true` | Ask for a reporter name before the first submission (saved to localStorage, included in the issue) |
124
+ | `nameStorageKey` | `wh_feedback_name` | localStorage key for the remembered name |
91
125
  | `fabLabel` | `Give feedback` | Floating button text |
92
- | `urlParam` | `feedback` | Toggle param (gate only) |
93
- | `cookieName` | `wh_feedback` | Enabled-state cookie (gate only) |
126
+ | `urlParam` ΒΉ | `feedback` | Toggle param |
127
+ | `cookieName` ΒΉ | `wh_feedback` | Enabled-state cookie name |
128
+ | `cookieValue` ΒΉ | `1` | Cookie value when enabled |
129
+ | `cookieMaxAgeSeconds` ΒΉ | `7776000` (90d) | Cookie lifetime |
94
130
 
95
- **Server config** (`createNextRoute` / `createNodeHandler` / `createFeedbackIssue`)
131
+ ΒΉ `<FeedbackGate>` only.
132
+
133
+ ### Server config (`createNextRoute` / `createNodeHandler` / `createFeedbackIssue`)
96
134
 
97
135
  | Field | Description |
98
136
  | --- | --- |
99
137
  | `apiKey` | Linear personal API key (**server-side only**) |
100
138
  | `teamId` | Target team UUID |
101
- | `labels` | Optional `{ [typeId]: labelName }` map. Default: the type id is the label name (so `bug` β†’ label `bug`) |
102
- | `allowedOrigin` | Optional single-origin allowlist |
103
- | `authorize(req)` | Optional gate; return `false` to reject (`cookieGate` provided) |
139
+ | `labels` | Optional `{ [typeId]: labelName }` map. Default: the type id **is** the label name (so type `bug` β†’ label `bug`) |
140
+ | `allowedOrigin` | Optional single-origin allowlist (adapters) |
141
+ | `authorize(req)` | Optional gate; return `false` to reject. `cookieGate(name, value="1")` is provided |
142
+
143
+ Remap type ids to differently-named Linear labels:
144
+
145
+ ```ts
146
+ createNextRoute({
147
+ apiKey, teamId,
148
+ labels: { bug: "bug", idea: "feature-request" },
149
+ });
150
+ ```
104
151
 
105
152
  ## Theming
106
153
 
@@ -118,14 +165,31 @@ Set `brandColor`, or override any CSS variable on `.lfb-doc-layer, .lfb-fixed-la
118
165
  />
119
166
  ```
120
167
 
121
- Each `type.id` is matched to a Linear label of the same name (or remap via the server `labels` config).
168
+ Each `type.id` is matched to a Linear label of the same name. If your label is named differently, map it on the server via `labels` (above). Built-in `icon` values: `"bug"`, `"improvement"`, `"dot"`.
122
169
 
123
170
  ## Security
124
171
 
125
- The endpoint **creates Linear issues**, so it's effectively write access to your tracker. It's gated only by what you wire up β€” use `authorize` (e.g. `cookieGate`, a session check) and/or `allowedOrigin`, and add rate limiting if the page is public. Your `LINEAR_API_KEY` stays server-side; the package never ships it to the browser.
172
+ ⚠️ **The endpoint creates Linear issues**, so it's effectively write access to your tracker, and it's **open by default**. Before shipping on a public page:
126
173
 
127
- > Linear asset URLs (the screenshots) are **private** β€” they render inside the issue but a fresh signed URL is needed to fetch them elsewhere.
174
+ - **Gate it** with `authorize` (e.g. `cookieGate`, or your own session check).
175
+ - **Restrict origin** with `allowedOrigin: "https://your-site.com"`.
176
+ - **Rate-limit** if the page is public (e.g. per-IP).
177
+
178
+ Your `LINEAR_API_KEY` stays server-side; the package never ships it to the browser. Screenshots are uploaded to Linear's **private** asset storage β€” they render inside the issue, but the URL needs a fresh signed token to fetch elsewhere (it can't be hot-linked). If a screenshot upload fails, the issue is still created without it.
179
+
180
+ ## Troubleshooting
181
+
182
+ Submissions never throw in the UI β€” failures are logged to the browser console with a `[feedback]` prefix, and server errors return a JSON `message`. If issues aren't being created, check:
183
+
184
+ - `endpoint` points at your route, and `LINEAR_API_KEY` / `LINEAR_TEAM_ID` are set.
185
+ - **CORS** is configured when the app and API are on different origins.
186
+ - `runtime = "nodejs"` is set on the Next.js route (Edge has no `Buffer`).
187
+ - The expected Linear **labels exist** (otherwise the issue is created without a label, with a warning).
128
188
 
129
189
  ## License
130
190
 
131
- MIT
191
+ MIT β€” see [LICENSE](./LICENSE).
192
+
193
+ ## Contributing
194
+
195
+ Issues and PRs welcome at [github.com/oliverodgaardwastehero/react-linear-feedback](https://github.com/oliverodgaardwastehero/react-linear-feedback).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-linear-feedback",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Drop-in React feedback widget: draw a box, write a note, and it captures a screenshot and opens a Linear issue. Framework-agnostic, self-contained styles, zero design-system dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Oliver Odgaard",