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.
- package/README.md +91 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,31 +1,42 @@
|
|
|
1
1
|
# react-linear-feedback
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/react-linear-feedback) [](./LICENSE)
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
|
|
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 β  -->
|
|
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
|
|
9
|
-
- π Tiny server core +
|
|
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
|
|
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
|
|
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"; //
|
|
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
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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` |
|
|
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
|
|
93
|
-
| `cookieName` | `wh_feedback` | Enabled-state cookie
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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",
|