@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.
Files changed (46) hide show
  1. package/.github/workflows/publish.yml +27 -0
  2. package/LICENSE +7 -0
  3. package/README.md +136 -0
  4. package/dist/adapters/in-memory.cjs +6 -0
  5. package/dist/adapters/in-memory.d.cts +23 -0
  6. package/dist/adapters/in-memory.d.ts +23 -0
  7. package/dist/adapters/in-memory.js +6 -0
  8. package/dist/adapters/supabase.cjs +6 -0
  9. package/dist/adapters/supabase.d.cts +27 -0
  10. package/dist/adapters/supabase.d.ts +27 -0
  11. package/dist/adapters/supabase.js +6 -0
  12. package/dist/chunk-3CF2TRVK.js +202 -0
  13. package/dist/chunk-5Q4LNXS7.cjs +46 -0
  14. package/dist/chunk-CEOUAA46.cjs +202 -0
  15. package/dist/chunk-F4UO2IK4.cjs +74 -0
  16. package/dist/chunk-IZIGPLKR.js +74 -0
  17. package/dist/chunk-KXORPGTI.js +57 -0
  18. package/dist/chunk-NKCO24NR.js +46 -0
  19. package/dist/chunk-SRH4NGAR.cjs +57 -0
  20. package/dist/index.cjs +18 -0
  21. package/dist/index.d.cts +8 -0
  22. package/dist/index.d.ts +8 -0
  23. package/dist/index.js +18 -0
  24. package/dist/react.cjs +6 -0
  25. package/dist/react.d.cts +7 -0
  26. package/dist/react.d.ts +7 -0
  27. package/dist/react.js +6 -0
  28. package/dist/server.cjs +6 -0
  29. package/dist/server.d.cts +20 -0
  30. package/dist/server.d.ts +20 -0
  31. package/dist/server.js +6 -0
  32. package/dist/types-CMsn8uhD.d.cts +49 -0
  33. package/dist/types-CMsn8uhD.d.ts +49 -0
  34. package/package.json +66 -0
  35. package/src/adapters/in-memory.ts +58 -0
  36. package/src/adapters/index.ts +2 -0
  37. package/src/adapters/supabase.ts +88 -0
  38. package/src/app/api/feedback/[...cyguin]/route.ts +20 -0
  39. package/src/components/FeedbackWidget.tsx +215 -0
  40. package/src/components/index.ts +2 -0
  41. package/src/handlers/route.ts +100 -0
  42. package/src/index.ts +11 -0
  43. package/src/server.ts +1 -0
  44. package/src/types.ts +50 -0
  45. package/tsconfig.json +18 -0
  46. package/tsup.config.ts +16 -0
