@cyguin/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/.github/workflows/publish.yml +27 -0
- package/LICENSE +7 -0
- package/README.md +136 -0
- package/dist/adapters/in-memory.cjs +6 -0
- package/dist/adapters/in-memory.d.cts +23 -0
- package/dist/adapters/in-memory.d.ts +23 -0
- package/dist/adapters/in-memory.js +6 -0
- package/dist/adapters/supabase.cjs +6 -0
- package/dist/adapters/supabase.d.cts +27 -0
- package/dist/adapters/supabase.d.ts +27 -0
- package/dist/adapters/supabase.js +6 -0
- package/dist/chunk-3CF2TRVK.js +202 -0
- package/dist/chunk-5Q4LNXS7.cjs +46 -0
- package/dist/chunk-CEOUAA46.cjs +202 -0
- package/dist/chunk-F4UO2IK4.cjs +74 -0
- package/dist/chunk-IZIGPLKR.js +74 -0
- package/dist/chunk-KXORPGTI.js +57 -0
- package/dist/chunk-NKCO24NR.js +46 -0
- package/dist/chunk-SRH4NGAR.cjs +57 -0
- package/dist/index.cjs +18 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +18 -0
- package/dist/react.cjs +6 -0
- package/dist/react.d.cts +7 -0
- package/dist/react.d.ts +7 -0
- package/dist/react.js +6 -0
- package/dist/server.cjs +6 -0
- package/dist/server.d.cts +20 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +6 -0
- package/dist/types-CMsn8uhD.d.cts +49 -0
- package/dist/types-CMsn8uhD.d.ts +49 -0
- package/package.json +66 -0
- package/src/adapters/in-memory.ts +58 -0
- package/src/adapters/index.ts +2 -0
- package/src/adapters/supabase.ts +88 -0
- package/src/app/api/feedback/[...cyguin]/route.ts +20 -0
- package/src/components/FeedbackWidget.tsx +215 -0
- package/src/components/index.ts +2 -0
- package/src/handlers/route.ts +100 -0
- package/src/index.ts +11 -0
- package/src/server.ts +1 -0
- package/src/types.ts +50 -0
- package/tsconfig.json +18 -0
- package/tsup.config.ts +16 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/components/FeedbackWidget.tsx
|
|
2
|
+
var _react = require('react');
|
|
3
|
+
var _jsxruntime = require('react/jsx-runtime');
|
|
4
|
+
var THUMBS_UP = "thumbs_up";
|
|
5
|
+
var THUMBS_DOWN = "thumbs_down";
|
|
6
|
+
function ThumbsUpIcon() {
|
|
7
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
8
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M7 10v12" }),
|
|
9
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" })
|
|
10
|
+
] });
|
|
11
|
+
}
|
|
12
|
+
function ThumbsDownIcon() {
|
|
13
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
14
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M17 14V2" }),
|
|
15
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z" })
|
|
16
|
+
] });
|
|
17
|
+
}
|
|
18
|
+
function StarIcon({ filled }) {
|
|
19
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: filled ? "currentColor" : "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" }) });
|
|
20
|
+
}
|
|
21
|
+
var lightTheme = {
|
|
22
|
+
"--cyguin-bg": "#ffffff",
|
|
23
|
+
"--cyguin-bg-subtle": "#f5f5f5",
|
|
24
|
+
"--cyguin-border": "#e5e5e5",
|
|
25
|
+
"--cyguin-border-focus": "#f5a800",
|
|
26
|
+
"--cyguin-fg": "#0a0a0a",
|
|
27
|
+
"--cyguin-fg-muted": "#888888",
|
|
28
|
+
"--cyguin-accent": "#f5a800",
|
|
29
|
+
"--cyguin-accent-dark": "#c47f00",
|
|
30
|
+
"--cyguin-accent-fg": "#0a0a0a",
|
|
31
|
+
"--cyguin-radius": "6px",
|
|
32
|
+
"--cyguin-shadow": "0 1px 4px rgba(0,0,0,0.08)"
|
|
33
|
+
};
|
|
34
|
+
var darkTheme = {
|
|
35
|
+
"--cyguin-bg": "#0a0a0a",
|
|
36
|
+
"--cyguin-bg-subtle": "#1a1a1a",
|
|
37
|
+
"--cyguin-border": "#2a2a2a",
|
|
38
|
+
"--cyguin-border-focus": "#f5a800",
|
|
39
|
+
"--cyguin-fg": "#f5f5f5",
|
|
40
|
+
"--cyguin-fg-muted": "#888888",
|
|
41
|
+
"--cyguin-accent": "#f5a800",
|
|
42
|
+
"--cyguin-accent-dark": "#c47f00",
|
|
43
|
+
"--cyguin-accent-fg": "#0a0a0a",
|
|
44
|
+
"--cyguin-radius": "6px",
|
|
45
|
+
"--cyguin-shadow": "0 1px 4px rgba(0,0,0,0.4)"
|
|
46
|
+
};
|
|
47
|
+
function FeedbackWidget({
|
|
48
|
+
type,
|
|
49
|
+
url: urlProp,
|
|
50
|
+
userId,
|
|
51
|
+
theme = "light",
|
|
52
|
+
className = "",
|
|
53
|
+
onSubmit,
|
|
54
|
+
maxStars = 5,
|
|
55
|
+
placeholder = "Share your feedback..."
|
|
56
|
+
}) {
|
|
57
|
+
const [submitted, setSubmitted] = _react.useState.call(void 0, false);
|
|
58
|
+
const [textValue, setTextValue] = _react.useState.call(void 0, "");
|
|
59
|
+
const [loading, setLoading] = _react.useState.call(void 0, false);
|
|
60
|
+
const getUrl = () => {
|
|
61
|
+
if (urlProp) return urlProp;
|
|
62
|
+
if (typeof window !== "undefined") return window.location.href;
|
|
63
|
+
return "";
|
|
64
|
+
};
|
|
65
|
+
const handleSubmit = _react.useCallback.call(void 0,
|
|
66
|
+
async (body) => {
|
|
67
|
+
setLoading(true);
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch("/api/feedback", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: { "Content-Type": "application/json" },
|
|
72
|
+
body: JSON.stringify({ type, body, url: getUrl(), userId })
|
|
73
|
+
});
|
|
74
|
+
const json = await res.json();
|
|
75
|
+
if (res.ok && json.data) {
|
|
76
|
+
setSubmitted(true);
|
|
77
|
+
_optionalChain([onSubmit, 'optionalCall', _2 => _2(json.data)]);
|
|
78
|
+
setTimeout(() => setSubmitted(false), 3e3);
|
|
79
|
+
}
|
|
80
|
+
} finally {
|
|
81
|
+
setLoading(false);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
[type, userId, onSubmit]
|
|
85
|
+
);
|
|
86
|
+
if (submitted) {
|
|
87
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
88
|
+
"div",
|
|
89
|
+
{
|
|
90
|
+
"data-theme": theme,
|
|
91
|
+
className,
|
|
92
|
+
style: { ...theme === "dark" ? darkTheme : lightTheme, padding: "12px", borderRadius: "var(--cyguin-radius)", background: "var(--cyguin-bg)", border: "1px solid var(--cyguin-border)", boxShadow: "var(--cyguin-shadow)", fontFamily: "system-ui, sans-serif", color: "var(--cyguin-fg)", textAlign: "center" },
|
|
93
|
+
children: "Thanks for your feedback!"
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (type === "thumbs") {
|
|
98
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
99
|
+
"div",
|
|
100
|
+
{
|
|
101
|
+
"data-theme": theme,
|
|
102
|
+
className,
|
|
103
|
+
style: { ...theme === "dark" ? darkTheme : lightTheme, display: "flex", gap: "8px", padding: "12px", borderRadius: "var(--cyguin-radius)", background: "var(--cyguin-bg)", border: "1px solid var(--cyguin-border)", boxShadow: "var(--cyguin-shadow)", fontFamily: "system-ui, sans-serif" },
|
|
104
|
+
children: [
|
|
105
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
106
|
+
"button",
|
|
107
|
+
{
|
|
108
|
+
onClick: () => handleSubmit(THUMBS_UP),
|
|
109
|
+
disabled: loading,
|
|
110
|
+
"aria-label": "Thumbs up",
|
|
111
|
+
style: { background: "none", border: "none", cursor: "pointer", color: "var(--cyguin-fg)", padding: "4px", borderRadius: "var(--cyguin-radius)", display: "flex", alignItems: "center", justifyContent: "center" },
|
|
112
|
+
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ThumbsUpIcon, {})
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
116
|
+
"button",
|
|
117
|
+
{
|
|
118
|
+
onClick: () => handleSubmit(THUMBS_DOWN),
|
|
119
|
+
disabled: loading,
|
|
120
|
+
"aria-label": "Thumbs down",
|
|
121
|
+
style: { background: "none", border: "none", cursor: "pointer", color: "var(--cyguin-fg)", padding: "4px", borderRadius: "var(--cyguin-radius)", display: "flex", alignItems: "center", justifyContent: "center" },
|
|
122
|
+
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ThumbsDownIcon, {})
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (type === "rating") {
|
|
130
|
+
const [selected, setSelected] = _react.useState.call(void 0, null);
|
|
131
|
+
const handleStarClick = (star) => {
|
|
132
|
+
setSelected(star);
|
|
133
|
+
handleSubmit(String(star));
|
|
134
|
+
};
|
|
135
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
136
|
+
"div",
|
|
137
|
+
{
|
|
138
|
+
"data-theme": theme,
|
|
139
|
+
className,
|
|
140
|
+
style: { ...theme === "dark" ? darkTheme : lightTheme, display: "flex", gap: "4px", padding: "12px", borderRadius: "var(--cyguin-radius)", background: "var(--cyguin-bg)", border: "1px solid var(--cyguin-border)", boxShadow: "var(--cyguin-shadow)", fontFamily: "system-ui, sans-serif" },
|
|
141
|
+
children: Array.from({ length: maxStars }, (_, i) => {
|
|
142
|
+
const star = i + 1;
|
|
143
|
+
const filled = selected !== null && star <= selected;
|
|
144
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
145
|
+
"button",
|
|
146
|
+
{
|
|
147
|
+
onClick: () => handleStarClick(star),
|
|
148
|
+
disabled: loading,
|
|
149
|
+
"aria-label": `${star} star${star > 1 ? "s" : ""}`,
|
|
150
|
+
style: { background: "none", border: "none", cursor: "pointer", color: filled ? "var(--cyguin-accent)" : "var(--cyguin-fg-muted)", padding: "2px", borderRadius: "var(--cyguin-radius)", display: "flex", alignItems: "center", transition: "color 0.15s" },
|
|
151
|
+
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, StarIcon, { filled })
|
|
152
|
+
},
|
|
153
|
+
star
|
|
154
|
+
);
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (type === "text") {
|
|
160
|
+
const handleTextSubmit = (e) => {
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
if (textValue.trim()) {
|
|
163
|
+
handleSubmit(textValue.trim());
|
|
164
|
+
setTextValue("");
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
168
|
+
"div",
|
|
169
|
+
{
|
|
170
|
+
"data-theme": theme,
|
|
171
|
+
className,
|
|
172
|
+
style: { ...theme === "dark" ? darkTheme : lightTheme, padding: "12px", borderRadius: "var(--cyguin-radius)", background: "var(--cyguin-bg)", border: "1px solid var(--cyguin-border)", boxShadow: "var(--cyguin-shadow)", fontFamily: "system-ui, sans-serif" },
|
|
173
|
+
children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "form", { onSubmit: handleTextSubmit, style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
|
|
174
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
175
|
+
"textarea",
|
|
176
|
+
{
|
|
177
|
+
value: textValue,
|
|
178
|
+
onChange: (e) => setTextValue(e.target.value),
|
|
179
|
+
placeholder,
|
|
180
|
+
rows: 3,
|
|
181
|
+
style: { width: "100%", padding: "8px", borderRadius: "var(--cyguin-radius)", border: "1px solid var(--cyguin-border)", background: "var(--cyguin-bg-subtle)", color: "var(--cyguin-fg)", fontSize: "14px", resize: "vertical", outline: "none", boxSizing: "border-box", fontFamily: "inherit" }
|
|
182
|
+
}
|
|
183
|
+
),
|
|
184
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
185
|
+
"button",
|
|
186
|
+
{
|
|
187
|
+
type: "submit",
|
|
188
|
+
disabled: loading || !textValue.trim(),
|
|
189
|
+
style: { alignSelf: "flex-end", padding: "6px 16px", borderRadius: "var(--cyguin-radius)", border: "none", background: "var(--cyguin-accent)", color: "var(--cyguin-accent-fg)", fontWeight: 600, fontSize: "14px", cursor: "pointer", opacity: loading || !textValue.trim() ? 0.5 : 1 },
|
|
190
|
+
children: loading ? "Sending..." : "Submit"
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
] })
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
exports.FeedbackWidget = FeedbackWidget;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/handlers/route.ts
|
|
2
|
+
var _server = require('next/server');
|
|
3
|
+
function parseJsonBody(req) {
|
|
4
|
+
return req.json();
|
|
5
|
+
}
|
|
6
|
+
function bearerToken(req, secret) {
|
|
7
|
+
const auth = _nullishCoalesce(req.headers.get("Authorization"), () => ( ""));
|
|
8
|
+
return auth === `Bearer ${secret}`;
|
|
9
|
+
}
|
|
10
|
+
function createFeedbackHandler({ adapter, secret }) {
|
|
11
|
+
return async function handler(req) {
|
|
12
|
+
const url = req.nextUrl.clone();
|
|
13
|
+
const pathname = url.pathname;
|
|
14
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
15
|
+
const cyguinIndex = segments.indexOf("cyguin");
|
|
16
|
+
if (cyguinIndex === -1) {
|
|
17
|
+
return _server.NextResponse.json({ error: "Invalid route" }, { status: 400 });
|
|
18
|
+
}
|
|
19
|
+
const remaining = segments.slice(cyguinIndex + 1);
|
|
20
|
+
try {
|
|
21
|
+
if (req.method === "GET") {
|
|
22
|
+
if (!bearerToken(req, secret)) {
|
|
23
|
+
return _server.NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
24
|
+
}
|
|
25
|
+
const urlParam = _nullishCoalesce(url.searchParams.get("url"), () => ( void 0));
|
|
26
|
+
const reviewedParam = url.searchParams.get("reviewed");
|
|
27
|
+
const limitParam = url.searchParams.get("limit");
|
|
28
|
+
const reviewed = reviewedParam !== null ? reviewedParam === "1" || reviewedParam === "true" : void 0;
|
|
29
|
+
const limit = limitParam !== null ? parseInt(limitParam, 10) : void 0;
|
|
30
|
+
const result = await adapter.list({ url: urlParam, reviewed, limit });
|
|
31
|
+
return _server.NextResponse.json({ data: result.data, total: result.total });
|
|
32
|
+
}
|
|
33
|
+
if (req.method === "POST") {
|
|
34
|
+
if (!bearerToken(req, secret)) {
|
|
35
|
+
return _server.NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
36
|
+
}
|
|
37
|
+
const body = await parseJsonBody(req);
|
|
38
|
+
if (!body.type || !body.body || !body.url) {
|
|
39
|
+
return _server.NextResponse.json(
|
|
40
|
+
{ error: "type, body, and url are required" },
|
|
41
|
+
{ status: 400 }
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const record = await adapter.create({
|
|
45
|
+
type: body.type,
|
|
46
|
+
body: body.body,
|
|
47
|
+
url: body.url,
|
|
48
|
+
userId: body.userId,
|
|
49
|
+
createdAt: Date.now()
|
|
50
|
+
});
|
|
51
|
+
return _server.NextResponse.json({ data: record }, { status: 201 });
|
|
52
|
+
}
|
|
53
|
+
if (req.method === "PATCH") {
|
|
54
|
+
if (!bearerToken(req, secret)) {
|
|
55
|
+
return _server.NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
56
|
+
}
|
|
57
|
+
if (remaining.length < 1) {
|
|
58
|
+
return _server.NextResponse.json({ error: "id is required" }, { status: 400 });
|
|
59
|
+
}
|
|
60
|
+
const id = remaining[0];
|
|
61
|
+
await adapter.setReviewed(id, true);
|
|
62
|
+
return _server.NextResponse.json({ data: { id, reviewed: true } });
|
|
63
|
+
}
|
|
64
|
+
return _server.NextResponse.json({ error: "Method not allowed" }, { status: 405 });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
67
|
+
return _server.NextResponse.json({ error: message }, { status: 500 });
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
exports.createFeedbackHandler = createFeedbackHandler;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/handlers/route.ts
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
function parseJsonBody(req) {
|
|
4
|
+
return req.json();
|
|
5
|
+
}
|
|
6
|
+
function bearerToken(req, secret) {
|
|
7
|
+
const auth = req.headers.get("Authorization") ?? "";
|
|
8
|
+
return auth === `Bearer ${secret}`;
|
|
9
|
+
}
|
|
10
|
+
function createFeedbackHandler({ adapter, secret }) {
|
|
11
|
+
return async function handler(req) {
|
|
12
|
+
const url = req.nextUrl.clone();
|
|
13
|
+
const pathname = url.pathname;
|
|
14
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
15
|
+
const cyguinIndex = segments.indexOf("cyguin");
|
|
16
|
+
if (cyguinIndex === -1) {
|
|
17
|
+
return NextResponse.json({ error: "Invalid route" }, { status: 400 });
|
|
18
|
+
}
|
|
19
|
+
const remaining = segments.slice(cyguinIndex + 1);
|
|
20
|
+
try {
|
|
21
|
+
if (req.method === "GET") {
|
|
22
|
+
if (!bearerToken(req, secret)) {
|
|
23
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
24
|
+
}
|
|
25
|
+
const urlParam = url.searchParams.get("url") ?? void 0;
|
|
26
|
+
const reviewedParam = url.searchParams.get("reviewed");
|
|
27
|
+
const limitParam = url.searchParams.get("limit");
|
|
28
|
+
const reviewed = reviewedParam !== null ? reviewedParam === "1" || reviewedParam === "true" : void 0;
|
|
29
|
+
const limit = limitParam !== null ? parseInt(limitParam, 10) : void 0;
|
|
30
|
+
const result = await adapter.list({ url: urlParam, reviewed, limit });
|
|
31
|
+
return NextResponse.json({ data: result.data, total: result.total });
|
|
32
|
+
}
|
|
33
|
+
if (req.method === "POST") {
|
|
34
|
+
if (!bearerToken(req, secret)) {
|
|
35
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
36
|
+
}
|
|
37
|
+
const body = await parseJsonBody(req);
|
|
38
|
+
if (!body.type || !body.body || !body.url) {
|
|
39
|
+
return NextResponse.json(
|
|
40
|
+
{ error: "type, body, and url are required" },
|
|
41
|
+
{ status: 400 }
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const record = await adapter.create({
|
|
45
|
+
type: body.type,
|
|
46
|
+
body: body.body,
|
|
47
|
+
url: body.url,
|
|
48
|
+
userId: body.userId,
|
|
49
|
+
createdAt: Date.now()
|
|
50
|
+
});
|
|
51
|
+
return NextResponse.json({ data: record }, { status: 201 });
|
|
52
|
+
}
|
|
53
|
+
if (req.method === "PATCH") {
|
|
54
|
+
if (!bearerToken(req, secret)) {
|
|
55
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
56
|
+
}
|
|
57
|
+
if (remaining.length < 1) {
|
|
58
|
+
return NextResponse.json({ error: "id is required" }, { status: 400 });
|
|
59
|
+
}
|
|
60
|
+
const id = remaining[0];
|
|
61
|
+
await adapter.setReviewed(id, true);
|
|
62
|
+
return NextResponse.json({ data: { id, reviewed: true } });
|
|
63
|
+
}
|
|
64
|
+
return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
67
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
createFeedbackHandler
|
|
74
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/adapters/supabase.ts
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
var SupabaseFeedbackAdapter = class {
|
|
4
|
+
constructor(client, tableName = "feedback") {
|
|
5
|
+
this.client = client;
|
|
6
|
+
this.tableName = tableName;
|
|
7
|
+
}
|
|
8
|
+
mapRow(row) {
|
|
9
|
+
return {
|
|
10
|
+
id: row.id,
|
|
11
|
+
userId: row.user_id,
|
|
12
|
+
type: row.type,
|
|
13
|
+
body: row.body,
|
|
14
|
+
url: row.url,
|
|
15
|
+
createdAt: row.created_at,
|
|
16
|
+
reviewed: Boolean(row.reviewed)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async list(opts) {
|
|
20
|
+
let query = this.client.from(this.tableName).select("*", { count: "exact" });
|
|
21
|
+
if (opts?.url !== void 0) {
|
|
22
|
+
query = query.eq("url", opts.url);
|
|
23
|
+
}
|
|
24
|
+
if (opts?.reviewed !== void 0) {
|
|
25
|
+
query = query.eq("reviewed", opts.reviewed ? 1 : 0);
|
|
26
|
+
}
|
|
27
|
+
query = query.order("created_at", { ascending: false });
|
|
28
|
+
if (opts?.limit !== void 0) {
|
|
29
|
+
query = query.limit(opts.limit);
|
|
30
|
+
}
|
|
31
|
+
const { data, count, error } = await query;
|
|
32
|
+
if (error) throw error;
|
|
33
|
+
return { data: (data ?? []).map((r) => this.mapRow(r)), total: count ?? 0 };
|
|
34
|
+
}
|
|
35
|
+
async create(record) {
|
|
36
|
+
const { data, error } = await this.client.from(this.tableName).insert({
|
|
37
|
+
id: nanoid(),
|
|
38
|
+
user_id: record.userId ?? null,
|
|
39
|
+
type: record.type,
|
|
40
|
+
body: record.body,
|
|
41
|
+
url: record.url,
|
|
42
|
+
created_at: record.createdAt,
|
|
43
|
+
reviewed: 0
|
|
44
|
+
}).select().single();
|
|
45
|
+
if (error) throw error;
|
|
46
|
+
return this.mapRow(data);
|
|
47
|
+
}
|
|
48
|
+
async setReviewed(id, reviewed) {
|
|
49
|
+
const { data, error } = await this.client.from(this.tableName).update({ reviewed: reviewed ? 1 : 0 }).eq("id", id).select().single();
|
|
50
|
+
if (error) throw error;
|
|
51
|
+
return this.mapRow(data);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
SupabaseFeedbackAdapter
|
|
57
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/adapters/in-memory.ts
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
var InMemoryFeedbackAdapter = class {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.store = /* @__PURE__ */ new Map();
|
|
6
|
+
}
|
|
7
|
+
async list(opts) {
|
|
8
|
+
let records = Array.from(this.store.values());
|
|
9
|
+
if (opts?.url !== void 0) {
|
|
10
|
+
records = records.filter((r) => r.url === opts.url);
|
|
11
|
+
}
|
|
12
|
+
if (opts?.reviewed !== void 0) {
|
|
13
|
+
records = records.filter((r) => r.reviewed === opts.reviewed);
|
|
14
|
+
}
|
|
15
|
+
records.sort((a, b) => b.createdAt - a.createdAt);
|
|
16
|
+
if (opts?.limit !== void 0) {
|
|
17
|
+
records = records.slice(0, opts.limit);
|
|
18
|
+
}
|
|
19
|
+
return { data: records, total: records.length };
|
|
20
|
+
}
|
|
21
|
+
async create(record) {
|
|
22
|
+
const feedbackRecord = {
|
|
23
|
+
id: nanoid(),
|
|
24
|
+
userId: record.userId,
|
|
25
|
+
type: record.type,
|
|
26
|
+
body: record.body,
|
|
27
|
+
url: record.url,
|
|
28
|
+
createdAt: record.createdAt,
|
|
29
|
+
reviewed: false
|
|
30
|
+
};
|
|
31
|
+
this.store.set(feedbackRecord.id, feedbackRecord);
|
|
32
|
+
return feedbackRecord;
|
|
33
|
+
}
|
|
34
|
+
async setReviewed(id, reviewed) {
|
|
35
|
+
const record = this.store.get(id);
|
|
36
|
+
if (!record) {
|
|
37
|
+
throw new Error(`Feedback record ${id} not found`);
|
|
38
|
+
}
|
|
39
|
+
record.reviewed = reviewed;
|
|
40
|
+
return record;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
InMemoryFeedbackAdapter
|
|
46
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/adapters/supabase.ts
|
|
2
|
+
var _nanoid = require('nanoid');
|
|
3
|
+
var SupabaseFeedbackAdapter = class {
|
|
4
|
+
constructor(client, tableName = "feedback") {
|
|
5
|
+
this.client = client;
|
|
6
|
+
this.tableName = tableName;
|
|
7
|
+
}
|
|
8
|
+
mapRow(row) {
|
|
9
|
+
return {
|
|
10
|
+
id: row.id,
|
|
11
|
+
userId: row.user_id,
|
|
12
|
+
type: row.type,
|
|
13
|
+
body: row.body,
|
|
14
|
+
url: row.url,
|
|
15
|
+
createdAt: row.created_at,
|
|
16
|
+
reviewed: Boolean(row.reviewed)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async list(opts) {
|
|
20
|
+
let query = this.client.from(this.tableName).select("*", { count: "exact" });
|
|
21
|
+
if (_optionalChain([opts, 'optionalAccess', _ => _.url]) !== void 0) {
|
|
22
|
+
query = query.eq("url", opts.url);
|
|
23
|
+
}
|
|
24
|
+
if (_optionalChain([opts, 'optionalAccess', _2 => _2.reviewed]) !== void 0) {
|
|
25
|
+
query = query.eq("reviewed", opts.reviewed ? 1 : 0);
|
|
26
|
+
}
|
|
27
|
+
query = query.order("created_at", { ascending: false });
|
|
28
|
+
if (_optionalChain([opts, 'optionalAccess', _3 => _3.limit]) !== void 0) {
|
|
29
|
+
query = query.limit(opts.limit);
|
|
30
|
+
}
|
|
31
|
+
const { data, count, error } = await query;
|
|
32
|
+
if (error) throw error;
|
|
33
|
+
return { data: (_nullishCoalesce(data, () => ( []))).map((r) => this.mapRow(r)), total: _nullishCoalesce(count, () => ( 0)) };
|
|
34
|
+
}
|
|
35
|
+
async create(record) {
|
|
36
|
+
const { data, error } = await this.client.from(this.tableName).insert({
|
|
37
|
+
id: _nanoid.nanoid.call(void 0, ),
|
|
38
|
+
user_id: _nullishCoalesce(record.userId, () => ( null)),
|
|
39
|
+
type: record.type,
|
|
40
|
+
body: record.body,
|
|
41
|
+
url: record.url,
|
|
42
|
+
created_at: record.createdAt,
|
|
43
|
+
reviewed: 0
|
|
44
|
+
}).select().single();
|
|
45
|
+
if (error) throw error;
|
|
46
|
+
return this.mapRow(data);
|
|
47
|
+
}
|
|
48
|
+
async setReviewed(id, reviewed) {
|
|
49
|
+
const { data, error } = await this.client.from(this.tableName).update({ reviewed: reviewed ? 1 : 0 }).eq("id", id).select().single();
|
|
50
|
+
if (error) throw error;
|
|
51
|
+
return this.mapRow(data);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
exports.SupabaseFeedbackAdapter = SupabaseFeedbackAdapter;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
|
+
|
|
3
|
+
var _chunkF4UO2IK4cjs = require('./chunk-F4UO2IK4.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
var _chunkCEOUAA46cjs = require('./chunk-CEOUAA46.cjs');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
var _chunk5Q4LNXS7cjs = require('./chunk-5Q4LNXS7.cjs');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
var _chunkSRH4NGARcjs = require('./chunk-SRH4NGAR.cjs');
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
exports.FeedbackWidget = _chunkCEOUAA46cjs.FeedbackWidget; exports.InMemoryFeedbackAdapter = _chunk5Q4LNXS7cjs.InMemoryFeedbackAdapter; exports.SupabaseFeedbackAdapter = _chunkSRH4NGARcjs.SupabaseFeedbackAdapter; exports.createFeedbackHandler = _chunkF4UO2IK4cjs.createFeedbackHandler;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { F as FeedbackAdapter, a as FeedbackData, b as FeedbackRecord, c as FeedbackType, d as FeedbackWidgetProps } from './types-CMsn8uhD.cjs';
|
|
2
|
+
export { createFeedbackHandler } from './server.cjs';
|
|
3
|
+
export { InMemoryFeedbackAdapter } from './adapters/in-memory.cjs';
|
|
4
|
+
export { SupabaseFeedbackAdapter } from './adapters/supabase.cjs';
|
|
5
|
+
export { FeedbackWidget } from './react.cjs';
|
|
6
|
+
import 'next/server';
|
|
7
|
+
import '@supabase/supabase-js';
|
|
8
|
+
import 'react/jsx-runtime';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { F as FeedbackAdapter, a as FeedbackData, b as FeedbackRecord, c as FeedbackType, d as FeedbackWidgetProps } from './types-CMsn8uhD.js';
|
|
2
|
+
export { createFeedbackHandler } from './server.js';
|
|
3
|
+
export { InMemoryFeedbackAdapter } from './adapters/in-memory.js';
|
|
4
|
+
export { SupabaseFeedbackAdapter } from './adapters/supabase.js';
|
|
5
|
+
export { FeedbackWidget } from './react.js';
|
|
6
|
+
import 'next/server';
|
|
7
|
+
import '@supabase/supabase-js';
|
|
8
|
+
import 'react/jsx-runtime';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createFeedbackHandler
|
|
3
|
+
} from "./chunk-IZIGPLKR.js";
|
|
4
|
+
import {
|
|
5
|
+
FeedbackWidget
|
|
6
|
+
} from "./chunk-3CF2TRVK.js";
|
|
7
|
+
import {
|
|
8
|
+
InMemoryFeedbackAdapter
|
|
9
|
+
} from "./chunk-NKCO24NR.js";
|
|
10
|
+
import {
|
|
11
|
+
SupabaseFeedbackAdapter
|
|
12
|
+
} from "./chunk-KXORPGTI.js";
|
|
13
|
+
export {
|
|
14
|
+
FeedbackWidget,
|
|
15
|
+
InMemoryFeedbackAdapter,
|
|
16
|
+
SupabaseFeedbackAdapter,
|
|
17
|
+
createFeedbackHandler
|
|
18
|
+
};
|
package/dist/react.cjs
ADDED
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { d as FeedbackWidgetProps } from './types-CMsn8uhD.cjs';
|
|
3
|
+
export { a as FeedbackData } from './types-CMsn8uhD.cjs';
|
|
4
|
+
|
|
5
|
+
declare function FeedbackWidget({ type, url: urlProp, userId, theme, className, onSubmit, maxStars, placeholder, }: FeedbackWidgetProps): react_jsx_runtime.JSX.Element | null;
|
|
6
|
+
|
|
7
|
+
export { FeedbackWidget, FeedbackWidgetProps };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { d as FeedbackWidgetProps } from './types-CMsn8uhD.js';
|
|
3
|
+
export { a as FeedbackData } from './types-CMsn8uhD.js';
|
|
4
|
+
|
|
5
|
+
declare function FeedbackWidget({ type, url: urlProp, userId, theme, className, onSubmit, maxStars, placeholder, }: FeedbackWidgetProps): react_jsx_runtime.JSX.Element | null;
|
|
6
|
+
|
|
7
|
+
export { FeedbackWidget, FeedbackWidgetProps };
|
package/dist/react.js
ADDED
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { F as FeedbackAdapter, b as FeedbackRecord } from './types-CMsn8uhD.cjs';
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
|
|
4
|
+
interface HandlerOptions {
|
|
5
|
+
adapter: FeedbackAdapter;
|
|
6
|
+
secret: string;
|
|
7
|
+
}
|
|
8
|
+
declare function createFeedbackHandler({ adapter, secret }: HandlerOptions): (req: NextRequest) => Promise<NextResponse<{
|
|
9
|
+
error: string;
|
|
10
|
+
}> | NextResponse<{
|
|
11
|
+
data: FeedbackRecord[];
|
|
12
|
+
total: number;
|
|
13
|
+
}> | NextResponse<{
|
|
14
|
+
data: {
|
|
15
|
+
id: string;
|
|
16
|
+
reviewed: boolean;
|
|
17
|
+
};
|
|
18
|
+
}>>;
|
|
19
|
+
|
|
20
|
+
export { createFeedbackHandler };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { F as FeedbackAdapter, b as FeedbackRecord } from './types-CMsn8uhD.js';
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
|
|
4
|
+
interface HandlerOptions {
|
|
5
|
+
adapter: FeedbackAdapter;
|
|
6
|
+
secret: string;
|
|
7
|
+
}
|
|
8
|
+
declare function createFeedbackHandler({ adapter, secret }: HandlerOptions): (req: NextRequest) => Promise<NextResponse<{
|
|
9
|
+
error: string;
|
|
10
|
+
}> | NextResponse<{
|
|
11
|
+
data: FeedbackRecord[];
|
|
12
|
+
total: number;
|
|
13
|
+
}> | NextResponse<{
|
|
14
|
+
data: {
|
|
15
|
+
id: string;
|
|
16
|
+
reviewed: boolean;
|
|
17
|
+
};
|
|
18
|
+
}>>;
|
|
19
|
+
|
|
20
|
+
export { createFeedbackHandler };
|