edsger-feedback 0.1.0
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 +87 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.js +94 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.js +8 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# edsger-feedback
|
|
2
|
+
|
|
3
|
+
Submit user feedback to [Edsger](https://edsger.ai) products programmatically.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install edsger-feedback
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { submitFeedback } from 'edsger-feedback'
|
|
15
|
+
|
|
16
|
+
const result = await submitFeedback({
|
|
17
|
+
slug: 'spoke-translator',
|
|
18
|
+
title: 'Add dark mode',
|
|
19
|
+
description: 'It would be great to have a dark mode option.',
|
|
20
|
+
category: 'feature_request',
|
|
21
|
+
reporterEmail: 'user@example.com',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
console.log('Feedback submitted:', result.id)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API
|
|
28
|
+
|
|
29
|
+
### `submitFeedback(options)`
|
|
30
|
+
|
|
31
|
+
Submit feedback for a product. Returns `Promise<FeedbackResult>`.
|
|
32
|
+
|
|
33
|
+
| Option | Type | Required | Description |
|
|
34
|
+
|--------|------|----------|-------------|
|
|
35
|
+
| `slug` | `string` | Yes | Product slug (e.g. `"spoke-translator"`) |
|
|
36
|
+
| `title` | `string` | Yes | Feedback title (max 200 characters) |
|
|
37
|
+
| `description` | `string` | Yes | Feedback description (max 5000 characters) |
|
|
38
|
+
| `category` | `FeedbackCategory` | No | One of `bug`, `feature_request`, `improvement`, `question`, `other`. Defaults to `other` |
|
|
39
|
+
| `reporterName` | `string` | No | Reporter's name |
|
|
40
|
+
| `reporterEmail` | `string` | No | Reporter's email |
|
|
41
|
+
|
|
42
|
+
**Returns:**
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
{ id: string; createdAt: string }
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `getFeedbackConfig(slug)`
|
|
49
|
+
|
|
50
|
+
Get the feedback page configuration for a product. Returns `Promise<FeedbackConfig>`.
|
|
51
|
+
|
|
52
|
+
**Returns:**
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
{
|
|
56
|
+
productId: string
|
|
57
|
+
productName: string
|
|
58
|
+
customTitle: string | null
|
|
59
|
+
welcomeMessage: string | null
|
|
60
|
+
slug: string
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Error Handling
|
|
65
|
+
|
|
66
|
+
All errors are thrown as `FeedbackError` with a `statusCode` property.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { submitFeedback, FeedbackError } from 'edsger-feedback'
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await submitFeedback({ ... })
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (err instanceof FeedbackError) {
|
|
75
|
+
console.error(err.message, err.statusCode)
|
|
76
|
+
// e.g. "Too many submissions..." 429
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Requirements
|
|
82
|
+
|
|
83
|
+
- Node.js >= 18 (uses native `fetch`)
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
ISC
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SubmitFeedbackOptions, FeedbackResult, FeedbackConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Get the feedback page config for a product slug.
|
|
4
|
+
* Useful for checking if feedback collection is enabled.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getFeedbackConfig(slug: string): Promise<FeedbackConfig>;
|
|
7
|
+
/**
|
|
8
|
+
* Submit feedback for a product.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { submitFeedback } from 'edsger-feedback'
|
|
13
|
+
*
|
|
14
|
+
* const result = await submitFeedback({
|
|
15
|
+
* slug: 'spoke-translator',
|
|
16
|
+
* title: 'Add dark mode',
|
|
17
|
+
* description: 'It would be great to have a dark mode option.',
|
|
18
|
+
* category: 'feature_request',
|
|
19
|
+
* reporterEmail: 'user@example.com',
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* console.log('Feedback submitted:', result.id)
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function submitFeedback(options: SubmitFeedbackOptions): Promise<FeedbackResult>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { FeedbackError } from './types.js';
|
|
2
|
+
const ENDPOINT = 'https://ktkogvogdaffjmvrewiu.supabase.co/functions/v1/public-feedback';
|
|
3
|
+
const VALID_CATEGORIES = [
|
|
4
|
+
'bug',
|
|
5
|
+
'feature_request',
|
|
6
|
+
'improvement',
|
|
7
|
+
'question',
|
|
8
|
+
'other',
|
|
9
|
+
];
|
|
10
|
+
/**
|
|
11
|
+
* Get the feedback page config for a product slug.
|
|
12
|
+
* Useful for checking if feedback collection is enabled.
|
|
13
|
+
*/
|
|
14
|
+
export async function getFeedbackConfig(slug) {
|
|
15
|
+
const res = await fetch(`${ENDPOINT}?slug=${encodeURIComponent(slug)}`);
|
|
16
|
+
const data = await res.json();
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
throw new FeedbackError(data.error || 'Failed to load feedback config', res.status);
|
|
19
|
+
}
|
|
20
|
+
const config = data.config;
|
|
21
|
+
return {
|
|
22
|
+
productId: config.product_id,
|
|
23
|
+
productName: config.product_name,
|
|
24
|
+
customTitle: config.custom_title,
|
|
25
|
+
welcomeMessage: config.welcome_message,
|
|
26
|
+
slug: config.slug,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Submit feedback for a product.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { submitFeedback } from 'edsger-feedback'
|
|
35
|
+
*
|
|
36
|
+
* const result = await submitFeedback({
|
|
37
|
+
* slug: 'spoke-translator',
|
|
38
|
+
* title: 'Add dark mode',
|
|
39
|
+
* description: 'It would be great to have a dark mode option.',
|
|
40
|
+
* category: 'feature_request',
|
|
41
|
+
* reporterEmail: 'user@example.com',
|
|
42
|
+
* })
|
|
43
|
+
*
|
|
44
|
+
* console.log('Feedback submitted:', result.id)
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export async function submitFeedback(options) {
|
|
48
|
+
validate(options);
|
|
49
|
+
const res = await fetch(ENDPOINT, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/json' },
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
slug: options.slug,
|
|
54
|
+
category: options.category || 'other',
|
|
55
|
+
title: options.title.trim(),
|
|
56
|
+
description: options.description.trim(),
|
|
57
|
+
reporter_name: options.reporterName?.trim() || undefined,
|
|
58
|
+
reporter_email: options.reporterEmail?.trim() || undefined,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
const data = await res.json();
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
throw new FeedbackError(data.error || 'Failed to submit feedback', res.status);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
id: data.feedback.id,
|
|
67
|
+
createdAt: data.feedback.created_at,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function validate(options) {
|
|
71
|
+
if (!options.slug) {
|
|
72
|
+
throw new FeedbackError('slug is required', 400);
|
|
73
|
+
}
|
|
74
|
+
if (!options.title?.trim()) {
|
|
75
|
+
throw new FeedbackError('title is required', 400);
|
|
76
|
+
}
|
|
77
|
+
if (options.title.length > 200) {
|
|
78
|
+
throw new FeedbackError('title must be 200 characters or less', 400);
|
|
79
|
+
}
|
|
80
|
+
if (!options.description?.trim()) {
|
|
81
|
+
throw new FeedbackError('description is required', 400);
|
|
82
|
+
}
|
|
83
|
+
if (options.description.length > 5000) {
|
|
84
|
+
throw new FeedbackError('description must be 5000 characters or less', 400);
|
|
85
|
+
}
|
|
86
|
+
if (options.category &&
|
|
87
|
+
!VALID_CATEGORIES.includes(options.category)) {
|
|
88
|
+
throw new FeedbackError(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(', ')}`, 400);
|
|
89
|
+
}
|
|
90
|
+
if (options.reporterEmail &&
|
|
91
|
+
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(options.reporterEmail)) {
|
|
92
|
+
throw new FeedbackError('Invalid email format', 400);
|
|
93
|
+
}
|
|
94
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type FeedbackCategory = 'bug' | 'feature_request' | 'improvement' | 'question' | 'other';
|
|
2
|
+
export interface SubmitFeedbackOptions {
|
|
3
|
+
/** Product slug, e.g. "spoke-translator" */
|
|
4
|
+
slug: string;
|
|
5
|
+
/** Feedback title (required, max 200 characters) */
|
|
6
|
+
title: string;
|
|
7
|
+
/** Feedback description (required, max 5000 characters) */
|
|
8
|
+
description: string;
|
|
9
|
+
/** Feedback category (defaults to "other") */
|
|
10
|
+
category?: FeedbackCategory;
|
|
11
|
+
/** Reporter name (optional) */
|
|
12
|
+
reporterName?: string;
|
|
13
|
+
/** Reporter email (optional, used for rate limiting) */
|
|
14
|
+
reporterEmail?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FeedbackResult {
|
|
17
|
+
id: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
}
|
|
20
|
+
export interface FeedbackConfig {
|
|
21
|
+
productId: string;
|
|
22
|
+
productName: string;
|
|
23
|
+
customTitle: string | null;
|
|
24
|
+
welcomeMessage: string | null;
|
|
25
|
+
slug: string;
|
|
26
|
+
}
|
|
27
|
+
export declare class FeedbackError extends Error {
|
|
28
|
+
readonly statusCode: number;
|
|
29
|
+
constructor(message: string, statusCode: number);
|
|
30
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "edsger-feedback",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"edsger",
|
|
23
|
+
"feedback",
|
|
24
|
+
"user-feedback",
|
|
25
|
+
"product-feedback"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"description": "Submit user feedback to Edsger products programmatically",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/stevenzg/edsger.git",
|
|
33
|
+
"directory": "packages/feedback"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"typescript": "^5.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|