@@ -0,0 +1,27 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ id-token: write
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-node@v4
17
+ with:
18
+ node-version: '20'
19
+ registry-url: 'https://registry.npmjs.org'
20
+ - name: Install dependencies
21
+ run: npm install --legacy-peer-deps
22
+ - name: Build
23
+ run: npm run build
24
+ - name: Publish
25
+ env:
26
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
27
+ run: npm publish --provenance --access public
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2026 cyguin.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @cyguin/feedback
2
+
3
+ Drop-in user feedback widget for Next.js — capture thumbs, star ratings, or free-text feedback from inside any page.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @cyguin/feedback
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ ### 1. Create the API route
14
+
15
+ Create `app/api/feedback/[...cyguin]/route.ts` in your Next.js app:
16
+
17
+ ```ts
18
+ import { createFeedbackHandler } from '@cyguin/feedback/server';
19
+ import { createInMemoryAdapter } from '@cyguin/feedback/adapters/in-memory';
20
+
21
+ const adapter = createInMemoryAdapter();
22
+ const handler = createFeedbackHandler({ adapter });
23
+
24
+ export { handler as GET, handler as POST, handler as PATCH };
25
+ ```
26
+
27
+ For production with Supabase:
28
+
29
+ ```ts
30
+ import { createFeedbackHandler } from '@cyguin/feedback/server';
31
+ import { createSupabaseAdapter } from '@cyguin/feedback/adapters/supabase';
32
+
33
+ const adapter = createSupabaseAdapter(supabaseClient);
34
+ const handler = createFeedbackHandler({ adapter });
35
+
36
+ export { handler as GET, handler as POST, handler as PATCH };
37
+ ```
38
+
39
+ ### 2. Run migrations
40
+
41
+ ```sql
42
+ CREATE TABLE feedback (
43
+ id TEXT PRIMARY KEY,
44
+ user_id TEXT,
45
+ type TEXT NOT NULL,
46
+ body TEXT NOT NULL,
47
+ url TEXT NOT NULL,
48
+ created_at INTEGER NOT NULL,
49
+ reviewed INTEGER NOT NULL DEFAULT 0
50
+ );
51
+ ```
52
+
53
+ ### 3. Add the widget
54
+
55
+ ```tsx
56
+ import { FeedbackWidget } from '@cyguin/feedback/react';
57
+
58
+ // Thumbs mode
59
+ <FeedbackWidget type="thumbs" />
60
+
61
+ // Star rating mode (default 5 stars)
62
+ <FeedbackWidget type="rating" maxStars={5} />
63
+
64
+ // Free text mode
65
+ <FeedbackWidget type="text" placeholder="Share your feedback..." />
66
+ ```
67
+
68
+ All three modes submit to your API and show a "Thanks for your feedback!" message for 3 seconds.
69
+
70
+ ## Theming
71
+
72
+ Use `--cyguin-*` CSS custom properties on a parent element or `:root`:
73
+
74
+ ```css
75
+ .feedback-widget {
76
+ --cyguin-bg: #ffffff;
77
+ --cyguin-bg-subtle: #f5f5f5;
78
+ --cyguin-border: #e5e5e5;
79
+ --cyguin-border-focus: #f5a800;
80
+ --cyguin-fg: #0a0a0a;
81
+ --cyguin-fg-muted: #888888;
82
+ --cyguin-accent: #f5a800;
83
+ --cyguin-accent-dark: #c47f00;
84
+ --cyguin-accent-fg: #0a0a0a;
85
+ --cyguin-radius: 6px;
86
+ --cyguin-shadow: 0 1px 4px rgba(0,0,0,0.08);
87
+ }
88
+ ```
89
+
90
+ Switch to dark theme with the `theme` prop:
91
+
92
+ ```tsx
93
+ <FeedbackWidget type="thumbs" theme="dark" />
94
+ ```
95
+
96
+ Dark theme applies these overrides automatically:
97
+
98
+ ```css
99
+ --cyguin-bg: #0a0a0a;
100
+ --cyguin-bg-subtle: #1a1a1a;
101
+ --cyguin-border: #2a2a2a;
102
+ --cyguin-fg: #f5f5f5;
103
+ --cyguin-shadow: 0 1px 4px rgba(0,0,0,0.4);
104
+ ```
105
+
106
+ ## Supabase Adapter Setup
107
+
108
+ ```ts
109
+ import { createFeedbackHandler } from '@cyguin/feedback/server';
110
+ import { createSupabaseAdapter } from '@cyguin/feedback/adapters/supabase';
111
+ import { createClient } from '@supabase/supabase-js';
112
+
113
+ const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);
114
+ const adapter = createSupabaseAdapter(supabase);
115
+
116
+ const handler = createFeedbackHandler({
117
+ adapter,
118
+ secret: process.env.FEEDBACK_SECRET,
119
+ });
120
+
121
+ export { handler as GET, handler as POST, handler as PATCH };
122
+ ```
123
+
124
+ Required environment variable: `FEEDBACK_SECRET` — Bearer token for admin API access.
125
+
126
+ ## API Routes
127
+
128
+ | Method | Route | Description |
129
+ |--------|-------|-------------|
130
+ | GET | `/api/feedback` | List feedback (admin, requires Bearer token) |
131
+ | POST | `/api/feedback` | Submit new feedback |
132
+ | PATCH | `/api/feedback/:id/reviewed` | Toggle reviewed flag |
133
+
134
+ ## License
135
+
136
+ MIT
@@ -0,0 +1,6 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ var _chunk5Q4LNXS7cjs = require('../chunk-5Q4LNXS7.cjs');
4
+
5
+
6
+ exports.InMemoryFeedbackAdapter = _chunk5Q4LNXS7cjs.InMemoryFeedbackAdapter;
@@ -0,0 +1,23 @@
1
+ import { F as FeedbackAdapter, b as FeedbackRecord, c as FeedbackType } from '../types-CMsn8uhD.cjs';
2
+
3
+ declare class InMemoryFeedbackAdapter implements FeedbackAdapter {
4
+ private store;
5
+ list(opts?: {
6
+ url?: string;
7
+ reviewed?: boolean;
8
+ limit?: number;
9
+ }): Promise<{
10
+ data: FeedbackRecord[];
11
+ total: number;
12
+ }>;
13
+ create(record: {
14
+ userId?: string;
15
+ type: FeedbackType;
16
+ body: string;
17
+ url: string;
18
+ createdAt: number;
19
+ }): Promise<FeedbackRecord>;
20
+ setReviewed(id: string, reviewed: boolean): Promise<FeedbackRecord>;
21
+ }
22
+
23
+ export { InMemoryFeedbackAdapter };
@@ -0,0 +1,23 @@
1
+ import { F as FeedbackAdapter, b as FeedbackRecord, c as FeedbackType } from '../types-CMsn8uhD.js';
2
+
3
+ declare class InMemoryFeedbackAdapter implements FeedbackAdapter {
4
+ private store;
5
+ list(opts?: {
6
+ url?: string;
7
+ reviewed?: boolean;
8
+ limit?: number;
9
+ }): Promise<{
10
+ data: FeedbackRecord[];
11
+ total: number;
12
+ }>;
13
+ create(record: {
14
+ userId?: string;
15
+ type: FeedbackType;
16
+ body: string;
17
+ url: string;
18
+ createdAt: number;
19
+ }): Promise<FeedbackRecord>;
20
+ setReviewed(id: string, reviewed: boolean): Promise<FeedbackRecord>;
21
+ }
22
+
23
+ export { InMemoryFeedbackAdapter };
@@ -0,0 +1,6 @@
1
+ import {
2
+ InMemoryFeedbackAdapter
3
+ } from "../chunk-NKCO24NR.js";
4
+ export {
5
+ InMemoryFeedbackAdapter
6
+ };
@@ -0,0 +1,6 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ var _chunkSRH4NGARcjs = require('../chunk-SRH4NGAR.cjs');
4
+
5
+
6
+ exports.SupabaseFeedbackAdapter = _chunkSRH4NGARcjs.SupabaseFeedbackAdapter;
@@ -0,0 +1,27 @@
1
+ import { F as FeedbackAdapter, b as FeedbackRecord, c as FeedbackType } from '../types-CMsn8uhD.cjs';
2
+ import { SupabaseClient } from '@supabase/supabase-js';
3
+
4
+ declare class SupabaseFeedbackAdapter implements FeedbackAdapter {
5
+ private client;
6
+ private tableName;
7
+ constructor(client: SupabaseClient, tableName?: string);
8
+ private mapRow;
9
+ list(opts?: {
10
+ url?: string;
11
+ reviewed?: boolean;
12
+ limit?: number;
13
+ }): Promise<{
14
+ data: FeedbackRecord[];
15
+ total: number;
16
+ }>;
17
+ create(record: {
18
+ userId?: string;
19
+ type: FeedbackType;
20
+ body: string;
21
+ url: string;
22
+ createdAt: number;
23
+ }): Promise<FeedbackRecord>;
24
+ setReviewed(id: string, reviewed: boolean): Promise<FeedbackRecord>;
25
+ }
26
+
27
+ export { SupabaseFeedbackAdapter };
@@ -0,0 +1,27 @@
1
+ import { F as FeedbackAdapter, b as FeedbackRecord, c as FeedbackType } from '../types-CMsn8uhD.js';
2
+ import { SupabaseClient } from '@supabase/supabase-js';
3
+
4
+ declare class SupabaseFeedbackAdapter implements FeedbackAdapter {
5
+ private client;
6
+ private tableName;
7
+ constructor(client: SupabaseClient, tableName?: string);
8
+ private mapRow;
9
+ list(opts?: {
10
+ url?: string;
11
+ reviewed?: boolean;
12
+ limit?: number;
13
+ }): Promise<{
14
+ data: FeedbackRecord[];
15
+ total: number;
16
+ }>;
17
+ create(record: {
18
+ userId?: string;
19
+ type: FeedbackType;
20
+ body: string;
21
+ url: string;
22
+ createdAt: number;
23
+ }): Promise<FeedbackRecord>;
24
+ setReviewed(id: string, reviewed: boolean): Promise<FeedbackRecord>;
25
+ }
26
+
27
+ export { SupabaseFeedbackAdapter };
@@ -0,0 +1,6 @@
1
+ import {
2
+ SupabaseFeedbackAdapter
3
+ } from "../chunk-KXORPGTI.js";
4
+ export {
5
+ SupabaseFeedbackAdapter
6
+ };
@@ -0,0 +1,202 @@
1
+ // src/components/FeedbackWidget.tsx
2
+ import { useState, useCallback } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ var THUMBS_UP = "thumbs_up";
5
+ var THUMBS_DOWN = "thumbs_down";
6
+ function ThumbsUpIcon() {
7
+ return /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
8
+ /* @__PURE__ */ jsx("path", { d: "M7 10v12" }),
9
+ /* @__PURE__ */ jsx("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__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
14
+ /* @__PURE__ */ jsx("path", { d: "M17 14V2" }),
15
+ /* @__PURE__ */ jsx("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__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: filled ? "currentColor" : "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("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] = useState(false);
58
+ const [textValue, setTextValue] = useState("");
59
+ const [loading, setLoading] = useState(false);
60
+ const getUrl = () => {
61
+ if (urlProp) return urlProp;
62
+ if (typeof window !== "undefined") return window.location.href;
63
+ return "";
64
+ };
65
+ const handleSubmit = useCallback(
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
+ onSubmit?.(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__ */ jsx(
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__ */ jsxs(
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__ */ jsx(
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__ */ jsx(ThumbsUpIcon, {})
113
+ }
114
+ ),
115
+ /* @__PURE__ */ jsx(
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__ */ jsx(ThumbsDownIcon, {})
123
+ }
124
+ )
125
+ ]
126
+ }
127
+ );
128
+ }
129
+ if (type === "rating") {
130
+ const [selected, setSelected] = useState(null);
131
+ const handleStarClick = (star) => {
132
+ setSelected(star);
133
+ handleSubmit(String(star));
134
+ };
135
+ return /* @__PURE__ */ jsx(
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__ */ jsx(
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__ */ jsx(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__ */ jsx(
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__ */ jsxs("form", { onSubmit: handleTextSubmit, style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
174
+ /* @__PURE__ */ jsx(
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__ */ jsx(
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
+ export {
201
+ FeedbackWidget
202
+ };
@@ -0,0 +1,46 @@
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/adapters/in-memory.ts
2
+ var _nanoid = require('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 (_optionalChain([opts, 'optionalAccess', _ => _.url]) !== void 0) {
10
+ records = records.filter((r) => r.url === opts.url);
11
+ }
12
+ if (_optionalChain([opts, 'optionalAccess', _2 => _2.reviewed]) !== void 0) {
13
+ records = records.filter((r) => r.reviewed === opts.reviewed);
14
+ }
15
+ records.sort((a, b) => b.createdAt - a.createdAt);
16
+ if (_optionalChain([opts, 'optionalAccess', _3 => _3.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.nanoid.call(void 0, ),
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
+
45
+
46
+ exports.InMemoryFeedbackAdapter = InMemoryFeedbackAdapter;