@geenius/feedback 0.1.0 → 0.3.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/package.json +16 -3
- package/packages/convex/dist/index.d.ts +192 -0
- package/packages/convex/dist/index.js +239 -0
- package/packages/convex/dist/index.js.map +1 -0
- package/packages/react/README.md +1 -1
- package/packages/react/dist/index.d.ts +146 -0
- package/packages/react/dist/index.js +545 -0
- package/packages/react/dist/index.js.map +1 -0
- package/packages/react-css/README.md +1 -1
- package/packages/react-css/dist/index.css +965 -0
- package/packages/react-css/dist/index.css.map +1 -0
- package/packages/react-css/dist/index.d.ts +49 -0
- package/packages/react-css/dist/index.js +228 -0
- package/packages/react-css/dist/index.js.map +1 -0
- package/packages/shared/README.md +1 -1
- package/packages/shared/dist/index.d.ts +115 -0
- package/packages/shared/dist/index.js +112 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/packages/solidjs/README.md +1 -1
- package/packages/solidjs/dist/index.d.ts +128 -0
- package/packages/solidjs/dist/index.js +289 -0
- package/packages/solidjs/dist/index.js.map +1 -0
- package/packages/solidjs-css/README.md +1 -1
- package/packages/solidjs-css/dist/index.css +965 -0
- package/packages/solidjs-css/dist/index.css.map +1 -0
- package/packages/solidjs-css/dist/index.d.ts +2 -0
- package/packages/solidjs-css/dist/index.js +29 -0
- package/packages/solidjs-css/dist/index.js.map +1 -0
- package/.changeset/config.json +0 -11
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/ci.yml +0 -23
- package/.github/workflows/release.yml +0 -29
- package/.nvmrc +0 -1
- package/.project/ACCOUNT.yaml +0 -4
- package/.project/IDEAS.yaml +0 -7
- package/.project/PROJECT.yaml +0 -11
- package/.project/ROADMAP.yaml +0 -15
- package/CODE_OF_CONDUCT.md +0 -16
- package/CONTRIBUTING.md +0 -26
- package/SECURITY.md +0 -15
- package/SUPPORT.md +0 -8
- package/packages/convex/package.json +0 -42
- package/packages/convex/src/index.ts +0 -3
- package/packages/convex/src/mutations.ts +0 -88
- package/packages/convex/src/queries.ts +0 -78
- package/packages/convex/src/schema.ts +0 -47
- package/packages/convex/tsconfig.json +0 -18
- package/packages/convex/tsup.config.ts +0 -17
- package/packages/react/package.json +0 -49
- package/packages/react/src/components/FeedbackCard.tsx +0 -51
- package/packages/react/src/components/FeedbackForm.tsx +0 -43
- package/packages/react/src/components/FeedbackWidget.tsx +0 -32
- package/packages/react/src/components/NPSSurvey.tsx +0 -62
- package/packages/react/src/components/index.ts +0 -4
- package/packages/react/src/hooks/index.ts +0 -5
- package/packages/react/src/hooks/useFeedback.ts +0 -23
- package/packages/react/src/hooks/useFeedbackAdmin.ts +0 -24
- package/packages/react/src/hooks/useFeedbackForm.ts +0 -35
- package/packages/react/src/hooks/useNPS.ts +0 -26
- package/packages/react/src/index.tsx +0 -13
- package/packages/react/src/pages/FeedbackAdminPage.tsx +0 -71
- package/packages/react/src/pages/FeedbackPublicPage.tsx +0 -42
- package/packages/react/src/pages/FeedbackWidgetPage.tsx +0 -25
- package/packages/react/src/pages/index.ts +0 -3
- package/packages/react/tsconfig.json +0 -19
- package/packages/react/tsup.config.ts +0 -12
- package/packages/react-css/package.json +0 -36
- package/packages/react-css/src/components/index.ts +0 -5
- package/packages/react-css/src/components/index.tsx +0 -107
- package/packages/react-css/src/hooks/index.ts +0 -2
- package/packages/react-css/src/index.tsx +0 -5
- package/packages/react-css/src/pages/FeedbackAdminPage.tsx +0 -112
- package/packages/react-css/src/pages/FeedbackPage.tsx +0 -76
- package/packages/react-css/src/styles.css +0 -281
- package/packages/react-css/tsconfig.json +0 -19
- package/packages/react-css/tsup.config.ts +0 -10
- package/packages/shared/package.json +0 -44
- package/packages/shared/src/__tests__/feedback.test.ts +0 -72
- package/packages/shared/src/config.ts +0 -49
- package/packages/shared/src/index.ts +0 -111
- package/packages/shared/src/types.ts +0 -59
- package/packages/shared/tsconfig.json +0 -18
- package/packages/shared/tsup.config.ts +0 -11
- package/packages/shared/vitest.config.ts +0 -4
- package/packages/solidjs/package.json +0 -45
- package/packages/solidjs/src/components.tsx +0 -72
- package/packages/solidjs/src/index.tsx +0 -3
- package/packages/solidjs/src/primitives.ts +0 -49
- package/packages/solidjs/tsconfig.json +0 -20
- package/packages/solidjs/tsup.config.ts +0 -12
- package/packages/solidjs-css/package.json +0 -32
- package/packages/solidjs-css/src/index.tsx +0 -4
- package/packages/solidjs-css/src/pages/FeedbackAdminPage.tsx +0 -78
- package/packages/solidjs-css/src/pages/FeedbackPage.tsx +0 -65
- package/packages/solidjs-css/src/primitives/index.ts +0 -1
- package/packages/solidjs-css/src/styles.css +0 -281
- package/packages/solidjs-css/tsconfig.json +0 -20
- package/packages/solidjs-css/tsup.config.ts +0 -10
- package/pnpm-workspace.yaml +0 -2
- package/tsconfig.json +0 -23
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import { createContext, useContext, useState, useMemo, useCallback, useEffect } from 'react';
|
|
2
|
+
import { defaultFeedbackConfig, calcFeedbackStats, STATUS_CONFIG, PRIORITY_CONFIG, TYPE_CONFIG, formatRelativeTime, FEEDBACK_TYPES, calcNPSStats, FEEDBACK_STATUSES, FEEDBACK_PRIORITIES } from '@geenius/feedback-shared';
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/provider/FeedbackProvider.tsx
|
|
6
|
+
var FeedbackContext = createContext(defaultFeedbackConfig);
|
|
7
|
+
function FeedbackProvider({ children, config }) {
|
|
8
|
+
const value = { ...defaultFeedbackConfig, ...config };
|
|
9
|
+
return /* @__PURE__ */ jsx(FeedbackContext.Provider, { value, children });
|
|
10
|
+
}
|
|
11
|
+
function useFeedbackConfig() {
|
|
12
|
+
return useContext(FeedbackContext);
|
|
13
|
+
}
|
|
14
|
+
function useFeedback(items) {
|
|
15
|
+
const [filter, setFilter] = useState({ sort: "newest" });
|
|
16
|
+
const isLoading = items === void 0;
|
|
17
|
+
const stats = useMemo(() => calcFeedbackStats(items ?? []), [items]);
|
|
18
|
+
const filteredItems = useMemo(() => {
|
|
19
|
+
let result = items ?? [];
|
|
20
|
+
if (filter.type) result = result.filter((i) => i.type === filter.type);
|
|
21
|
+
if (filter.status) result = result.filter((i) => i.status === filter.status);
|
|
22
|
+
if (filter.priority) result = result.filter((i) => i.priority === filter.priority);
|
|
23
|
+
if (filter.search) {
|
|
24
|
+
const q = filter.search.toLowerCase();
|
|
25
|
+
result = result.filter((i) => i.title.toLowerCase().includes(q) || i.description.toLowerCase().includes(q));
|
|
26
|
+
}
|
|
27
|
+
if (filter.sort === "votes") result = [...result].sort((a, b) => b.votes - a.votes);
|
|
28
|
+
return result;
|
|
29
|
+
}, [items, filter]);
|
|
30
|
+
return { items: filteredItems, allItems: items ?? [], stats, filter, setFilter, isLoading };
|
|
31
|
+
}
|
|
32
|
+
function useFeedbackForm(submitFn) {
|
|
33
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
34
|
+
const [error, setError] = useState(null);
|
|
35
|
+
const [success, setSuccess] = useState(false);
|
|
36
|
+
const [formData, setFormData] = useState({ type: "general", title: "", description: "" });
|
|
37
|
+
const validate = useCallback(() => {
|
|
38
|
+
if (!formData.title.trim()) return "Title is required";
|
|
39
|
+
if (formData.title.trim().length < 3) return "Title must be at least 3 characters";
|
|
40
|
+
if (!formData.description.trim()) return "Description is required";
|
|
41
|
+
if (formData.description.trim().length < 10) return "Description must be at least 10 characters";
|
|
42
|
+
return null;
|
|
43
|
+
}, [formData]);
|
|
44
|
+
const submit = useCallback(async () => {
|
|
45
|
+
const err = validate();
|
|
46
|
+
if (err) {
|
|
47
|
+
setError(err);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
setIsSubmitting(true);
|
|
51
|
+
setError(null);
|
|
52
|
+
try {
|
|
53
|
+
const browser = typeof navigator !== "undefined" ? navigator.userAgent : void 0;
|
|
54
|
+
await submitFn({ ...formData, browser, url: formData.url || (typeof window !== "undefined" ? window.location.href : void 0) });
|
|
55
|
+
setSuccess(true);
|
|
56
|
+
setFormData({ type: "general", title: "", description: "" });
|
|
57
|
+
} catch (e) {
|
|
58
|
+
setError(e instanceof Error ? e.message : "Submission failed");
|
|
59
|
+
} finally {
|
|
60
|
+
setIsSubmitting(false);
|
|
61
|
+
}
|
|
62
|
+
}, [formData, submitFn, validate]);
|
|
63
|
+
const reset = useCallback(() => {
|
|
64
|
+
setFormData({ type: "general", title: "", description: "" });
|
|
65
|
+
setError(null);
|
|
66
|
+
setSuccess(false);
|
|
67
|
+
}, []);
|
|
68
|
+
return { formData, setFormData, submit, isSubmitting, error, success, reset, validate };
|
|
69
|
+
}
|
|
70
|
+
var NPS_STORAGE_KEY = "geenius-nps-last-shown";
|
|
71
|
+
function useNPS(submitFn, intervalDays = 90) {
|
|
72
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
73
|
+
const [showSurvey, setShowSurvey] = useState(false);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (typeof window === "undefined") return;
|
|
76
|
+
const last = localStorage.getItem(NPS_STORAGE_KEY);
|
|
77
|
+
if (!last) {
|
|
78
|
+
setShowSurvey(true);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const daysSince = (Date.now() - Number(last)) / (1e3 * 60 * 60 * 24);
|
|
82
|
+
if (daysSince >= intervalDays) setShowSurvey(true);
|
|
83
|
+
}, [intervalDays]);
|
|
84
|
+
const submit = useCallback(async (score, comment) => {
|
|
85
|
+
setIsSubmitting(true);
|
|
86
|
+
try {
|
|
87
|
+
await submitFn(score, comment);
|
|
88
|
+
localStorage.setItem(NPS_STORAGE_KEY, String(Date.now()));
|
|
89
|
+
setShowSurvey(false);
|
|
90
|
+
} finally {
|
|
91
|
+
setIsSubmitting(false);
|
|
92
|
+
}
|
|
93
|
+
}, [submitFn]);
|
|
94
|
+
const dismiss = useCallback(() => {
|
|
95
|
+
setShowSurvey(false);
|
|
96
|
+
localStorage.setItem(NPS_STORAGE_KEY, String(Date.now()));
|
|
97
|
+
}, []);
|
|
98
|
+
return { submit, isSubmitting, showSurvey, dismiss };
|
|
99
|
+
}
|
|
100
|
+
function useFeedbackAdmin(items, mutations) {
|
|
101
|
+
const [search, setSearch] = useState("");
|
|
102
|
+
const isLoading = items === void 0;
|
|
103
|
+
const stats = useMemo(() => calcFeedbackStats(items ?? []), [items]);
|
|
104
|
+
const filteredItems = useMemo(() => {
|
|
105
|
+
if (!search) return items ?? [];
|
|
106
|
+
const q = search.toLowerCase();
|
|
107
|
+
return (items ?? []).filter((i) => i.title.toLowerCase().includes(q) || i.description.toLowerCase().includes(q) || i.userEmail?.toLowerCase().includes(q));
|
|
108
|
+
}, [items, search]);
|
|
109
|
+
return { items: filteredItems, allItems: items ?? [], stats, search, setSearch, isLoading, ...mutations };
|
|
110
|
+
}
|
|
111
|
+
function StatusBadge({ status }) {
|
|
112
|
+
const cfg = STATUS_CONFIG[status];
|
|
113
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-[10px] font-medium", style: { background: `${cfg.color}22`, color: cfg.color }, children: [
|
|
114
|
+
/* @__PURE__ */ jsx("span", { children: cfg.emoji }),
|
|
115
|
+
cfg.label
|
|
116
|
+
] });
|
|
117
|
+
}
|
|
118
|
+
function PriorityBadge({ priority }) {
|
|
119
|
+
const cfg = PRIORITY_CONFIG[priority];
|
|
120
|
+
return /* @__PURE__ */ jsx("span", { className: "rounded-full px-2 py-0.5 text-[10px] font-medium", style: { background: `${cfg.color}22`, color: cfg.color }, children: cfg.label });
|
|
121
|
+
}
|
|
122
|
+
function TypeBadge({ type }) {
|
|
123
|
+
const cfg = TYPE_CONFIG[type];
|
|
124
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium", style: { background: `${cfg.color}22`, color: cfg.color }, children: [
|
|
125
|
+
/* @__PURE__ */ jsx("span", { children: cfg.icon }),
|
|
126
|
+
cfg.label
|
|
127
|
+
] });
|
|
128
|
+
}
|
|
129
|
+
function FeedbackCard({ item, onVote, onClick, hasVoted }) {
|
|
130
|
+
return /* @__PURE__ */ jsxs("div", { className: "group flex gap-3 rounded-xl border border-white/8 bg-white/[0.02] p-4 transition-all hover:border-indigo-500/20 hover:bg-white/[0.04] cursor-pointer", onClick: () => onClick?.(item.id), children: [
|
|
131
|
+
onVote && /* @__PURE__ */ jsxs(
|
|
132
|
+
"button",
|
|
133
|
+
{
|
|
134
|
+
type: "button",
|
|
135
|
+
onClick: (e) => {
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
onVote(item.id);
|
|
138
|
+
},
|
|
139
|
+
className: `flex flex-col items-center gap-0.5 rounded-lg px-2 py-1.5 text-xs font-bold transition-colors ${hasVoted ? "bg-indigo-500/15 text-indigo-400" : "bg-white/5 text-white/30 hover:bg-white/10 hover:text-white/50"}`,
|
|
140
|
+
children: [
|
|
141
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm", children: hasVoted ? "\u25B2" : "\u25B3" }),
|
|
142
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: item.votes })
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
),
|
|
146
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
147
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1 flex-wrap", children: [
|
|
148
|
+
/* @__PURE__ */ jsx(TypeBadge, { type: item.type }),
|
|
149
|
+
/* @__PURE__ */ jsx(StatusBadge, { status: item.status }),
|
|
150
|
+
/* @__PURE__ */ jsx(PriorityBadge, { priority: item.priority })
|
|
151
|
+
] }),
|
|
152
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-white/90 mb-0.5 truncate", children: item.title }),
|
|
153
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-white/40 line-clamp-2", children: item.description }),
|
|
154
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-3 text-[10px] text-white/25", children: [
|
|
155
|
+
item.userName && /* @__PURE__ */ jsx("span", { children: item.userName }),
|
|
156
|
+
/* @__PURE__ */ jsx("span", { children: formatRelativeTime(item.createdAt) }),
|
|
157
|
+
item.tags.length > 0 && item.tags.map((t) => /* @__PURE__ */ jsx("span", { className: "rounded bg-white/5 px-1.5 py-0.5", children: t }, t))
|
|
158
|
+
] })
|
|
159
|
+
] })
|
|
160
|
+
] });
|
|
161
|
+
}
|
|
162
|
+
function FeedbackList({ items, onVote, votedIds, onStatusChange }) {
|
|
163
|
+
if (items.length === 0) return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center py-16", children: [
|
|
164
|
+
/* @__PURE__ */ jsx("div", { className: "mb-3 text-5xl opacity-20", children: "\u{1F4AC}" }),
|
|
165
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-white/40", children: "No feedback yet" })
|
|
166
|
+
] });
|
|
167
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: items.map((item) => /* @__PURE__ */ jsx(FeedbackCard, { item, onVote, hasVoted: votedIds?.includes(item.id) }, item.id)) });
|
|
168
|
+
}
|
|
169
|
+
function FeedbackTypeSelector({ value, onChange }) {
|
|
170
|
+
return /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: FEEDBACK_TYPES.map((t) => {
|
|
171
|
+
const cfg = TYPE_CONFIG[t];
|
|
172
|
+
return /* @__PURE__ */ jsxs(
|
|
173
|
+
"button",
|
|
174
|
+
{
|
|
175
|
+
type: "button",
|
|
176
|
+
onClick: () => onChange(t),
|
|
177
|
+
className: `flex items-center gap-1.5 rounded-lg px-3 py-2 text-xs font-medium transition-all ${value === t ? "text-white" : "bg-white/5 text-white/50 hover:bg-white/10"}`,
|
|
178
|
+
style: value === t ? { background: cfg.color } : void 0,
|
|
179
|
+
children: [
|
|
180
|
+
/* @__PURE__ */ jsx("span", { children: cfg.icon }),
|
|
181
|
+
cfg.label
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
t
|
|
185
|
+
);
|
|
186
|
+
}) });
|
|
187
|
+
}
|
|
188
|
+
function FeedbackForm({ onSubmit, onClose }) {
|
|
189
|
+
const form = useFeedbackForm(onSubmit);
|
|
190
|
+
if (form.success) return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center py-8 text-center", children: [
|
|
191
|
+
/* @__PURE__ */ jsx("div", { className: "mb-3 text-4xl", children: "\u{1F389}" }),
|
|
192
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-white/90 mb-1", children: "Thank you!" }),
|
|
193
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-white/50 mb-4", children: "Your feedback has been submitted." }),
|
|
194
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
|
|
195
|
+
form.reset();
|
|
196
|
+
onClose?.();
|
|
197
|
+
}, className: "rounded-lg bg-indigo-600 px-4 py-2 text-xs font-medium text-white", children: "Done" })
|
|
198
|
+
] });
|
|
199
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
200
|
+
/* @__PURE__ */ jsx(FeedbackTypeSelector, { value: form.formData.type, onChange: (t) => form.setFormData((d) => ({ ...d, type: t })) }),
|
|
201
|
+
/* @__PURE__ */ jsx(
|
|
202
|
+
"input",
|
|
203
|
+
{
|
|
204
|
+
type: "text",
|
|
205
|
+
placeholder: "Short title for your feedback\u2026",
|
|
206
|
+
value: form.formData.title,
|
|
207
|
+
onChange: (e) => form.setFormData((d) => ({ ...d, title: e.target.value })),
|
|
208
|
+
className: "w-full rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white placeholder-white/30 outline-none focus:border-indigo-500/40"
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
/* @__PURE__ */ jsx(
|
|
212
|
+
"textarea",
|
|
213
|
+
{
|
|
214
|
+
placeholder: "Describe your feedback in detail\u2026",
|
|
215
|
+
value: form.formData.description,
|
|
216
|
+
onChange: (e) => form.setFormData((d) => ({ ...d, description: e.target.value })),
|
|
217
|
+
rows: 4,
|
|
218
|
+
className: "w-full rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white placeholder-white/30 outline-none focus:border-indigo-500/40 resize-none"
|
|
219
|
+
}
|
|
220
|
+
),
|
|
221
|
+
form.error && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-400", children: form.error }),
|
|
222
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
223
|
+
onClose && /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, className: "rounded-lg border border-white/10 px-4 py-2 text-xs text-white/50 hover:bg-white/5", children: "Cancel" }),
|
|
224
|
+
/* @__PURE__ */ jsx(
|
|
225
|
+
"button",
|
|
226
|
+
{
|
|
227
|
+
type: "button",
|
|
228
|
+
onClick: form.submit,
|
|
229
|
+
disabled: form.isSubmitting,
|
|
230
|
+
className: "rounded-lg bg-indigo-600 px-5 py-2 text-xs font-medium text-white hover:bg-indigo-500 disabled:opacity-50",
|
|
231
|
+
children: form.isSubmitting ? "Submitting\u2026" : "Submit"
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
] })
|
|
235
|
+
] });
|
|
236
|
+
}
|
|
237
|
+
function FeedbackWidget({ onSubmit, position = "right" }) {
|
|
238
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
239
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
240
|
+
/* @__PURE__ */ jsx(
|
|
241
|
+
"button",
|
|
242
|
+
{
|
|
243
|
+
type: "button",
|
|
244
|
+
onClick: () => setIsOpen(true),
|
|
245
|
+
className: `fixed top-1/2 -translate-y-1/2 z-40 rounded-l-xl bg-indigo-600 px-2 py-4 text-white shadow-lg hover:bg-indigo-500 transition-all ${position === "right" ? "right-0" : "left-0 rounded-l-none rounded-r-xl"}`,
|
|
246
|
+
style: { writingMode: "vertical-rl" },
|
|
247
|
+
children: /* @__PURE__ */ jsx("span", { className: "text-xs font-medium tracking-wider", children: "Feedback" })
|
|
248
|
+
}
|
|
249
|
+
),
|
|
250
|
+
isOpen && /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex justify-end", onClick: () => setIsOpen(false), children: [
|
|
251
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm" }),
|
|
252
|
+
/* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-md bg-[#0f0f17] border-l border-white/10 p-6 overflow-y-auto", onClick: (e) => e.stopPropagation(), children: [
|
|
253
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
254
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: "Send Feedback" }),
|
|
255
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setIsOpen(false), className: "rounded-lg p-1.5 text-white/30 hover:bg-white/5 hover:text-white/50", children: "\u2715" })
|
|
256
|
+
] }),
|
|
257
|
+
/* @__PURE__ */ jsx(FeedbackForm, { onSubmit, onClose: () => setIsOpen(false) })
|
|
258
|
+
] })
|
|
259
|
+
] })
|
|
260
|
+
] });
|
|
261
|
+
}
|
|
262
|
+
function NPSSurvey({ onSubmit, onDismiss }) {
|
|
263
|
+
const [score, setScore] = useState(null);
|
|
264
|
+
const [comment, setComment] = useState("");
|
|
265
|
+
const [step, setStep] = useState("score");
|
|
266
|
+
const handleScore = (s) => {
|
|
267
|
+
setScore(s);
|
|
268
|
+
setStep("comment");
|
|
269
|
+
};
|
|
270
|
+
const handleSubmit = () => {
|
|
271
|
+
if (score !== null) {
|
|
272
|
+
onSubmit(score, comment || void 0);
|
|
273
|
+
setStep("thanks");
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed bottom-6 right-6 z-50 w-96 rounded-2xl border border-white/10 bg-[#111118] p-6 shadow-2xl", children: [
|
|
277
|
+
step === "score" && /* @__PURE__ */ jsxs("div", { children: [
|
|
278
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between", children: [
|
|
279
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-white/90", children: "How likely are you to recommend us?" }),
|
|
280
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onDismiss, className: "text-white/20 hover:text-white/40", children: "\u2715" })
|
|
281
|
+
] }),
|
|
282
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-white/40 mb-4", children: "0 = Not at all, 10 = Extremely likely" }),
|
|
283
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-1", children: Array.from({ length: 11 }).map((_, i) => /* @__PURE__ */ jsx(
|
|
284
|
+
"button",
|
|
285
|
+
{
|
|
286
|
+
type: "button",
|
|
287
|
+
onClick: () => handleScore(i),
|
|
288
|
+
className: `flex-1 rounded-lg py-2.5 text-xs font-bold transition-all ${i <= 6 ? "hover:bg-red-500/20 hover:text-red-400" : i <= 8 ? "hover:bg-amber-500/20 hover:text-amber-400" : "hover:bg-emerald-500/20 hover:text-emerald-400"} bg-white/5 text-white/50`,
|
|
289
|
+
children: i
|
|
290
|
+
},
|
|
291
|
+
i
|
|
292
|
+
)) }),
|
|
293
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2 flex justify-between text-[10px] text-white/20", children: [
|
|
294
|
+
/* @__PURE__ */ jsx("span", { children: "Not likely" }),
|
|
295
|
+
/* @__PURE__ */ jsx("span", { children: "Very likely" })
|
|
296
|
+
] })
|
|
297
|
+
] }),
|
|
298
|
+
step === "comment" && /* @__PURE__ */ jsxs("div", { children: [
|
|
299
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-white/90 mb-1", children: "Thanks! Any additional thoughts?" }),
|
|
300
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-white/40 mb-3", children: [
|
|
301
|
+
"Score: ",
|
|
302
|
+
/* @__PURE__ */ jsx("span", { className: `font-bold ${score <= 6 ? "text-red-400" : score <= 8 ? "text-amber-400" : "text-emerald-400"}`, children: score })
|
|
303
|
+
] }),
|
|
304
|
+
/* @__PURE__ */ jsx("textarea", { value: comment, onChange: (e) => setComment(e.target.value), placeholder: "Optional feedback\u2026", rows: 3, className: "w-full rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-sm text-white placeholder-white/30 outline-none resize-none mb-3" }),
|
|
305
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
306
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: handleSubmit, className: "flex-1 rounded-lg bg-indigo-600 py-2 text-xs font-medium text-white hover:bg-indigo-500", children: "Submit" }),
|
|
307
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
|
|
308
|
+
onSubmit(score, void 0);
|
|
309
|
+
setStep("thanks");
|
|
310
|
+
}, className: "rounded-lg border border-white/10 px-3 py-2 text-xs text-white/50", children: "Skip" })
|
|
311
|
+
] })
|
|
312
|
+
] }),
|
|
313
|
+
step === "thanks" && /* @__PURE__ */ jsxs("div", { className: "text-center py-4", children: [
|
|
314
|
+
/* @__PURE__ */ jsx("div", { className: "text-3xl mb-2", children: "\u{1F389}" }),
|
|
315
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-white/80", children: "Thank you for your feedback!" })
|
|
316
|
+
] })
|
|
317
|
+
] });
|
|
318
|
+
}
|
|
319
|
+
function NPSResults({ responses }) {
|
|
320
|
+
const stats = calcNPSStats(responses);
|
|
321
|
+
const total = stats.totalResponses || 1;
|
|
322
|
+
const scoreColor = stats.npsScore >= 50 ? "text-emerald-400" : stats.npsScore >= 0 ? "text-amber-400" : "text-red-400";
|
|
323
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-white/8 bg-white/[0.02] p-5", children: [
|
|
324
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center justify-between", children: [
|
|
325
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-white/80", children: "NPS Score" }),
|
|
326
|
+
/* @__PURE__ */ jsx("span", { className: `text-3xl font-black tabular-nums ${scoreColor}`, children: stats.npsScore })
|
|
327
|
+
] }),
|
|
328
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4 flex h-5 overflow-hidden rounded-full", children: [
|
|
329
|
+
/* @__PURE__ */ jsx("div", { className: "bg-red-500/70", style: { width: `${stats.detractors / total * 100}%` }, title: `Detractors: ${stats.detractors}` }),
|
|
330
|
+
/* @__PURE__ */ jsx("div", { className: "bg-amber-500/70", style: { width: `${stats.passives / total * 100}%` }, title: `Passives: ${stats.passives}` }),
|
|
331
|
+
/* @__PURE__ */ jsx("div", { className: "bg-emerald-500/70", style: { width: `${stats.promoters / total * 100}%` }, title: `Promoters: ${stats.promoters}` })
|
|
332
|
+
] }),
|
|
333
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2 text-center text-xs", children: [
|
|
334
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
335
|
+
/* @__PURE__ */ jsx("span", { className: "block text-red-400 font-bold", children: stats.detractors }),
|
|
336
|
+
/* @__PURE__ */ jsx("span", { className: "text-white/30", children: "Detractors" })
|
|
337
|
+
] }),
|
|
338
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
339
|
+
/* @__PURE__ */ jsx("span", { className: "block text-amber-400 font-bold", children: stats.passives }),
|
|
340
|
+
/* @__PURE__ */ jsx("span", { className: "text-white/30", children: "Passives" })
|
|
341
|
+
] }),
|
|
342
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
343
|
+
/* @__PURE__ */ jsx("span", { className: "block text-emerald-400 font-bold", children: stats.promoters }),
|
|
344
|
+
/* @__PURE__ */ jsx("span", { className: "text-white/30", children: "Promoters" })
|
|
345
|
+
] })
|
|
346
|
+
] }),
|
|
347
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 pt-3 border-t border-white/5 flex justify-between text-xs text-white/40", children: [
|
|
348
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
349
|
+
"Avg: ",
|
|
350
|
+
stats.averageScore,
|
|
351
|
+
"/10"
|
|
352
|
+
] }),
|
|
353
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
354
|
+
stats.totalResponses,
|
|
355
|
+
" responses"
|
|
356
|
+
] })
|
|
357
|
+
] })
|
|
358
|
+
] });
|
|
359
|
+
}
|
|
360
|
+
function FeedbackPublicPage({ items, onVote, votedIds }) {
|
|
361
|
+
const feedback = useFeedback(items);
|
|
362
|
+
const [activeType, setActiveType] = useState("all");
|
|
363
|
+
if (feedback.isLoading) return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-[#090a0f] px-6 py-12", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-3xl", children: [
|
|
364
|
+
/* @__PURE__ */ jsx("div", { className: "mb-8 h-10 w-48 animate-pulse rounded-lg bg-white/5" }),
|
|
365
|
+
/* @__PURE__ */ jsx("div", { className: "mb-6 flex gap-2", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-9 w-24 animate-pulse rounded-lg bg-white/5" }, i)) }),
|
|
366
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-3", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-24 animate-pulse rounded-xl bg-white/5" }, i)) })
|
|
367
|
+
] }) });
|
|
368
|
+
return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-[#090a0f] text-white", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-3xl px-6 py-12", children: [
|
|
369
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-8", children: [
|
|
370
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-tight mb-1", children: "Feedback Board" }),
|
|
371
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-white/40", children: [
|
|
372
|
+
feedback.stats.total,
|
|
373
|
+
" submissions \xB7 ",
|
|
374
|
+
feedback.stats.byStatus.open,
|
|
375
|
+
" open"
|
|
376
|
+
] })
|
|
377
|
+
] }),
|
|
378
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex flex-wrap items-center gap-3", children: [
|
|
379
|
+
/* @__PURE__ */ jsx(
|
|
380
|
+
"input",
|
|
381
|
+
{
|
|
382
|
+
type: "text",
|
|
383
|
+
placeholder: "Search feedback\u2026",
|
|
384
|
+
value: feedback.filter.search ?? "",
|
|
385
|
+
onChange: (e) => feedback.setFilter({ ...feedback.filter, search: e.target.value || void 0 }),
|
|
386
|
+
className: "w-56 rounded-xl border border-white/10 bg-white/5 px-4 py-2.5 text-sm text-white placeholder-white/30 outline-none focus:border-indigo-500/40"
|
|
387
|
+
}
|
|
388
|
+
),
|
|
389
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
|
|
390
|
+
/* @__PURE__ */ jsxs(
|
|
391
|
+
"button",
|
|
392
|
+
{
|
|
393
|
+
type: "button",
|
|
394
|
+
onClick: () => {
|
|
395
|
+
setActiveType("all");
|
|
396
|
+
feedback.setFilter({ ...feedback.filter, type: void 0 });
|
|
397
|
+
},
|
|
398
|
+
className: `rounded-lg px-3 py-1.5 text-xs font-medium ${activeType === "all" ? "bg-indigo-600 text-white" : "bg-white/5 text-white/50 hover:bg-white/10"}`,
|
|
399
|
+
children: [
|
|
400
|
+
"All (",
|
|
401
|
+
feedback.stats.total,
|
|
402
|
+
")"
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
),
|
|
406
|
+
FEEDBACK_TYPES.map((t) => /* @__PURE__ */ jsxs(
|
|
407
|
+
"button",
|
|
408
|
+
{
|
|
409
|
+
type: "button",
|
|
410
|
+
onClick: () => {
|
|
411
|
+
setActiveType(t);
|
|
412
|
+
feedback.setFilter({ ...feedback.filter, type: t });
|
|
413
|
+
},
|
|
414
|
+
className: `rounded-lg px-3 py-1.5 text-xs font-medium ${activeType === t ? "text-white" : "bg-white/5 text-white/50 hover:bg-white/10"}`,
|
|
415
|
+
style: activeType === t ? { background: TYPE_CONFIG[t].color } : void 0,
|
|
416
|
+
children: [
|
|
417
|
+
TYPE_CONFIG[t].icon,
|
|
418
|
+
" ",
|
|
419
|
+
TYPE_CONFIG[t].label,
|
|
420
|
+
" (",
|
|
421
|
+
feedback.stats.byType[t],
|
|
422
|
+
")"
|
|
423
|
+
]
|
|
424
|
+
},
|
|
425
|
+
t
|
|
426
|
+
))
|
|
427
|
+
] }),
|
|
428
|
+
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex gap-1.5", children: [
|
|
429
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => feedback.setFilter({ ...feedback.filter, sort: "newest" }), className: `rounded-lg px-3 py-1.5 text-xs ${feedback.filter.sort === "newest" ? "bg-white/10 text-white/80" : "text-white/40"}`, children: "Newest" }),
|
|
430
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => feedback.setFilter({ ...feedback.filter, sort: "votes" }), className: `rounded-lg px-3 py-1.5 text-xs ${feedback.filter.sort === "votes" ? "bg-white/10 text-white/80" : "text-white/40"}`, children: "Top voted" })
|
|
431
|
+
] })
|
|
432
|
+
] }),
|
|
433
|
+
/* @__PURE__ */ jsx(FeedbackList, { items: feedback.items, onVote, votedIds })
|
|
434
|
+
] }) });
|
|
435
|
+
}
|
|
436
|
+
function FeedbackAdminPage({ items, npsResponses, mutations }) {
|
|
437
|
+
const admin = useFeedbackAdmin(items, mutations);
|
|
438
|
+
if (admin.isLoading) return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-[#090a0f] px-6 py-12", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl", children: [
|
|
439
|
+
/* @__PURE__ */ jsx("div", { className: "mb-8 h-10 w-48 animate-pulse rounded-lg bg-white/5" }),
|
|
440
|
+
/* @__PURE__ */ jsx("div", { className: "mb-6 grid grid-cols-4 gap-4", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-20 animate-pulse rounded-xl bg-white/5" }, i)) }),
|
|
441
|
+
/* @__PURE__ */ jsx("div", { className: "h-96 animate-pulse rounded-xl bg-white/5" })
|
|
442
|
+
] }) });
|
|
443
|
+
return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-[#090a0f] text-white", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl px-6 py-12", children: [
|
|
444
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-tight mb-8", children: "Feedback Admin" }),
|
|
445
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-8 grid grid-cols-2 gap-4 md:grid-cols-5", children: [
|
|
446
|
+
/* @__PURE__ */ jsx(StatCard, { label: "Total", value: admin.stats.total, icon: "\u{1F4CA}" }),
|
|
447
|
+
/* @__PURE__ */ jsx(StatCard, { label: "Open", value: admin.stats.byStatus.open, icon: "\u{1F4EC}", color: "text-blue-400" }),
|
|
448
|
+
/* @__PURE__ */ jsx(StatCard, { label: "Planned", value: admin.stats.byStatus.planned, icon: "\u{1F4CB}", color: "text-purple-300" }),
|
|
449
|
+
/* @__PURE__ */ jsx(StatCard, { label: "In Progress", value: admin.stats.byStatus["in-progress"], icon: "\u{1F6A7}", color: "text-indigo-300" }),
|
|
450
|
+
/* @__PURE__ */ jsx(StatCard, { label: "Done", value: admin.stats.byStatus.done, icon: "\u2705", color: "text-emerald-400" })
|
|
451
|
+
] }),
|
|
452
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-6 lg:grid-cols-3", children: [
|
|
453
|
+
/* @__PURE__ */ jsxs("div", { className: "lg:col-span-2", children: [
|
|
454
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-3", children: [
|
|
455
|
+
/* @__PURE__ */ jsx("input", { type: "text", placeholder: "Search\u2026", value: admin.search, onChange: (e) => admin.setSearch(e.target.value), className: "w-64 rounded-xl border border-white/10 bg-white/5 px-4 py-2.5 text-sm text-white placeholder-white/30 outline-none focus:border-indigo-500/40" }),
|
|
456
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-white/30", children: [
|
|
457
|
+
admin.items.length,
|
|
458
|
+
" items"
|
|
459
|
+
] })
|
|
460
|
+
] }),
|
|
461
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-xl border border-white/8", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
|
|
462
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-white/8 bg-white/[0.02]", children: [
|
|
463
|
+
/* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase", children: "Type" }),
|
|
464
|
+
/* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase", children: "Title" }),
|
|
465
|
+
/* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase", children: "Status" }),
|
|
466
|
+
/* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase", children: "Priority" }),
|
|
467
|
+
/* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase", children: "Votes" }),
|
|
468
|
+
/* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase", children: "Date" }),
|
|
469
|
+
/* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right text-xs font-medium text-white/40 uppercase", children: "Actions" })
|
|
470
|
+
] }) }),
|
|
471
|
+
/* @__PURE__ */ jsx("tbody", { className: "divide-y divide-white/5", children: admin.items.map((item) => /* @__PURE__ */ jsxs("tr", { className: "group hover:bg-white/[0.02]", children: [
|
|
472
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(TypeBadge, { type: item.type }) }),
|
|
473
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3 max-w-xs truncate text-white/80 font-medium", children: item.title }),
|
|
474
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsx("select", { value: item.status, onChange: (e) => admin.updateStatus(item.id, e.target.value), className: "rounded border border-white/10 bg-white/5 px-1.5 py-0.5 text-xs text-white outline-none", children: FEEDBACK_STATUSES.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: STATUS_CONFIG[s].label }, s)) }) }),
|
|
475
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsx("select", { value: item.priority, onChange: (e) => admin.updatePriority(item.id, e.target.value), className: "rounded border border-white/10 bg-white/5 px-1.5 py-0.5 text-xs text-white outline-none", children: FEEDBACK_PRIORITIES.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: PRIORITY_CONFIG[p].label }, p)) }) }),
|
|
476
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3 tabular-nums text-white/50", children: item.votes }),
|
|
477
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-xs text-white/30", children: formatRelativeTime(item.createdAt) }),
|
|
478
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-right", children: /* @__PURE__ */ jsx("button", { type: "button", onClick: () => admin.deleteFeedback(item.id), className: "rounded px-2 py-1 text-xs text-white/20 hover:text-red-400 hover:bg-red-500/10 opacity-0 group-hover:opacity-100", children: "Delete" }) })
|
|
479
|
+
] }, item.id)) })
|
|
480
|
+
] }) })
|
|
481
|
+
] }),
|
|
482
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
483
|
+
npsResponses && /* @__PURE__ */ jsx(NPSResults, { responses: npsResponses }),
|
|
484
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-white/8 bg-white/[0.02] p-5", children: [
|
|
485
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-white/80 mb-3", children: "By Type" }),
|
|
486
|
+
Object.entries(admin.stats.byType).map(([t, c]) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between mb-1.5", children: [
|
|
487
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-white/50 capitalize", children: [
|
|
488
|
+
TYPE_CONFIG[t]?.icon,
|
|
489
|
+
" ",
|
|
490
|
+
t
|
|
491
|
+
] }),
|
|
492
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-bold text-white/70", children: c })
|
|
493
|
+
] }, t))
|
|
494
|
+
] })
|
|
495
|
+
] })
|
|
496
|
+
] })
|
|
497
|
+
] }) });
|
|
498
|
+
}
|
|
499
|
+
function StatCard({ label, value, icon, color }) {
|
|
500
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-white/8 bg-white/[0.02] p-4", children: [
|
|
501
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
502
|
+
/* @__PURE__ */ jsx("span", { className: "text-lg", children: icon }),
|
|
503
|
+
/* @__PURE__ */ jsx("span", { className: `text-2xl font-bold tabular-nums ${color ?? "text-white/90"}`, children: value })
|
|
504
|
+
] }),
|
|
505
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-white/40", children: label })
|
|
506
|
+
] });
|
|
507
|
+
}
|
|
508
|
+
function FeedbackWidgetPage({ onSubmit }) {
|
|
509
|
+
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-[#090a0f] text-white flex items-center justify-center", children: [
|
|
510
|
+
/* @__PURE__ */ jsxs("div", { className: "text-center max-w-md", children: [
|
|
511
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold mb-2", children: "Feedback Widget" }),
|
|
512
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-white/40 mb-8", children: 'Click the floating "Feedback" tab on the right edge to open the feedback form.' }),
|
|
513
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-white/8 bg-white/[0.02] p-6 text-left", children: [
|
|
514
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-white/80 mb-3", children: "Features" }),
|
|
515
|
+
/* @__PURE__ */ jsxs("ul", { className: "space-y-2 text-xs text-white/50", children: [
|
|
516
|
+
/* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
|
|
517
|
+
/* @__PURE__ */ jsx("span", { className: "text-emerald-400", children: "\u2713" }),
|
|
518
|
+
" Floating edge tab trigger"
|
|
519
|
+
] }),
|
|
520
|
+
/* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
|
|
521
|
+
/* @__PURE__ */ jsx("span", { className: "text-emerald-400", children: "\u2713" }),
|
|
522
|
+
" Type selector (Bug/Feature/Suggestion/General)"
|
|
523
|
+
] }),
|
|
524
|
+
/* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
|
|
525
|
+
/* @__PURE__ */ jsx("span", { className: "text-emerald-400", children: "\u2713" }),
|
|
526
|
+
" Title + description form with validation"
|
|
527
|
+
] }),
|
|
528
|
+
/* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
|
|
529
|
+
/* @__PURE__ */ jsx("span", { className: "text-emerald-400", children: "\u2713" }),
|
|
530
|
+
" Auto-captures page URL + browser"
|
|
531
|
+
] }),
|
|
532
|
+
/* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
|
|
533
|
+
/* @__PURE__ */ jsx("span", { className: "text-emerald-400", children: "\u2713" }),
|
|
534
|
+
" Success confirmation"
|
|
535
|
+
] })
|
|
536
|
+
] })
|
|
537
|
+
] })
|
|
538
|
+
] }),
|
|
539
|
+
/* @__PURE__ */ jsx(FeedbackWidget, { onSubmit })
|
|
540
|
+
] });
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export { FeedbackAdminPage, FeedbackCard, FeedbackForm, FeedbackList, FeedbackProvider, FeedbackPublicPage, FeedbackTypeSelector, FeedbackWidget, FeedbackWidgetPage, NPSResults, NPSSurvey, PriorityBadge, StatusBadge, TypeBadge, useFeedback, useFeedbackAdmin, useFeedbackConfig, useFeedbackForm, useNPS };
|
|
544
|
+
//# sourceMappingURL=index.js.map
|
|
545
|
+
//# sourceMappingURL=index.js.map
|