@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.
Files changed (104) hide show
  1. package/package.json +16 -3
  2. package/packages/convex/dist/index.d.ts +192 -0
  3. package/packages/convex/dist/index.js +239 -0
  4. package/packages/convex/dist/index.js.map +1 -0
  5. package/packages/react/README.md +1 -1
  6. package/packages/react/dist/index.d.ts +146 -0
  7. package/packages/react/dist/index.js +545 -0
  8. package/packages/react/dist/index.js.map +1 -0
  9. package/packages/react-css/README.md +1 -1
  10. package/packages/react-css/dist/index.css +965 -0
  11. package/packages/react-css/dist/index.css.map +1 -0
  12. package/packages/react-css/dist/index.d.ts +49 -0
  13. package/packages/react-css/dist/index.js +228 -0
  14. package/packages/react-css/dist/index.js.map +1 -0
  15. package/packages/shared/README.md +1 -1
  16. package/packages/shared/dist/index.d.ts +115 -0
  17. package/packages/shared/dist/index.js +112 -0
  18. package/packages/shared/dist/index.js.map +1 -0
  19. package/packages/solidjs/README.md +1 -1
  20. package/packages/solidjs/dist/index.d.ts +128 -0
  21. package/packages/solidjs/dist/index.js +289 -0
  22. package/packages/solidjs/dist/index.js.map +1 -0
  23. package/packages/solidjs-css/README.md +1 -1
  24. package/packages/solidjs-css/dist/index.css +965 -0
  25. package/packages/solidjs-css/dist/index.css.map +1 -0
  26. package/packages/solidjs-css/dist/index.d.ts +2 -0
  27. package/packages/solidjs-css/dist/index.js +29 -0
  28. package/packages/solidjs-css/dist/index.js.map +1 -0
  29. package/.changeset/config.json +0 -11
  30. package/.github/CODEOWNERS +0 -1
  31. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
  32. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
  33. package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
  34. package/.github/dependabot.yml +0 -11
  35. package/.github/workflows/ci.yml +0 -23
  36. package/.github/workflows/release.yml +0 -29
  37. package/.nvmrc +0 -1
  38. package/.project/ACCOUNT.yaml +0 -4
  39. package/.project/IDEAS.yaml +0 -7
  40. package/.project/PROJECT.yaml +0 -11
  41. package/.project/ROADMAP.yaml +0 -15
  42. package/CODE_OF_CONDUCT.md +0 -16
  43. package/CONTRIBUTING.md +0 -26
  44. package/SECURITY.md +0 -15
  45. package/SUPPORT.md +0 -8
  46. package/packages/convex/package.json +0 -42
  47. package/packages/convex/src/index.ts +0 -3
  48. package/packages/convex/src/mutations.ts +0 -88
  49. package/packages/convex/src/queries.ts +0 -78
  50. package/packages/convex/src/schema.ts +0 -47
  51. package/packages/convex/tsconfig.json +0 -18
  52. package/packages/convex/tsup.config.ts +0 -17
  53. package/packages/react/package.json +0 -49
  54. package/packages/react/src/components/FeedbackCard.tsx +0 -51
  55. package/packages/react/src/components/FeedbackForm.tsx +0 -43
  56. package/packages/react/src/components/FeedbackWidget.tsx +0 -32
  57. package/packages/react/src/components/NPSSurvey.tsx +0 -62
  58. package/packages/react/src/components/index.ts +0 -4
  59. package/packages/react/src/hooks/index.ts +0 -5
  60. package/packages/react/src/hooks/useFeedback.ts +0 -23
  61. package/packages/react/src/hooks/useFeedbackAdmin.ts +0 -24
  62. package/packages/react/src/hooks/useFeedbackForm.ts +0 -35
  63. package/packages/react/src/hooks/useNPS.ts +0 -26
  64. package/packages/react/src/index.tsx +0 -13
  65. package/packages/react/src/pages/FeedbackAdminPage.tsx +0 -71
  66. package/packages/react/src/pages/FeedbackPublicPage.tsx +0 -42
  67. package/packages/react/src/pages/FeedbackWidgetPage.tsx +0 -25
  68. package/packages/react/src/pages/index.ts +0 -3
  69. package/packages/react/tsconfig.json +0 -19
  70. package/packages/react/tsup.config.ts +0 -12
  71. package/packages/react-css/package.json +0 -36
  72. package/packages/react-css/src/components/index.ts +0 -5
  73. package/packages/react-css/src/components/index.tsx +0 -107
  74. package/packages/react-css/src/hooks/index.ts +0 -2
  75. package/packages/react-css/src/index.tsx +0 -5
  76. package/packages/react-css/src/pages/FeedbackAdminPage.tsx +0 -112
  77. package/packages/react-css/src/pages/FeedbackPage.tsx +0 -76
  78. package/packages/react-css/src/styles.css +0 -281
  79. package/packages/react-css/tsconfig.json +0 -19
  80. package/packages/react-css/tsup.config.ts +0 -10
  81. package/packages/shared/package.json +0 -44
  82. package/packages/shared/src/__tests__/feedback.test.ts +0 -72
  83. package/packages/shared/src/config.ts +0 -49
  84. package/packages/shared/src/index.ts +0 -111
  85. package/packages/shared/src/types.ts +0 -59
  86. package/packages/shared/tsconfig.json +0 -18
  87. package/packages/shared/tsup.config.ts +0 -11
  88. package/packages/shared/vitest.config.ts +0 -4
  89. package/packages/solidjs/package.json +0 -45
  90. package/packages/solidjs/src/components.tsx +0 -72
  91. package/packages/solidjs/src/index.tsx +0 -3
  92. package/packages/solidjs/src/primitives.ts +0 -49
  93. package/packages/solidjs/tsconfig.json +0 -20
  94. package/packages/solidjs/tsup.config.ts +0 -12
  95. package/packages/solidjs-css/package.json +0 -32
  96. package/packages/solidjs-css/src/index.tsx +0 -4
  97. package/packages/solidjs-css/src/pages/FeedbackAdminPage.tsx +0 -78
  98. package/packages/solidjs-css/src/pages/FeedbackPage.tsx +0 -65
  99. package/packages/solidjs-css/src/primitives/index.ts +0 -1
  100. package/packages/solidjs-css/src/styles.css +0 -281
  101. package/packages/solidjs-css/tsconfig.json +0 -20
  102. package/packages/solidjs-css/tsup.config.ts +0 -10
  103. package/pnpm-workspace.yaml +0 -2
  104. package/tsconfig.json +0 -23
@@ -0,0 +1,112 @@
1
+ // src/config.ts
2
+ function configureFeedback(options = {}) {
3
+ return {
4
+ floatingWidget: options.floatingWidget ?? true,
5
+ npsEnabled: options.npsEnabled ?? true,
6
+ npsInterval: options.npsInterval ?? 7,
7
+ categories: options.categories ?? ["bug", "feature", "general", "suggestion"],
8
+ allowAnonymous: options.allowAnonymous ?? true
9
+ };
10
+ }
11
+ var defaultFeedbackConfig = {
12
+ floatingWidget: true,
13
+ npsEnabled: true,
14
+ npsInterval: 7,
15
+ categories: ["bug", "feature", "general", "suggestion"],
16
+ allowAnonymous: true
17
+ };
18
+
19
+ // src/index.ts
20
+ var STATUS_CONFIG = {
21
+ open: { label: "Open", color: "oklch(0.65 0.20 245)", emoji: "\u{1F4EC}" },
22
+ "under-review": { label: "Under Review", color: "oklch(0.72 0.18 60)", emoji: "\u{1F50D}" },
23
+ planned: { label: "Planned", color: "oklch(0.70 0.22 280)", emoji: "\u{1F4CB}" },
24
+ "in-progress": { label: "In Progress", color: "oklch(0.65 0.20 265)", emoji: "\u{1F6A7}" },
25
+ done: { label: "Done", color: "oklch(0.72 0.18 155)", emoji: "\u2705" },
26
+ declined: { label: "Declined", color: "oklch(0.50 0.10 250)", emoji: "\u{1F6AB}" }
27
+ };
28
+ var PRIORITY_CONFIG = {
29
+ low: { label: "Low", color: "oklch(0.58 0.10 250)" },
30
+ medium: { label: "Medium", color: "oklch(0.72 0.18 60)" },
31
+ high: { label: "High", color: "oklch(0.68 0.22 35)" },
32
+ critical: { label: "Critical", color: "oklch(0.60 0.25 25)" }
33
+ };
34
+ var TYPE_CONFIG = {
35
+ bug: { label: "Bug", icon: "\u{1F41B}", color: "oklch(0.60 0.25 25)" },
36
+ feature: { label: "Feature", icon: "\u2728", color: "oklch(0.70 0.22 280)" },
37
+ suggestion: { label: "Suggestion", icon: "\u{1F4A1}", color: "oklch(0.72 0.18 60)" },
38
+ general: { label: "General", icon: "\u{1F4AC}", color: "oklch(0.65 0.20 245)" }
39
+ };
40
+ var FEEDBACK_TYPES = ["bug", "feature", "suggestion", "general"];
41
+ var FEEDBACK_STATUSES = ["open", "under-review", "planned", "in-progress", "done", "declined"];
42
+ var FEEDBACK_PRIORITIES = ["low", "medium", "high", "critical"];
43
+ var STATUS_TRANSITIONS = {
44
+ open: ["under-review", "planned", "declined"],
45
+ "under-review": ["planned", "in-progress", "declined"],
46
+ planned: ["in-progress", "declined"],
47
+ "in-progress": ["done", "planned"],
48
+ done: ["open"],
49
+ declined: ["open"]
50
+ };
51
+ function getStatusTransitions(status) {
52
+ return STATUS_TRANSITIONS[status] ?? [];
53
+ }
54
+ function getNPSCategory(score) {
55
+ if (score <= 6) return "detractor";
56
+ if (score <= 8) return "passive";
57
+ return "promoter";
58
+ }
59
+ function calcNPSScore(responses) {
60
+ if (responses.length === 0) return 0;
61
+ let promoters = 0, detractors = 0;
62
+ for (const r of responses) {
63
+ const cat = getNPSCategory(r.score);
64
+ if (cat === "promoter") promoters++;
65
+ else if (cat === "detractor") detractors++;
66
+ }
67
+ return Math.round((promoters - detractors) / responses.length * 100);
68
+ }
69
+ function calcNPSStats(responses) {
70
+ let promoters = 0, passives = 0, detractors = 0, totalScore = 0;
71
+ for (const r of responses) {
72
+ totalScore += r.score;
73
+ const cat = getNPSCategory(r.score);
74
+ if (cat === "promoter") promoters++;
75
+ else if (cat === "passive") passives++;
76
+ else detractors++;
77
+ }
78
+ return {
79
+ averageScore: responses.length > 0 ? Math.round(totalScore / responses.length * 10) / 10 : 0,
80
+ totalResponses: responses.length,
81
+ promoters,
82
+ passives,
83
+ detractors,
84
+ npsScore: calcNPSScore(responses)
85
+ };
86
+ }
87
+ function calcFeedbackStats(items) {
88
+ const byType = { bug: 0, feature: 0, suggestion: 0, general: 0 };
89
+ const byStatus = { open: 0, "under-review": 0, planned: 0, "in-progress": 0, done: 0, declined: 0 };
90
+ const byPriority = { low: 0, medium: 0, high: 0, critical: 0 };
91
+ for (const item of items) {
92
+ byType[item.type]++;
93
+ byStatus[item.status]++;
94
+ byPriority[item.priority]++;
95
+ }
96
+ return { total: items.length, byType, byStatus, byPriority };
97
+ }
98
+ function formatRelativeTime(dateStr) {
99
+ const diff = Date.now() - new Date(dateStr).getTime();
100
+ const mins = Math.floor(diff / 6e4);
101
+ if (mins < 1) return "just now";
102
+ if (mins < 60) return `${mins}m ago`;
103
+ const hrs = Math.floor(mins / 60);
104
+ if (hrs < 24) return `${hrs}h ago`;
105
+ const days = Math.floor(hrs / 24);
106
+ if (days < 30) return `${days}d ago`;
107
+ return new Date(dateStr).toLocaleDateString();
108
+ }
109
+
110
+ export { FEEDBACK_PRIORITIES, FEEDBACK_STATUSES, FEEDBACK_TYPES, PRIORITY_CONFIG, STATUS_CONFIG, TYPE_CONFIG, calcFeedbackStats, calcNPSScore, calcNPSStats, configureFeedback, defaultFeedbackConfig, formatRelativeTime, getNPSCategory, getStatusTransitions };
111
+ //# sourceMappingURL=index.js.map
112
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @fileoverview Configuration factory for Geenius Feedback\n */\n\nimport type { FeedbackConfig } from './types'\n\nexport interface ConfigureFeedbackOptions {\n floatingWidget?: boolean\n npsEnabled?: boolean\n npsInterval?: number\n categories?: Array<'bug' | 'feature' | 'general' | 'suggestion'>\n allowAnonymous?: boolean\n}\n\n/**\n * Configure the feedback system with custom options\n * @param options Configuration options for the feedback system\n * @returns FeedbackConfig object ready for use\n * @example\n * ```ts\n * const config = configureFeedback({\n * floatingWidget: true,\n * npsEnabled: true,\n * npsInterval: 7,\n * categories: ['bug', 'feature', 'general'],\n * allowAnonymous: true,\n * })\n * ```\n */\nexport function configureFeedback(options: ConfigureFeedbackOptions = {}): FeedbackConfig {\n return {\n floatingWidget: options.floatingWidget ?? true,\n npsEnabled: options.npsEnabled ?? true,\n npsInterval: options.npsInterval ?? 7,\n categories: options.categories ?? ['bug', 'feature', 'general', 'suggestion'],\n allowAnonymous: options.allowAnonymous ?? true,\n }\n}\n\n/**\n * Default feedback configuration\n */\nexport const defaultFeedbackConfig: FeedbackConfig = {\n floatingWidget: true,\n npsEnabled: true,\n npsInterval: 7,\n categories: ['bug', 'feature', 'general', 'suggestion'],\n allowAnonymous: true,\n}\n","export type {\n FeedbackType, FeedbackStatus, FeedbackPriority, NPSCategory,\n Attachment, FeedbackItem, NPSResponse, FeedbackConfig,\n FeedbackStats, NPSStats,\n} from './types'\n\nexport { configureFeedback, defaultFeedbackConfig } from './config'\nexport type { ConfigureFeedbackOptions } from './config'\n\nimport type { FeedbackStatus, FeedbackPriority, FeedbackType, NPSResponse, FeedbackStats, NPSStats, FeedbackItem } from './types'\n\n// ─── Status Config ───────────────────────────────────\nexport const STATUS_CONFIG: Record<FeedbackStatus, { label: string; color: string; emoji: string }> = {\n open: { label: 'Open', color: 'oklch(0.65 0.20 245)', emoji: '📬' },\n 'under-review': { label: 'Under Review', color: 'oklch(0.72 0.18 60)', emoji: '🔍' },\n planned: { label: 'Planned', color: 'oklch(0.70 0.22 280)', emoji: '📋' },\n 'in-progress': { label: 'In Progress', color: 'oklch(0.65 0.20 265)', emoji: '🚧' },\n done: { label: 'Done', color: 'oklch(0.72 0.18 155)', emoji: '✅' },\n declined: { label: 'Declined', color: 'oklch(0.50 0.10 250)', emoji: '🚫' },\n}\n\nexport const PRIORITY_CONFIG: Record<FeedbackPriority, { label: string; color: string }> = {\n low: { label: 'Low', color: 'oklch(0.58 0.10 250)' },\n medium: { label: 'Medium', color: 'oklch(0.72 0.18 60)' },\n high: { label: 'High', color: 'oklch(0.68 0.22 35)' },\n critical: { label: 'Critical', color: 'oklch(0.60 0.25 25)' },\n}\n\nexport const TYPE_CONFIG: Record<FeedbackType, { label: string; icon: string; color: string }> = {\n bug: { label: 'Bug', icon: '🐛', color: 'oklch(0.60 0.25 25)' },\n feature: { label: 'Feature', icon: '✨', color: 'oklch(0.70 0.22 280)' },\n suggestion: { label: 'Suggestion', icon: '💡', color: 'oklch(0.72 0.18 60)' },\n general: { label: 'General', icon: '💬', color: 'oklch(0.65 0.20 245)' },\n}\n\nexport const FEEDBACK_TYPES: FeedbackType[] = ['bug', 'feature', 'suggestion', 'general']\nexport const FEEDBACK_STATUSES: FeedbackStatus[] = ['open', 'under-review', 'planned', 'in-progress', 'done', 'declined']\nexport const FEEDBACK_PRIORITIES: FeedbackPriority[] = ['low', 'medium', 'high', 'critical']\n\n// ─── Status Transitions ─────────────────────────────\nconst STATUS_TRANSITIONS: Record<FeedbackStatus, FeedbackStatus[]> = {\n open: ['under-review', 'planned', 'declined'],\n 'under-review': ['planned', 'in-progress', 'declined'],\n planned: ['in-progress', 'declined'],\n 'in-progress': ['done', 'planned'],\n done: ['open'],\n declined: ['open'],\n}\n\nexport function getStatusTransitions(status: FeedbackStatus): FeedbackStatus[] {\n return STATUS_TRANSITIONS[status] ?? []\n}\n\n// ─── NPS Utilities ───────────────────────────────────\nexport function getNPSCategory(score: number): 'detractor' | 'passive' | 'promoter' {\n if (score <= 6) return 'detractor'\n if (score <= 8) return 'passive'\n return 'promoter'\n}\n\nexport function calcNPSScore(responses: NPSResponse[]): number {\n if (responses.length === 0) return 0\n let promoters = 0, detractors = 0\n for (const r of responses) {\n const cat = getNPSCategory(r.score)\n if (cat === 'promoter') promoters++\n else if (cat === 'detractor') detractors++\n }\n return Math.round(((promoters - detractors) / responses.length) * 100)\n}\n\nexport function calcNPSStats(responses: NPSResponse[]): NPSStats {\n let promoters = 0, passives = 0, detractors = 0, totalScore = 0\n for (const r of responses) {\n totalScore += r.score\n const cat = getNPSCategory(r.score)\n if (cat === 'promoter') promoters++\n else if (cat === 'passive') passives++\n else detractors++\n }\n return {\n averageScore: responses.length > 0 ? Math.round((totalScore / responses.length) * 10) / 10 : 0,\n totalResponses: responses.length,\n promoters, passives, detractors,\n npsScore: calcNPSScore(responses),\n }\n}\n\nexport function calcFeedbackStats(items: FeedbackItem[]): FeedbackStats {\n const byType = { bug: 0, feature: 0, suggestion: 0, general: 0 }\n const byStatus = { open: 0, 'under-review': 0, planned: 0, 'in-progress': 0, done: 0, declined: 0 }\n const byPriority = { low: 0, medium: 0, high: 0, critical: 0 }\n for (const item of items) {\n byType[item.type]++\n byStatus[item.status]++\n byPriority[item.priority]++\n }\n return { total: items.length, byType, byStatus, byPriority }\n}\n\nexport function formatRelativeTime(dateStr: string): string {\n const diff = Date.now() - new Date(dateStr).getTime()\n const mins = Math.floor(diff / 60000)\n if (mins < 1) return 'just now'\n if (mins < 60) return `${mins}m ago`\n const hrs = Math.floor(mins / 60)\n if (hrs < 24) return `${hrs}h ago`\n const days = Math.floor(hrs / 24)\n if (days < 30) return `${days}d ago`\n return new Date(dateStr).toLocaleDateString()\n}\n"]}
@@ -1 +1 @@
1
- # ✦ @geenius-feedback/solidjs\n\n> Geenius Feedback — SolidJS components & primitives\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius-feedback/solidjs\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-feedback/solidjs';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
1
+ # ✦ @geenius/feedback-solidjs\n\n> Geenius Feedback — SolidJS components & primitives\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius/feedback-solidjs\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius/feedback-solidjs';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
@@ -0,0 +1,128 @@
1
+ import * as solid_js from 'solid-js';
2
+ import { JSX } from 'solid-js';
3
+ import { FeedbackConfig, FeedbackItem, FeedbackStatus, FeedbackPriority, FeedbackStats, FeedbackType, NPSResponse } from '@geenius/feedback-shared';
4
+ export { FeedbackConfig, FeedbackItem, FeedbackPriority, FeedbackStats, FeedbackStatus, FeedbackType, NPSResponse, NPSStats } from '@geenius/feedback-shared';
5
+
6
+ declare function FeedbackProvider(props: {
7
+ children: JSX.Element;
8
+ config?: Partial<FeedbackConfig>;
9
+ }): JSX.Element;
10
+ declare function useFeedbackConfig(): FeedbackConfig;
11
+
12
+ declare function createFeedback(items: () => FeedbackItem[] | undefined): {
13
+ items: solid_js.Accessor<FeedbackItem[]>;
14
+ stats: solid_js.Accessor<FeedbackStats>;
15
+ isLoading: solid_js.Accessor<boolean>;
16
+ typeFilter: solid_js.Accessor<FeedbackType | undefined>;
17
+ setTypeFilter: solid_js.Setter<FeedbackType | undefined>;
18
+ search: solid_js.Accessor<string>;
19
+ setSearch: solid_js.Setter<string>;
20
+ sort: solid_js.Accessor<"newest" | "votes">;
21
+ setSort: solid_js.Setter<"newest" | "votes">;
22
+ };
23
+ declare function createFeedbackForm(submitFn: (data: {
24
+ type: FeedbackType;
25
+ title: string;
26
+ description: string;
27
+ }) => Promise<string>): {
28
+ type: solid_js.Accessor<FeedbackType>;
29
+ setType: solid_js.Setter<FeedbackType>;
30
+ title: solid_js.Accessor<string>;
31
+ setTitle: solid_js.Setter<string>;
32
+ description: solid_js.Accessor<string>;
33
+ setDescription: solid_js.Setter<string>;
34
+ submit: () => Promise<void>;
35
+ isSubmitting: solid_js.Accessor<boolean>;
36
+ error: solid_js.Accessor<string | null>;
37
+ success: solid_js.Accessor<boolean>;
38
+ reset: () => void;
39
+ };
40
+ declare function createNPS(submitFn: (score: number, comment?: string) => Promise<void>, intervalDays?: number): {
41
+ submit: (score: number, comment?: string) => Promise<void>;
42
+ isSubmitting: solid_js.Accessor<boolean>;
43
+ showSurvey: solid_js.Accessor<boolean>;
44
+ dismiss: () => void;
45
+ };
46
+ declare function createFeedbackAdmin(items: () => FeedbackItem[] | undefined, mutations: {
47
+ updateStatus: (id: string, s: FeedbackStatus, note?: string) => Promise<void>;
48
+ updatePriority: (id: string, p: FeedbackPriority) => Promise<void>;
49
+ deleteFeedback: (id: string) => Promise<void>;
50
+ }): {
51
+ updateStatus: (id: string, s: FeedbackStatus, note?: string) => Promise<void>;
52
+ updatePriority: (id: string, p: FeedbackPriority) => Promise<void>;
53
+ deleteFeedback: (id: string) => Promise<void>;
54
+ items: solid_js.Accessor<FeedbackItem[]>;
55
+ stats: solid_js.Accessor<FeedbackStats>;
56
+ search: solid_js.Accessor<string>;
57
+ setSearch: solid_js.Setter<string>;
58
+ isLoading: solid_js.Accessor<boolean>;
59
+ };
60
+
61
+ declare function StatusBadge(props: {
62
+ status: FeedbackStatus;
63
+ }): solid_js.JSX.Element;
64
+ declare function PriorityBadge(props: {
65
+ priority: FeedbackPriority;
66
+ }): solid_js.JSX.Element;
67
+ declare function TypeBadge(props: {
68
+ type: FeedbackType;
69
+ }): solid_js.JSX.Element;
70
+ declare function FeedbackCard(props: {
71
+ item: FeedbackItem;
72
+ onVote?: (id: string) => void;
73
+ hasVoted?: boolean;
74
+ }): solid_js.JSX.Element;
75
+ declare function FeedbackWidget(props: {
76
+ onSubmit: (data: {
77
+ type: FeedbackType;
78
+ title: string;
79
+ description: string;
80
+ }) => Promise<string>;
81
+ }): solid_js.JSX.Element;
82
+ declare function NPSSurvey(props: {
83
+ onSubmit: (score: number, comment?: string) => void;
84
+ onDismiss: () => void;
85
+ }): solid_js.JSX.Element;
86
+ declare function FeedbackList(props: {
87
+ items: FeedbackItem[];
88
+ onVote?: (id: string) => void;
89
+ votedIds?: string[];
90
+ }): solid_js.JSX.Element;
91
+ declare function FeedbackTypeSelector(props: {
92
+ value: FeedbackType;
93
+ onChange: (t: FeedbackType) => void;
94
+ }): solid_js.JSX.Element;
95
+ declare function FeedbackForm(props: {
96
+ onSubmit: (data: {
97
+ type: FeedbackType;
98
+ title: string;
99
+ description: string;
100
+ }) => Promise<string>;
101
+ onClose?: () => void;
102
+ }): solid_js.JSX.Element;
103
+ declare function NPSResults(props: {
104
+ responses: NPSResponse[];
105
+ }): solid_js.JSX.Element;
106
+
107
+ declare function FeedbackPublicPage(props: {
108
+ items: FeedbackItem[] | undefined;
109
+ onVote?: (id: string) => void;
110
+ votedIds?: string[];
111
+ }): solid_js.JSX.Element;
112
+
113
+ interface FeedbackAdminPageProps {
114
+ items: FeedbackItem[] | undefined;
115
+ npsResponses?: NPSResponse[];
116
+ mutations: Parameters<typeof createFeedbackAdmin>[1];
117
+ }
118
+ declare function FeedbackAdminPage(props: FeedbackAdminPageProps): solid_js.JSX.Element;
119
+
120
+ declare function FeedbackWidgetPage(props: {
121
+ onSubmit: (data: {
122
+ type: FeedbackType;
123
+ title: string;
124
+ description: string;
125
+ }) => Promise<string>;
126
+ }): solid_js.JSX.Element;
127
+
128
+ export { FeedbackAdminPage, FeedbackCard, FeedbackForm, FeedbackList, FeedbackProvider, FeedbackPublicPage, FeedbackTypeSelector, FeedbackWidget, FeedbackWidgetPage, NPSResults, NPSSurvey, PriorityBadge, StatusBadge, TypeBadge, createFeedback, createFeedbackAdmin, createFeedbackForm, createNPS, useFeedbackConfig };
@@ -0,0 +1,289 @@
1
+ import { createContext, useContext, createSignal, createMemo, Show, For } from 'solid-js';
2
+ import { defaultFeedbackConfig, calcFeedbackStats, formatRelativeTime, FEEDBACK_TYPES, TYPE_CONFIG, FEEDBACK_STATUSES, STATUS_CONFIG, FEEDBACK_PRIORITIES, PRIORITY_CONFIG, calcNPSStats } from '@geenius/feedback-shared';
3
+
4
+ // src/provider/FeedbackProvider.tsx
5
+ var FeedbackContext = createContext(defaultFeedbackConfig);
6
+ function FeedbackProvider(props) {
7
+ const value = { ...defaultFeedbackConfig, ...props.config };
8
+ return /* @__PURE__ */ React.createElement(FeedbackContext.Provider, { value }, props.children);
9
+ }
10
+ function useFeedbackConfig() {
11
+ return useContext(FeedbackContext);
12
+ }
13
+ function createFeedback(items) {
14
+ const [typeFilter, setTypeFilter] = createSignal();
15
+ const [search, setSearch] = createSignal("");
16
+ const [sort, setSort] = createSignal("newest");
17
+ const isLoading = createMemo(() => items() === void 0);
18
+ const stats = createMemo(() => calcFeedbackStats(items() ?? []));
19
+ const filtered = createMemo(() => {
20
+ let r = items() ?? [];
21
+ const tf = typeFilter();
22
+ if (tf) r = r.filter((i) => i.type === tf);
23
+ const q = search().toLowerCase();
24
+ if (q) r = r.filter((i) => i.title.toLowerCase().includes(q) || i.description.toLowerCase().includes(q));
25
+ if (sort() === "votes") r = [...r].sort((a, b) => b.votes - a.votes);
26
+ return r;
27
+ });
28
+ return { items: filtered, stats, isLoading, typeFilter, setTypeFilter, search, setSearch, sort, setSort };
29
+ }
30
+ function createFeedbackForm(submitFn) {
31
+ const [type, setType] = createSignal("general");
32
+ const [title, setTitle] = createSignal("");
33
+ const [desc, setDesc] = createSignal("");
34
+ const [isSubmitting, setIsSubmitting] = createSignal(false);
35
+ const [error, setError] = createSignal(null);
36
+ const [success, setSuccess] = createSignal(false);
37
+ const submit = async () => {
38
+ if (!title().trim()) {
39
+ setError("Title required");
40
+ return;
41
+ }
42
+ if (desc().length < 10) {
43
+ setError("Description must be 10+ chars");
44
+ return;
45
+ }
46
+ setIsSubmitting(true);
47
+ setError(null);
48
+ try {
49
+ await submitFn({ type: type(), title: title(), description: desc() });
50
+ setSuccess(true);
51
+ setTitle("");
52
+ setDesc("");
53
+ } catch (e) {
54
+ setError(e instanceof Error ? e.message : "Failed");
55
+ } finally {
56
+ setIsSubmitting(false);
57
+ }
58
+ };
59
+ const reset = () => {
60
+ setType("general");
61
+ setTitle("");
62
+ setDesc("");
63
+ setError(null);
64
+ setSuccess(false);
65
+ };
66
+ return { type, setType, title, setTitle, description: desc, setDescription: setDesc, submit, isSubmitting, error, success, reset };
67
+ }
68
+ function createNPS(submitFn, intervalDays = 90) {
69
+ const NPS_KEY = "geenius-nps-last-shown";
70
+ const [isSubmitting, setIsSubmitting] = createSignal(false);
71
+ const shouldShow = () => {
72
+ if (typeof window === "undefined") return false;
73
+ const last = localStorage.getItem(NPS_KEY);
74
+ if (!last) return true;
75
+ const daysSince = (Date.now() - Number(last)) / (1e3 * 60 * 60 * 24);
76
+ return daysSince >= intervalDays;
77
+ };
78
+ const [showSurvey, setShowSurvey] = createSignal(shouldShow());
79
+ const submit = async (score, comment) => {
80
+ setIsSubmitting(true);
81
+ try {
82
+ await submitFn(score, comment);
83
+ localStorage.setItem(NPS_KEY, String(Date.now()));
84
+ setShowSurvey(false);
85
+ } finally {
86
+ setIsSubmitting(false);
87
+ }
88
+ };
89
+ const dismiss = () => {
90
+ setShowSurvey(false);
91
+ localStorage.setItem(NPS_KEY, String(Date.now()));
92
+ };
93
+ return { submit, isSubmitting, showSurvey, dismiss };
94
+ }
95
+ function createFeedbackAdmin(items, mutations) {
96
+ const [search, setSearch] = createSignal("");
97
+ const stats = createMemo(() => calcFeedbackStats(items() ?? []));
98
+ const filtered = createMemo(() => {
99
+ const q = search().toLowerCase();
100
+ return q ? (items() ?? []).filter((i) => i.title.toLowerCase().includes(q)) : items() ?? [];
101
+ });
102
+ return { items: filtered, stats, search, setSearch, isLoading: createMemo(() => items() === void 0), ...mutations };
103
+ }
104
+ function StatusBadge(props) {
105
+ const cfg = () => STATUS_CONFIG[props.status];
106
+ return /* @__PURE__ */ React.createElement("span", { class: "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 } }, /* @__PURE__ */ React.createElement("span", null, cfg().emoji), cfg().label);
107
+ }
108
+ function PriorityBadge(props) {
109
+ const cfg = () => PRIORITY_CONFIG[props.priority];
110
+ return /* @__PURE__ */ React.createElement("span", { class: "rounded-full px-2 py-0.5 text-[10px] font-medium", style: { background: `${cfg().color}22`, color: cfg().color } }, cfg().label);
111
+ }
112
+ function TypeBadge(props) {
113
+ const cfg = () => TYPE_CONFIG[props.type];
114
+ return /* @__PURE__ */ React.createElement("span", { class: "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 } }, /* @__PURE__ */ React.createElement("span", null, cfg().icon), cfg().label);
115
+ }
116
+ function FeedbackCard(props) {
117
+ return /* @__PURE__ */ React.createElement("div", { class: "group flex gap-3 rounded-xl border border-white/8 bg-white/[0.02] p-4 hover:border-indigo-500/20" }, /* @__PURE__ */ React.createElement(Show, { when: props.onVote }, /* @__PURE__ */ React.createElement(
118
+ "button",
119
+ {
120
+ type: "button",
121
+ onClick: () => props.onVote?.(props.item.id),
122
+ class: `flex flex-col items-center gap-0.5 rounded-lg px-2 py-1.5 text-xs font-bold ${props.hasVoted ? "bg-indigo-500/15 text-indigo-400" : "bg-white/5 text-white/30 hover:bg-white/10"}`
123
+ },
124
+ /* @__PURE__ */ React.createElement("span", { class: "text-sm" }, props.hasVoted ? "\u25B2" : "\u25B3"),
125
+ /* @__PURE__ */ React.createElement("span", null, props.item.votes)
126
+ )), /* @__PURE__ */ React.createElement("div", { class: "flex-1 min-w-0" }, /* @__PURE__ */ React.createElement("div", { class: "flex items-center gap-2 mb-1 flex-wrap" }, /* @__PURE__ */ React.createElement(TypeBadge, { type: props.item.type }), /* @__PURE__ */ React.createElement(StatusBadge, { status: props.item.status })), /* @__PURE__ */ React.createElement("h3", { class: "text-sm font-semibold text-white/90 mb-0.5 truncate" }, props.item.title), /* @__PURE__ */ React.createElement("p", { class: "text-xs text-white/40 line-clamp-2" }, props.item.description), /* @__PURE__ */ React.createElement("div", { class: "mt-2 text-[10px] text-white/25" }, formatRelativeTime(props.item.createdAt))));
127
+ }
128
+ function FeedbackWidget(props) {
129
+ const [isOpen, setIsOpen] = createSignal(false);
130
+ const [type, setType] = createSignal("general");
131
+ const [title, setTitle] = createSignal("");
132
+ const [desc, setDesc] = createSignal("");
133
+ const [submitting, setSubmitting] = createSignal(false);
134
+ const [done, setDone] = createSignal(false);
135
+ const submit = async () => {
136
+ setSubmitting(true);
137
+ try {
138
+ await props.onSubmit({ type: type(), title: title(), description: desc() });
139
+ setDone(true);
140
+ } finally {
141
+ setSubmitting(false);
142
+ }
143
+ };
144
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setIsOpen(true), class: "fixed top-1/2 right-0 -translate-y-1/2 z-40 rounded-l-xl bg-indigo-600 px-2 py-4 text-white shadow-lg hover:bg-indigo-500", style: { "writing-mode": "vertical-rl" } }, /* @__PURE__ */ React.createElement("span", { class: "text-xs font-medium tracking-wider" }, "Feedback")), /* @__PURE__ */ React.createElement(Show, { when: isOpen() }, /* @__PURE__ */ React.createElement("div", { class: "fixed inset-0 z-50 flex justify-end", onClick: () => setIsOpen(false) }, /* @__PURE__ */ React.createElement("div", { class: "absolute inset-0 bg-black/50 backdrop-blur-sm" }), /* @__PURE__ */ React.createElement("div", { class: "relative w-full max-w-md bg-[#0f0f17] border-l border-white/10 p-6", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { class: "mb-6 flex items-center justify-between" }, /* @__PURE__ */ React.createElement("h2", { class: "text-lg font-bold text-white" }, "Send Feedback"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setIsOpen(false), class: "text-white/30 hover:text-white/50" }, "\u2715")), /* @__PURE__ */ React.createElement(Show, { when: !done(), fallback: /* @__PURE__ */ React.createElement("div", { class: "text-center py-8" }, /* @__PURE__ */ React.createElement("div", { class: "text-4xl mb-3" }, "\u{1F389}"), /* @__PURE__ */ React.createElement("p", { class: "text-sm text-white/80" }, "Thank you!")) }, /* @__PURE__ */ React.createElement("div", { class: "flex gap-1.5 mb-4" }, /* @__PURE__ */ React.createElement(For, { each: FEEDBACK_TYPES }, (t) => /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setType(t), class: `flex items-center gap-1 rounded-lg px-3 py-2 text-xs font-medium ${type() === t ? "text-white" : "bg-white/5 text-white/50"}`, style: type() === t ? { background: TYPE_CONFIG[t].color } : void 0 }, TYPE_CONFIG[t].icon, " ", TYPE_CONFIG[t].label))), /* @__PURE__ */ React.createElement("input", { type: "text", placeholder: "Title\u2026", value: title(), onInput: (e) => setTitle(e.target.value), class: "w-full rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white placeholder-white/30 outline-none mb-3" }), /* @__PURE__ */ React.createElement("textarea", { placeholder: "Description\u2026", value: desc(), onInput: (e) => setDesc(e.target.value), rows: 4, class: "w-full rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white placeholder-white/30 outline-none resize-none mb-4" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: submit, disabled: submitting(), class: "w-full rounded-lg bg-indigo-600 py-2.5 text-sm font-medium text-white hover:bg-indigo-500 disabled:opacity-50" }, submitting() ? "Submitting\u2026" : "Submit"))))));
145
+ }
146
+ function NPSSurvey(props) {
147
+ const [score, setScore] = createSignal(null);
148
+ const [comment, setComment] = createSignal("");
149
+ const [step, setStep] = createSignal("score");
150
+ return /* @__PURE__ */ React.createElement("div", { class: "fixed bottom-6 right-6 z-50 w-96 rounded-2xl border border-white/10 bg-[#111118] p-6 shadow-2xl" }, /* @__PURE__ */ React.createElement(Show, { when: step() === "score" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { class: "mb-1 flex items-center justify-between" }, /* @__PURE__ */ React.createElement("h3", { class: "text-sm font-semibold text-white/90" }, "How likely to recommend us?"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: props.onDismiss, class: "text-white/20" }, "\u2715")), /* @__PURE__ */ React.createElement("div", { class: "flex gap-1 mt-3" }, /* @__PURE__ */ React.createElement(For, { each: Array.from({ length: 11 }, (_, i) => i) }, (i) => /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => {
151
+ setScore(i);
152
+ setStep("comment");
153
+ }, class: "flex-1 rounded-lg py-2.5 text-xs font-bold bg-white/5 text-white/50 hover:bg-white/10" }, i))))), /* @__PURE__ */ React.createElement(Show, { when: step() === "comment" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { class: "text-sm font-semibold text-white/90 mb-3" }, "Score: ", score()), /* @__PURE__ */ React.createElement("textarea", { value: comment(), onInput: (e) => setComment(e.target.value), rows: 3, class: "w-full rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-sm text-white outline-none resize-none mb-3" }), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => {
154
+ props.onSubmit(score(), comment() || void 0);
155
+ setStep("thanks");
156
+ }, class: "w-full rounded-lg bg-indigo-600 py-2 text-xs text-white" }, "Submit"))), /* @__PURE__ */ React.createElement(Show, { when: step() === "thanks" }, /* @__PURE__ */ React.createElement("div", { class: "text-center py-4" }, /* @__PURE__ */ React.createElement("div", { class: "text-3xl mb-2" }, "\u{1F389}"), /* @__PURE__ */ React.createElement("p", { class: "text-sm text-white/80" }, "Thank you!"))));
157
+ }
158
+ function FeedbackList(props) {
159
+ return /* @__PURE__ */ React.createElement(Show, { when: props.items.length > 0, fallback: /* @__PURE__ */ React.createElement("div", { class: "flex flex-col items-center py-16" }, /* @__PURE__ */ React.createElement("div", { class: "mb-3 text-5xl opacity-20" }, "\u{1F4AC}"), /* @__PURE__ */ React.createElement("p", { class: "text-sm text-white/40" }, "No feedback yet")) }, /* @__PURE__ */ React.createElement("div", { class: "space-y-2" }, /* @__PURE__ */ React.createElement(For, { each: props.items }, (item) => /* @__PURE__ */ React.createElement(FeedbackCard, { item, onVote: props.onVote, hasVoted: props.votedIds?.includes(item.id) }))));
160
+ }
161
+ function FeedbackTypeSelector(props) {
162
+ return /* @__PURE__ */ React.createElement("div", { class: "flex gap-1.5" }, /* @__PURE__ */ React.createElement(For, { each: FEEDBACK_TYPES }, (t) => /* @__PURE__ */ React.createElement(
163
+ "button",
164
+ {
165
+ type: "button",
166
+ onClick: () => props.onChange(t),
167
+ class: `flex items-center gap-1.5 rounded-lg px-3 py-2 text-xs font-medium ${props.value === t ? "text-white" : "bg-white/5 text-white/50 hover:bg-white/10"}`,
168
+ style: props.value === t ? { background: TYPE_CONFIG[t].color } : void 0
169
+ },
170
+ /* @__PURE__ */ React.createElement("span", null, TYPE_CONFIG[t].icon),
171
+ TYPE_CONFIG[t].label
172
+ )));
173
+ }
174
+ function FeedbackForm(props) {
175
+ const [type, setType] = createSignal("general");
176
+ const [title, setTitle] = createSignal("");
177
+ const [desc, setDesc] = createSignal("");
178
+ const [submitting, setSubmitting] = createSignal(false);
179
+ const [error, setError] = createSignal(null);
180
+ const [success, setSuccess] = createSignal(false);
181
+ const submit = async () => {
182
+ if (!title().trim()) {
183
+ setError("Title required");
184
+ return;
185
+ }
186
+ if (desc().length < 10) {
187
+ setError("Description must be 10+ chars");
188
+ return;
189
+ }
190
+ setSubmitting(true);
191
+ setError(null);
192
+ try {
193
+ await props.onSubmit({ type: type(), title: title(), description: desc() });
194
+ setSuccess(true);
195
+ } catch (e) {
196
+ setError(e instanceof Error ? e.message : "Failed");
197
+ } finally {
198
+ setSubmitting(false);
199
+ }
200
+ };
201
+ return /* @__PURE__ */ React.createElement(Show, { when: !success(), fallback: /* @__PURE__ */ React.createElement("div", { class: "flex flex-col items-center py-8 text-center" }, /* @__PURE__ */ React.createElement("div", { class: "mb-3 text-4xl" }, "\u{1F389}"), /* @__PURE__ */ React.createElement("h3", { class: "text-base font-semibold text-white/90 mb-1" }, "Thank you!"), /* @__PURE__ */ React.createElement("p", { class: "text-sm text-white/50 mb-4" }, "Your feedback has been submitted."), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => {
202
+ setSuccess(false);
203
+ props.onClose?.();
204
+ }, class: "rounded-lg bg-indigo-600 px-4 py-2 text-xs font-medium text-white" }, "Done")) }, /* @__PURE__ */ React.createElement("div", { class: "space-y-4" }, /* @__PURE__ */ React.createElement(FeedbackTypeSelector, { value: type(), onChange: setType }), /* @__PURE__ */ React.createElement(
205
+ "input",
206
+ {
207
+ type: "text",
208
+ placeholder: "Short title for your feedback\u2026",
209
+ value: title(),
210
+ onInput: (e) => setTitle(e.target.value),
211
+ class: "w-full rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white placeholder-white/30 outline-none"
212
+ }
213
+ ), /* @__PURE__ */ React.createElement(
214
+ "textarea",
215
+ {
216
+ placeholder: "Describe your feedback in detail\u2026",
217
+ value: desc(),
218
+ onInput: (e) => setDesc(e.target.value),
219
+ rows: 4,
220
+ class: "w-full rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white placeholder-white/30 outline-none resize-none"
221
+ }
222
+ ), error() && /* @__PURE__ */ React.createElement("p", { class: "text-xs text-red-400" }, error()), /* @__PURE__ */ React.createElement("div", { class: "flex justify-end gap-2" }, /* @__PURE__ */ React.createElement(Show, { when: props.onClose }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: props.onClose, class: "rounded-lg border border-white/10 px-4 py-2 text-xs text-white/50 hover:bg-white/5" }, "Cancel")), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: submit, disabled: submitting(), class: "rounded-lg bg-indigo-600 px-5 py-2 text-xs font-medium text-white hover:bg-indigo-500 disabled:opacity-50" }, submitting() ? "Submitting\u2026" : "Submit"))));
223
+ }
224
+ function NPSResults(props) {
225
+ const stats = () => calcNPSStats(props.responses);
226
+ const total = () => stats().totalResponses || 1;
227
+ const scoreColor = () => stats().npsScore >= 50 ? "text-emerald-400" : stats().npsScore >= 0 ? "text-amber-400" : "text-red-400";
228
+ return /* @__PURE__ */ React.createElement("div", { class: "rounded-xl border border-white/8 bg-white/[0.02] p-5" }, /* @__PURE__ */ React.createElement("div", { class: "mb-4 flex items-center justify-between" }, /* @__PURE__ */ React.createElement("h4", { class: "text-sm font-semibold text-white/80" }, "NPS Score"), /* @__PURE__ */ React.createElement("span", { class: `text-3xl font-black tabular-nums ${scoreColor()}` }, stats().npsScore)), /* @__PURE__ */ React.createElement("div", { class: "mb-4 flex h-5 overflow-hidden rounded-full" }, /* @__PURE__ */ React.createElement("div", { class: "bg-red-500/70", style: { width: `${stats().detractors / total() * 100}%` } }), /* @__PURE__ */ React.createElement("div", { class: "bg-amber-500/70", style: { width: `${stats().passives / total() * 100}%` } }), /* @__PURE__ */ React.createElement("div", { class: "bg-emerald-500/70", style: { width: `${stats().promoters / total() * 100}%` } })), /* @__PURE__ */ React.createElement("div", { class: "grid grid-cols-3 gap-2 text-center text-xs" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", { class: "block text-red-400 font-bold" }, stats().detractors), /* @__PURE__ */ React.createElement("span", { class: "text-white/30" }, "Detractors")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", { class: "block text-amber-400 font-bold" }, stats().passives), /* @__PURE__ */ React.createElement("span", { class: "text-white/30" }, "Passives")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", { class: "block text-emerald-400 font-bold" }, stats().promoters), /* @__PURE__ */ React.createElement("span", { class: "text-white/30" }, "Promoters"))), /* @__PURE__ */ React.createElement("div", { class: "mt-3 pt-3 border-t border-white/5 flex justify-between text-xs text-white/40" }, /* @__PURE__ */ React.createElement("span", null, "Avg: ", stats().averageScore, "/10"), /* @__PURE__ */ React.createElement("span", null, stats().totalResponses, " responses")));
229
+ }
230
+ function FeedbackPublicPage(props) {
231
+ const feedback = createFeedback(() => props.items);
232
+ const [activeType, setActiveType] = createSignal("all");
233
+ return /* @__PURE__ */ React.createElement(Show, { when: !feedback.isLoading(), fallback: /* @__PURE__ */ React.createElement("div", { class: "min-h-screen bg-[#090a0f] px-6 py-12" }, /* @__PURE__ */ React.createElement("div", { class: "mx-auto max-w-3xl" }, /* @__PURE__ */ React.createElement("div", { class: "mb-8 h-10 w-48 animate-pulse rounded-lg bg-white/5" }), /* @__PURE__ */ React.createElement("div", { class: "mb-6 flex gap-2" }, /* @__PURE__ */ React.createElement(For, { each: Array.from({ length: 5 }) }, () => /* @__PURE__ */ React.createElement("div", { class: "h-9 w-24 animate-pulse rounded-lg bg-white/5" }))), /* @__PURE__ */ React.createElement("div", { class: "space-y-3" }, /* @__PURE__ */ React.createElement(For, { each: Array.from({ length: 4 }) }, () => /* @__PURE__ */ React.createElement("div", { class: "h-24 animate-pulse rounded-xl bg-white/5" }))))) }, /* @__PURE__ */ React.createElement("div", { class: "min-h-screen bg-[#090a0f] text-white" }, /* @__PURE__ */ React.createElement("div", { class: "mx-auto max-w-3xl px-6 py-12" }, /* @__PURE__ */ React.createElement("div", { class: "mb-8" }, /* @__PURE__ */ React.createElement("h1", { class: "text-2xl font-bold tracking-tight mb-1" }, "Feedback Board"), /* @__PURE__ */ React.createElement("p", { class: "text-sm text-white/40" }, feedback.stats().total, " submissions \xB7 ", feedback.stats().byStatus.open, " open")), /* @__PURE__ */ React.createElement("div", { class: "mb-6 flex flex-wrap items-center gap-3" }, /* @__PURE__ */ React.createElement(
234
+ "input",
235
+ {
236
+ type: "text",
237
+ placeholder: "Search feedback\u2026",
238
+ value: feedback.search(),
239
+ onInput: (e) => feedback.setSearch(e.target.value),
240
+ class: "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"
241
+ }
242
+ ), /* @__PURE__ */ React.createElement("div", { class: "flex gap-1.5" }, /* @__PURE__ */ React.createElement(
243
+ "button",
244
+ {
245
+ type: "button",
246
+ onClick: () => {
247
+ setActiveType("all");
248
+ feedback.setTypeFilter(void 0);
249
+ },
250
+ class: `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"}`
251
+ },
252
+ "All (",
253
+ feedback.stats().total,
254
+ ")"
255
+ ), /* @__PURE__ */ React.createElement(For, { each: FEEDBACK_TYPES }, (t) => /* @__PURE__ */ React.createElement(
256
+ "button",
257
+ {
258
+ type: "button",
259
+ onClick: () => {
260
+ setActiveType(t);
261
+ feedback.setTypeFilter(t);
262
+ },
263
+ class: `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"}`,
264
+ style: activeType() === t ? { background: TYPE_CONFIG[t].color } : void 0
265
+ },
266
+ TYPE_CONFIG[t].icon,
267
+ " ",
268
+ TYPE_CONFIG[t].label,
269
+ " (",
270
+ feedback.stats().byType[t],
271
+ ")"
272
+ ))), /* @__PURE__ */ React.createElement("div", { class: "ml-auto flex gap-1.5" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => feedback.setSort("newest"), class: `rounded-lg px-3 py-1.5 text-xs ${feedback.sort() === "newest" ? "bg-white/10 text-white/80" : "text-white/40"}` }, "Newest"), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => feedback.setSort("votes"), class: `rounded-lg px-3 py-1.5 text-xs ${feedback.sort() === "votes" ? "bg-white/10 text-white/80" : "text-white/40"}` }, "Top voted"))), /* @__PURE__ */ React.createElement(FeedbackList, { items: feedback.items(), onVote: props.onVote, votedIds: props.votedIds }))));
273
+ }
274
+ function FeedbackAdminPage(props) {
275
+ const admin = createFeedbackAdmin(() => props.items, props.mutations);
276
+ return /* @__PURE__ */ React.createElement(Show, { when: !admin.isLoading(), fallback: /* @__PURE__ */ React.createElement("div", { class: "min-h-screen bg-[#090a0f] px-6 py-12" }, /* @__PURE__ */ React.createElement("div", { class: "mx-auto max-w-6xl" }, /* @__PURE__ */ React.createElement("div", { class: "mb-8 h-10 w-48 animate-pulse rounded-lg bg-white/5" }), /* @__PURE__ */ React.createElement("div", { class: "mb-6 grid grid-cols-4 gap-4" }, /* @__PURE__ */ React.createElement(For, { each: Array.from({ length: 4 }) }, () => /* @__PURE__ */ React.createElement("div", { class: "h-20 animate-pulse rounded-xl bg-white/5" }))), /* @__PURE__ */ React.createElement("div", { class: "h-96 animate-pulse rounded-xl bg-white/5" }))) }, /* @__PURE__ */ React.createElement("div", { class: "min-h-screen bg-[#090a0f] text-white" }, /* @__PURE__ */ React.createElement("div", { class: "mx-auto max-w-6xl px-6 py-12" }, /* @__PURE__ */ React.createElement("h1", { class: "text-2xl font-bold tracking-tight mb-8" }, "Feedback Admin"), /* @__PURE__ */ React.createElement("div", { class: "mb-8 grid grid-cols-2 gap-4 md:grid-cols-5" }, /* @__PURE__ */ React.createElement(StatCard, { label: "Total", value: admin.stats().total, icon: "\u{1F4CA}" }), /* @__PURE__ */ React.createElement(StatCard, { label: "Open", value: admin.stats().byStatus.open, icon: "\u{1F4EC}", color: "text-blue-400" }), /* @__PURE__ */ React.createElement(StatCard, { label: "Planned", value: admin.stats().byStatus.planned, icon: "\u{1F4CB}", color: "text-purple-300" }), /* @__PURE__ */ React.createElement(StatCard, { label: "In Progress", value: admin.stats().byStatus["in-progress"], icon: "\u{1F6A7}", color: "text-indigo-300" }), /* @__PURE__ */ React.createElement(StatCard, { label: "Done", value: admin.stats().byStatus.done, icon: "\u2705", color: "text-emerald-400" })), /* @__PURE__ */ React.createElement("div", { class: "grid gap-6 lg:grid-cols-3" }, /* @__PURE__ */ React.createElement("div", { class: "lg:col-span-2" }, /* @__PURE__ */ React.createElement("div", { class: "mb-4 flex items-center gap-3" }, /* @__PURE__ */ React.createElement("input", { type: "text", placeholder: "Search\u2026", value: admin.search(), onInput: (e) => admin.setSearch(e.target.value), class: "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" }), /* @__PURE__ */ React.createElement("span", { class: "text-xs text-white/30" }, admin.items().length, " items")), /* @__PURE__ */ React.createElement("div", { class: "overflow-x-auto rounded-xl border border-white/8" }, /* @__PURE__ */ React.createElement("table", { class: "w-full text-sm" }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", { class: "border-b border-white/8 bg-white/[0.02]" }, /* @__PURE__ */ React.createElement("th", { class: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase" }, "Type"), /* @__PURE__ */ React.createElement("th", { class: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase" }, "Title"), /* @__PURE__ */ React.createElement("th", { class: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase" }, "Status"), /* @__PURE__ */ React.createElement("th", { class: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase" }, "Priority"), /* @__PURE__ */ React.createElement("th", { class: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase" }, "Votes"), /* @__PURE__ */ React.createElement("th", { class: "px-4 py-3 text-left text-xs font-medium text-white/40 uppercase" }, "Date"), /* @__PURE__ */ React.createElement("th", { class: "px-4 py-3 text-right text-xs font-medium text-white/40 uppercase" }, "Actions"))), /* @__PURE__ */ React.createElement("tbody", { class: "divide-y divide-white/5" }, /* @__PURE__ */ React.createElement(For, { each: admin.items() }, (item) => /* @__PURE__ */ React.createElement("tr", { class: "group hover:bg-white/[0.02]" }, /* @__PURE__ */ React.createElement("td", { class: "px-4 py-3" }, /* @__PURE__ */ React.createElement(TypeBadge, { type: item.type })), /* @__PURE__ */ React.createElement("td", { class: "px-4 py-3 max-w-xs truncate text-white/80 font-medium" }, item.title), /* @__PURE__ */ React.createElement("td", { class: "px-4 py-3" }, /* @__PURE__ */ React.createElement("select", { value: item.status, onChange: (e) => admin.updateStatus(item.id, e.target.value), class: "rounded border border-white/10 bg-white/5 px-1.5 py-0.5 text-xs text-white outline-none" }, /* @__PURE__ */ React.createElement(For, { each: FEEDBACK_STATUSES }, (s) => /* @__PURE__ */ React.createElement("option", { value: s }, STATUS_CONFIG[s].label)))), /* @__PURE__ */ React.createElement("td", { class: "px-4 py-3" }, /* @__PURE__ */ React.createElement("select", { value: item.priority, onChange: (e) => admin.updatePriority(item.id, e.target.value), class: "rounded border border-white/10 bg-white/5 px-1.5 py-0.5 text-xs text-white outline-none" }, /* @__PURE__ */ React.createElement(For, { each: FEEDBACK_PRIORITIES }, (p) => /* @__PURE__ */ React.createElement("option", { value: p }, PRIORITY_CONFIG[p].label)))), /* @__PURE__ */ React.createElement("td", { class: "px-4 py-3 tabular-nums text-white/50" }, item.votes), /* @__PURE__ */ React.createElement("td", { class: "px-4 py-3 text-xs text-white/30" }, formatRelativeTime(item.createdAt)), /* @__PURE__ */ React.createElement("td", { class: "px-4 py-3 text-right" }, /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => admin.deleteFeedback(item.id), class: "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" }, "Delete")))))))), /* @__PURE__ */ React.createElement("div", { class: "space-y-6" }, /* @__PURE__ */ React.createElement(Show, { when: props.npsResponses }, (responses) => /* @__PURE__ */ React.createElement(NPSResults, { responses: responses() })), /* @__PURE__ */ React.createElement("div", { class: "rounded-xl border border-white/8 bg-white/[0.02] p-5" }, /* @__PURE__ */ React.createElement("h4", { class: "text-sm font-semibold text-white/80 mb-3" }, "By Type"), /* @__PURE__ */ React.createElement(For, { each: Object.entries(admin.stats().byType) }, ([t, c]) => /* @__PURE__ */ React.createElement("div", { class: "flex justify-between mb-1.5" }, /* @__PURE__ */ React.createElement("span", { class: "text-xs text-white/50 capitalize" }, TYPE_CONFIG[t]?.icon, " ", t), /* @__PURE__ */ React.createElement("span", { class: "text-xs font-bold text-white/70" }, c)))))))));
277
+ }
278
+ function StatCard(props) {
279
+ return /* @__PURE__ */ React.createElement("div", { class: "rounded-xl border border-white/8 bg-white/[0.02] p-4" }, /* @__PURE__ */ React.createElement("div", { class: "mb-2 flex items-center justify-between" }, /* @__PURE__ */ React.createElement("span", { class: "text-lg" }, props.icon), /* @__PURE__ */ React.createElement("span", { class: `text-2xl font-bold tabular-nums ${props.color ?? "text-white/90"}` }, props.value)), /* @__PURE__ */ React.createElement("p", { class: "text-xs text-white/40" }, props.label));
280
+ }
281
+
282
+ // src/pages/FeedbackWidgetPage.tsx
283
+ function FeedbackWidgetPage(props) {
284
+ return /* @__PURE__ */ React.createElement("div", { class: "min-h-screen bg-[#090a0f] text-white flex items-center justify-center" }, /* @__PURE__ */ React.createElement("div", { class: "text-center max-w-md" }, /* @__PURE__ */ React.createElement("h1", { class: "text-2xl font-bold mb-2" }, "Feedback Widget"), /* @__PURE__ */ React.createElement("p", { class: "text-sm text-white/40 mb-8" }, 'Click the floating "Feedback" tab on the right edge to open the feedback form.'), /* @__PURE__ */ React.createElement("div", { class: "rounded-xl border border-white/8 bg-white/[0.02] p-6 text-left" }, /* @__PURE__ */ React.createElement("h3", { class: "text-sm font-semibold text-white/80 mb-3" }, "Features"), /* @__PURE__ */ React.createElement("ul", { class: "space-y-2 text-xs text-white/50" }, /* @__PURE__ */ React.createElement("li", { class: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { class: "text-emerald-400" }, "\u2713"), " Floating edge tab trigger"), /* @__PURE__ */ React.createElement("li", { class: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { class: "text-emerald-400" }, "\u2713"), " Type selector (Bug/Feature/Suggestion/General)"), /* @__PURE__ */ React.createElement("li", { class: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { class: "text-emerald-400" }, "\u2713"), " Title + description form with validation"), /* @__PURE__ */ React.createElement("li", { class: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { class: "text-emerald-400" }, "\u2713"), " Auto-captures page URL + browser"), /* @__PURE__ */ React.createElement("li", { class: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { class: "text-emerald-400" }, "\u2713"), " Success confirmation")))), /* @__PURE__ */ React.createElement(FeedbackWidget, { onSubmit: props.onSubmit }));
285
+ }
286
+
287
+ export { FeedbackAdminPage, FeedbackCard, FeedbackForm, FeedbackList, FeedbackProvider, FeedbackPublicPage, FeedbackTypeSelector, FeedbackWidget, FeedbackWidgetPage, NPSResults, NPSSurvey, PriorityBadge, StatusBadge, TypeBadge, createFeedback, createFeedbackAdmin, createFeedbackForm, createNPS, useFeedbackConfig };
288
+ //# sourceMappingURL=index.js.map
289
+ //# sourceMappingURL=index.js.map