@nextsparkjs/theme-default 0.1.0-beta.1

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 (333) hide show
  1. package/about/business.md +49 -0
  2. package/about/features.json +302 -0
  3. package/about/team.md +79 -0
  4. package/api/ai/chat/stream/route.ts +212 -0
  5. package/api/ai/orchestrator/route.ts +226 -0
  6. package/api/ai/single-agent/route.ts +291 -0
  7. package/api/ai/usage/route.ts +122 -0
  8. package/blocks/benefits/component.tsx +100 -0
  9. package/blocks/benefits/config.ts +11 -0
  10. package/blocks/benefits/examples.ts +85 -0
  11. package/blocks/benefits/fields.ts +156 -0
  12. package/blocks/benefits/schema.ts +33 -0
  13. package/blocks/cta-section/component.tsx +100 -0
  14. package/blocks/cta-section/config.ts +11 -0
  15. package/blocks/cta-section/examples.ts +41 -0
  16. package/blocks/cta-section/fields.ts +89 -0
  17. package/blocks/cta-section/index.ts +6 -0
  18. package/blocks/cta-section/schema.ts +32 -0
  19. package/blocks/cta-section/thumbnail.png +1 -0
  20. package/blocks/faq-accordion/component.tsx +156 -0
  21. package/blocks/faq-accordion/config.ts +11 -0
  22. package/blocks/faq-accordion/examples.ts +77 -0
  23. package/blocks/faq-accordion/fields.ts +119 -0
  24. package/blocks/faq-accordion/index.ts +6 -0
  25. package/blocks/faq-accordion/schema.ts +45 -0
  26. package/blocks/features-grid/component.tsx +112 -0
  27. package/blocks/features-grid/config.ts +11 -0
  28. package/blocks/features-grid/examples.ts +63 -0
  29. package/blocks/features-grid/fields.ts +97 -0
  30. package/blocks/features-grid/index.ts +6 -0
  31. package/blocks/features-grid/schema.ts +40 -0
  32. package/blocks/features-grid/thumbnail.png +1 -0
  33. package/blocks/hero/component.tsx +100 -0
  34. package/blocks/hero/config.ts +11 -0
  35. package/blocks/hero/examples.ts +35 -0
  36. package/blocks/hero/fields.ts +60 -0
  37. package/blocks/hero/index.ts +6 -0
  38. package/blocks/hero/schema.ts +32 -0
  39. package/blocks/hero/thumbnail.png +1 -0
  40. package/blocks/hero/thumbnail.png.txt +6 -0
  41. package/blocks/hero-with-form/component.tsx +232 -0
  42. package/blocks/hero-with-form/config.ts +11 -0
  43. package/blocks/hero-with-form/examples.ts +16 -0
  44. package/blocks/hero-with-form/fields.ts +207 -0
  45. package/blocks/hero-with-form/index.ts +6 -0
  46. package/blocks/hero-with-form/schema.ts +54 -0
  47. package/blocks/jumbotron/component.tsx +136 -0
  48. package/blocks/jumbotron/config.ts +11 -0
  49. package/blocks/jumbotron/examples.ts +36 -0
  50. package/blocks/jumbotron/fields.ts +202 -0
  51. package/blocks/jumbotron/index.ts +6 -0
  52. package/blocks/jumbotron/schema.ts +55 -0
  53. package/blocks/logo-cloud/component.tsx +154 -0
  54. package/blocks/logo-cloud/config.ts +11 -0
  55. package/blocks/logo-cloud/examples.ts +34 -0
  56. package/blocks/logo-cloud/fields.ts +133 -0
  57. package/blocks/logo-cloud/index.ts +6 -0
  58. package/blocks/logo-cloud/schema.ts +46 -0
  59. package/blocks/post-content/component.tsx +197 -0
  60. package/blocks/post-content/config.ts +11 -0
  61. package/blocks/post-content/examples.ts +33 -0
  62. package/blocks/post-content/fields.ts +165 -0
  63. package/blocks/post-content/index.ts +4 -0
  64. package/blocks/post-content/schema.ts +46 -0
  65. package/blocks/pricing-table/component.tsx +154 -0
  66. package/blocks/pricing-table/config.ts +11 -0
  67. package/blocks/pricing-table/examples.ts +96 -0
  68. package/blocks/pricing-table/fields.ts +161 -0
  69. package/blocks/pricing-table/index.ts +4 -0
  70. package/blocks/pricing-table/schema.ts +50 -0
  71. package/blocks/split-content/component.tsx +135 -0
  72. package/blocks/split-content/config.ts +11 -0
  73. package/blocks/split-content/examples.ts +38 -0
  74. package/blocks/split-content/fields.ts +198 -0
  75. package/blocks/split-content/index.ts +6 -0
  76. package/blocks/split-content/schema.ts +67 -0
  77. package/blocks/stats-counter/component.tsx +124 -0
  78. package/blocks/stats-counter/config.ts +11 -0
  79. package/blocks/stats-counter/examples.ts +61 -0
  80. package/blocks/stats-counter/fields.ts +134 -0
  81. package/blocks/stats-counter/index.ts +6 -0
  82. package/blocks/stats-counter/schema.ts +47 -0
  83. package/blocks/testimonials/component.tsx +114 -0
  84. package/blocks/testimonials/config.ts +11 -0
  85. package/blocks/testimonials/examples.ts +65 -0
  86. package/blocks/testimonials/fields.ts +105 -0
  87. package/blocks/testimonials/index.ts +6 -0
  88. package/blocks/testimonials/schema.ts +41 -0
  89. package/blocks/testimonials/thumbnail.png +1 -0
  90. package/blocks/text-content/component.tsx +97 -0
  91. package/blocks/text-content/config.ts +11 -0
  92. package/blocks/text-content/examples.ts +30 -0
  93. package/blocks/text-content/fields.ts +88 -0
  94. package/blocks/text-content/index.ts +6 -0
  95. package/blocks/text-content/schema.ts +30 -0
  96. package/blocks/text-content/thumbnail.png +1 -0
  97. package/blocks/timeline/component.tsx +267 -0
  98. package/blocks/timeline/config.ts +11 -0
  99. package/blocks/timeline/examples.ts +68 -0
  100. package/blocks/timeline/fields.ts +147 -0
  101. package/blocks/timeline/index.ts +6 -0
  102. package/blocks/timeline/schema.ts +49 -0
  103. package/blocks/video-hero/component.tsx +270 -0
  104. package/blocks/video-hero/config.ts +11 -0
  105. package/blocks/video-hero/examples.ts +24 -0
  106. package/blocks/video-hero/fields.ts +98 -0
  107. package/blocks/video-hero/index.ts +6 -0
  108. package/blocks/video-hero/schema.ts +39 -0
  109. package/components/ai-chat/ChatPanel.tsx +575 -0
  110. package/components/ai-chat/ConversationItem.tsx +266 -0
  111. package/components/ai-chat/ConversationSidebar.tsx +99 -0
  112. package/components/ai-chat/MarkdownRenderer.tsx +15 -0
  113. package/components/ai-chat/Message.tsx +42 -0
  114. package/components/ai-chat/MessageInput.tsx +49 -0
  115. package/components/ai-chat/MessageList.tsx +46 -0
  116. package/components/ai-chat/TypingIndicator.tsx +11 -0
  117. package/config/app.config.ts +367 -0
  118. package/config/billing.config.ts +349 -0
  119. package/config/dashboard.config.ts +506 -0
  120. package/config/dev.config.ts +104 -0
  121. package/config/features.config.ts +203 -0
  122. package/config/flows.config.ts +129 -0
  123. package/config/permissions.config.ts +245 -0
  124. package/config/theme.config.ts +74 -0
  125. package/docs/01-overview/01-introduction.md +335 -0
  126. package/docs/01-overview/02-customization.md +671 -0
  127. package/docs/02-features/01-components.md +155 -0
  128. package/docs/02-features/02-styling.md +139 -0
  129. package/docs/02-features/03-tasks-entity.md +407 -0
  130. package/docs/03-ai/01-overview.md +211 -0
  131. package/docs/03-ai/02-customization.md +436 -0
  132. package/entities/customers/customers.config.ts +75 -0
  133. package/entities/customers/customers.fields.ts +165 -0
  134. package/entities/customers/customers.service.ts +516 -0
  135. package/entities/customers/customers.types.ts +83 -0
  136. package/entities/customers/messages/en.json +66 -0
  137. package/entities/customers/messages/es.json +66 -0
  138. package/entities/customers/migrations/001_customers_table.sql +102 -0
  139. package/entities/customers/migrations/002_customers_metas.sql +92 -0
  140. package/entities/pages/messages/en.json +41 -0
  141. package/entities/pages/messages/es.json +41 -0
  142. package/entities/pages/migrations/001_pages_table.sql +112 -0
  143. package/entities/pages/migrations/002_pages_metas.sql +56 -0
  144. package/entities/pages/migrations/003_add_status.sql +50 -0
  145. package/entities/pages/pages-management.service.ts +610 -0
  146. package/entities/pages/pages.config.ts +94 -0
  147. package/entities/pages/pages.fields.ts +101 -0
  148. package/entities/pages/pages.service.ts +290 -0
  149. package/entities/pages/pages.types.ts +124 -0
  150. package/entities/posts/components/post-header.tsx +97 -0
  151. package/entities/posts/messages/en.json +55 -0
  152. package/entities/posts/messages/es.json +55 -0
  153. package/entities/posts/migrations/001_posts_table.sql +115 -0
  154. package/entities/posts/migrations/003_add_status.sql +44 -0
  155. package/entities/posts/migrations/004_entity_taxonomy_relations.sql +129 -0
  156. package/entities/posts/migrations/006_posts_metas.sql +56 -0
  157. package/entities/posts/posts.config.ts +101 -0
  158. package/entities/posts/posts.fields.ts +116 -0
  159. package/entities/posts/posts.service.ts +376 -0
  160. package/entities/posts/posts.types.ts +74 -0
  161. package/entities/tasks/messages/en.json +204 -0
  162. package/entities/tasks/messages/es.json +204 -0
  163. package/entities/tasks/migrations/001_tasks_table.sql +105 -0
  164. package/entities/tasks/migrations/002_task_metas.sql +85 -0
  165. package/entities/tasks/migrations/sample_data.json +77 -0
  166. package/entities/tasks/tasks.config.ts +79 -0
  167. package/entities/tasks/tasks.fields.ts +196 -0
  168. package/entities/tasks/tasks.service.ts +541 -0
  169. package/entities/tasks/tasks.types.ts +56 -0
  170. package/lib/hooks/useAiChat.ts +114 -0
  171. package/lib/hooks/useConversations.ts +376 -0
  172. package/lib/hooks/useOrchestratorChat.ts +122 -0
  173. package/lib/hooks/usePersistentChat.ts +315 -0
  174. package/lib/hooks/useStreamingChat.ts +127 -0
  175. package/lib/hooks/useTokenUsage.ts +63 -0
  176. package/lib/langchain/agents/customer-assistant.md +69 -0
  177. package/lib/langchain/agents/index.ts +61 -0
  178. package/lib/langchain/agents/orchestrator.md +59 -0
  179. package/lib/langchain/agents/page-assistant.md +85 -0
  180. package/lib/langchain/agents/single-agent.md +46 -0
  181. package/lib/langchain/agents/task-assistant.md +55 -0
  182. package/lib/langchain/config.ts +45 -0
  183. package/lib/langchain/handlers/customer-handler.ts +338 -0
  184. package/lib/langchain/handlers/page-handler.ts +232 -0
  185. package/lib/langchain/handlers/task-handler.ts +323 -0
  186. package/lib/langchain/langchain.config.ts +223 -0
  187. package/lib/langchain/observability.config.ts +30 -0
  188. package/lib/langchain/orchestrator.ts +562 -0
  189. package/lib/langchain/tools/customers.ts +176 -0
  190. package/lib/langchain/tools/index.ts +10 -0
  191. package/lib/langchain/tools/orchestrator.ts +92 -0
  192. package/lib/langchain/tools/pages.ts +289 -0
  193. package/lib/langchain/tools/tasks.ts +167 -0
  194. package/lib/scheduled-actions/billing.ts +149 -0
  195. package/lib/scheduled-actions/index.ts +170 -0
  196. package/lib/scheduled-actions/webhook.ts +231 -0
  197. package/lib/selectors.ts +197 -0
  198. package/messages/de/admin.json +219 -0
  199. package/messages/de/aiUsage.json +36 -0
  200. package/messages/de/buttons.json +19 -0
  201. package/messages/de/categories.json +35 -0
  202. package/messages/de/common.json +16 -0
  203. package/messages/de/dev.json +101 -0
  204. package/messages/de/docs.json +27 -0
  205. package/messages/de/entities.json +7 -0
  206. package/messages/de/features.json +119 -0
  207. package/messages/de/footer.json +22 -0
  208. package/messages/de/home.json +57 -0
  209. package/messages/de/index.ts +39 -0
  210. package/messages/de/mobileNav.json +13 -0
  211. package/messages/de/navigation.json +8 -0
  212. package/messages/de/observability.json +74 -0
  213. package/messages/de/posts.json +54 -0
  214. package/messages/de/pricing.json +102 -0
  215. package/messages/de/support.json +9 -0
  216. package/messages/de/teams.json +8 -0
  217. package/messages/en/admin.json +219 -0
  218. package/messages/en/aiUsage.json +36 -0
  219. package/messages/en/buttons.json +19 -0
  220. package/messages/en/categories.json +35 -0
  221. package/messages/en/common.json +16 -0
  222. package/messages/en/dev.json +106 -0
  223. package/messages/en/docs.json +27 -0
  224. package/messages/en/entities.json +7 -0
  225. package/messages/en/features.json +119 -0
  226. package/messages/en/footer.json +22 -0
  227. package/messages/en/home.json +57 -0
  228. package/messages/en/index.ts +39 -0
  229. package/messages/en/mobileNav.json +13 -0
  230. package/messages/en/navigation.json +8 -0
  231. package/messages/en/observability.json +74 -0
  232. package/messages/en/posts.json +54 -0
  233. package/messages/en/pricing.json +102 -0
  234. package/messages/en/support.json +9 -0
  235. package/messages/en/teams.json +8 -0
  236. package/messages/es/admin.json +219 -0
  237. package/messages/es/aiUsage.json +36 -0
  238. package/messages/es/buttons.json +19 -0
  239. package/messages/es/categories.json +35 -0
  240. package/messages/es/common.json +16 -0
  241. package/messages/es/dev.json +101 -0
  242. package/messages/es/docs.json +27 -0
  243. package/messages/es/entities.json +7 -0
  244. package/messages/es/features.json +119 -0
  245. package/messages/es/footer.json +22 -0
  246. package/messages/es/home.json +57 -0
  247. package/messages/es/index.ts +39 -0
  248. package/messages/es/mobileNav.json +13 -0
  249. package/messages/es/navigation.json +8 -0
  250. package/messages/es/observability.json +74 -0
  251. package/messages/es/posts.json +54 -0
  252. package/messages/es/pricing.json +102 -0
  253. package/messages/es/support.json +9 -0
  254. package/messages/es/teams.json +8 -0
  255. package/messages/fr/admin.json +219 -0
  256. package/messages/fr/aiUsage.json +36 -0
  257. package/messages/fr/buttons.json +19 -0
  258. package/messages/fr/categories.json +35 -0
  259. package/messages/fr/common.json +16 -0
  260. package/messages/fr/dev.json +101 -0
  261. package/messages/fr/docs.json +27 -0
  262. package/messages/fr/entities.json +7 -0
  263. package/messages/fr/features.json +119 -0
  264. package/messages/fr/footer.json +22 -0
  265. package/messages/fr/home.json +57 -0
  266. package/messages/fr/index.ts +39 -0
  267. package/messages/fr/mobileNav.json +13 -0
  268. package/messages/fr/navigation.json +8 -0
  269. package/messages/fr/observability.json +74 -0
  270. package/messages/fr/posts.json +54 -0
  271. package/messages/fr/pricing.json +102 -0
  272. package/messages/fr/support.json +9 -0
  273. package/messages/fr/teams.json +8 -0
  274. package/messages/it/admin.json +219 -0
  275. package/messages/it/aiUsage.json +36 -0
  276. package/messages/it/buttons.json +19 -0
  277. package/messages/it/categories.json +35 -0
  278. package/messages/it/common.json +16 -0
  279. package/messages/it/dev.json +101 -0
  280. package/messages/it/docs.json +27 -0
  281. package/messages/it/entities.json +7 -0
  282. package/messages/it/features.json +119 -0
  283. package/messages/it/footer.json +22 -0
  284. package/messages/it/home.json +57 -0
  285. package/messages/it/index.ts +39 -0
  286. package/messages/it/mobileNav.json +13 -0
  287. package/messages/it/navigation.json +8 -0
  288. package/messages/it/observability.json +74 -0
  289. package/messages/it/posts.json +54 -0
  290. package/messages/it/pricing.json +102 -0
  291. package/messages/it/support.json +9 -0
  292. package/messages/it/teams.json +8 -0
  293. package/messages/pt/admin.json +219 -0
  294. package/messages/pt/aiUsage.json +36 -0
  295. package/messages/pt/buttons.json +19 -0
  296. package/messages/pt/categories.json +35 -0
  297. package/messages/pt/common.json +16 -0
  298. package/messages/pt/dev.json +101 -0
  299. package/messages/pt/docs.json +27 -0
  300. package/messages/pt/entities.json +7 -0
  301. package/messages/pt/features.json +119 -0
  302. package/messages/pt/footer.json +22 -0
  303. package/messages/pt/home.json +57 -0
  304. package/messages/pt/index.ts +39 -0
  305. package/messages/pt/mobileNav.json +13 -0
  306. package/messages/pt/navigation.json +8 -0
  307. package/messages/pt/observability.json +74 -0
  308. package/messages/pt/posts.json +54 -0
  309. package/messages/pt/pricing.json +102 -0
  310. package/messages/pt/support.json +9 -0
  311. package/messages/pt/teams.json +8 -0
  312. package/migrations/089_add_editor_team_role.sql +39 -0
  313. package/migrations/090_demo_users_teams.sql +540 -0
  314. package/migrations/091_greek_teams_billing.sql +523 -0
  315. package/migrations/092_billing_sample_data.sql +774 -0
  316. package/migrations/093_pages_sample_data.sql +1158 -0
  317. package/migrations/094_posts_sample_data.sql +278 -0
  318. package/migrations/095_tasks_sample_data.sql +440 -0
  319. package/migrations/096_customers_sample_data.sql +358 -0
  320. package/migrations/097_scheduled_actions_sample_data.sql +111 -0
  321. package/package.json +22 -0
  322. package/public/docs/desktop-layout-example.png +0 -0
  323. package/styles/components.css +11 -0
  324. package/styles/globals.css +179 -0
  325. package/templates/(public)/blog/[slug]/page.tsx +65 -0
  326. package/templates/(public)/layout.tsx +25 -0
  327. package/templates/(public)/page.tsx +200 -0
  328. package/templates/(public)/support/page.tsx +321 -0
  329. package/templates/dashboard/(main)/agent-multi/page.tsx +63 -0
  330. package/templates/dashboard/(main)/agent-single/page.tsx +142 -0
  331. package/templates/dashboard/(main)/settings/ai-usage/page.tsx +157 -0
  332. package/templates/superadmin/ai-observability/[traceId]/page.tsx +27 -0
  333. package/templates/superadmin/ai-observability/page.tsx +17 -0
@@ -0,0 +1,541 @@
1
+ /**
2
+ * Tasks Service
3
+ *
4
+ * Provides data access methods for tasks.
5
+ * Tasks is a private entity with shared: false - users only see their own tasks.
6
+ *
7
+ * All methods require authentication (use RLS with userId filter).
8
+ *
9
+ * @module TasksService
10
+ */
11
+
12
+ import { queryOneWithRLS, queryWithRLS } from '@nextsparkjs/core/lib/db'
13
+ import type {
14
+ Task,
15
+ TaskListOptions,
16
+ TaskListResult,
17
+ TaskStatus,
18
+ TaskPriority,
19
+ } from './tasks.types'
20
+
21
+ // Database row type for task
22
+ interface DbTask {
23
+ id: string
24
+ title: string
25
+ description: string | null
26
+ status: TaskStatus
27
+ priority: TaskPriority
28
+ tags: string[] | null
29
+ dueDate: string | null
30
+ estimatedHours: number | null
31
+ completed: boolean | null
32
+ createdAt: string
33
+ updatedAt: string
34
+ }
35
+
36
+ export class TasksService {
37
+ // ============================================
38
+ // AUTHENTICATED METHODS (con RLS)
39
+ // ============================================
40
+
41
+ /**
42
+ * Get a task by ID
43
+ *
44
+ * Respects RLS policies. Since tasks has shared: false,
45
+ * only the task owner can access it.
46
+ *
47
+ * @param id - Task ID
48
+ * @param userId - Current user ID for RLS
49
+ * @returns Task data or null if not found/not authorized
50
+ *
51
+ * @example
52
+ * const task = await TasksService.getById('task-uuid', currentUserId)
53
+ */
54
+ static async getById(
55
+ id: string,
56
+ userId: string
57
+ ): Promise<Task | null> {
58
+ try {
59
+ if (!id || id.trim() === '') {
60
+ throw new Error('Task ID is required')
61
+ }
62
+
63
+ if (!userId || userId.trim() === '') {
64
+ throw new Error('User ID is required for authentication')
65
+ }
66
+
67
+ const task = await queryOneWithRLS<DbTask>(
68
+ `
69
+ SELECT
70
+ id,
71
+ title,
72
+ description,
73
+ status,
74
+ priority,
75
+ tags,
76
+ "dueDate",
77
+ "estimatedHours",
78
+ completed,
79
+ "createdAt",
80
+ "updatedAt"
81
+ FROM tasks
82
+ WHERE id = $1
83
+ `,
84
+ [id],
85
+ userId
86
+ )
87
+
88
+ if (!task) {
89
+ return null
90
+ }
91
+
92
+ return {
93
+ id: task.id,
94
+ title: task.title,
95
+ description: task.description ?? undefined,
96
+ status: task.status,
97
+ priority: task.priority,
98
+ tags: task.tags ?? undefined,
99
+ dueDate: task.dueDate ?? undefined,
100
+ estimatedHours: task.estimatedHours ?? undefined,
101
+ completed: task.completed ?? undefined,
102
+ createdAt: task.createdAt,
103
+ updatedAt: task.updatedAt,
104
+ }
105
+ } catch (error) {
106
+ console.error('TasksService.getById error:', error)
107
+ throw new Error(
108
+ error instanceof Error ? error.message : 'Failed to fetch task'
109
+ )
110
+ }
111
+ }
112
+
113
+ /**
114
+ * List tasks with pagination and filtering
115
+ *
116
+ * @param userId - Current user ID for RLS
117
+ * @param options - List options (limit, offset, status, priority, orderBy, orderDir)
118
+ * @returns Object with tasks array and total count
119
+ *
120
+ * @example
121
+ * const { tasks, total } = await TasksService.list(currentUserId, {
122
+ * status: 'todo',
123
+ * limit: 10
124
+ * })
125
+ */
126
+ static async list(
127
+ userId: string,
128
+ options: TaskListOptions = {}
129
+ ): Promise<TaskListResult> {
130
+ try {
131
+ if (!userId || userId.trim() === '') {
132
+ throw new Error('User ID is required for authentication')
133
+ }
134
+
135
+ const {
136
+ limit = 10,
137
+ offset = 0,
138
+ status,
139
+ priority,
140
+ orderBy = 'createdAt',
141
+ orderDir = 'desc',
142
+ } = options
143
+
144
+ // Build WHERE clause
145
+ const conditions: string[] = []
146
+ const params: unknown[] = []
147
+ let paramIndex = 1
148
+
149
+ if (status) {
150
+ conditions.push(`status = $${paramIndex}`)
151
+ params.push(status)
152
+ paramIndex++
153
+ }
154
+
155
+ if (priority) {
156
+ conditions.push(`priority = $${paramIndex}`)
157
+ params.push(priority)
158
+ paramIndex++
159
+ }
160
+
161
+ if (options.teamId) {
162
+ conditions.push(`"teamId" = $${paramIndex}`)
163
+ params.push(options.teamId)
164
+ paramIndex++
165
+ }
166
+
167
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
168
+
169
+ // Validate orderBy to prevent SQL injection
170
+ const validOrderBy = ['title', 'status', 'priority', 'dueDate', 'createdAt'].includes(orderBy)
171
+ ? orderBy
172
+ : 'createdAt'
173
+ const validOrderDir = orderDir === 'asc' ? 'ASC' : 'DESC'
174
+
175
+ // Map field names to database columns
176
+ const orderColumnMap: Record<string, string> = {
177
+ title: 'title',
178
+ status: 'status',
179
+ priority: 'priority',
180
+ dueDate: '"dueDate"',
181
+ createdAt: '"createdAt"',
182
+ }
183
+ const orderColumn = orderColumnMap[validOrderBy] || '"createdAt"'
184
+
185
+ // Get total count
186
+ const countResult = await queryWithRLS<{ count: string }>(
187
+ `SELECT COUNT(*)::text as count FROM tasks ${whereClause}`,
188
+ params,
189
+ userId
190
+ )
191
+ const total = parseInt(countResult[0]?.count || '0', 10)
192
+
193
+ // Get tasks
194
+ params.push(limit, offset)
195
+ const tasks = await queryWithRLS<DbTask>(
196
+ `
197
+ SELECT
198
+ id,
199
+ title,
200
+ description,
201
+ status,
202
+ priority,
203
+ tags,
204
+ "dueDate",
205
+ "estimatedHours",
206
+ completed,
207
+ "createdAt",
208
+ "updatedAt"
209
+ FROM tasks
210
+ ${whereClause}
211
+ ORDER BY ${orderColumn} ${validOrderDir}
212
+ LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
213
+ `,
214
+ params,
215
+ userId
216
+ )
217
+
218
+ return {
219
+ tasks: tasks.map((task) => ({
220
+ id: task.id,
221
+ title: task.title,
222
+ description: task.description ?? undefined,
223
+ status: task.status,
224
+ priority: task.priority,
225
+ tags: task.tags ?? undefined,
226
+ dueDate: task.dueDate ?? undefined,
227
+ estimatedHours: task.estimatedHours ?? undefined,
228
+ completed: task.completed ?? undefined,
229
+ createdAt: task.createdAt,
230
+ updatedAt: task.updatedAt,
231
+ })),
232
+ total,
233
+ }
234
+ } catch (error) {
235
+ console.error('TasksService.list error:', error)
236
+ throw new Error(
237
+ error instanceof Error ? error.message : 'Failed to list tasks'
238
+ )
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Get tasks by status
244
+ *
245
+ * Convenience method to fetch all tasks with a specific status.
246
+ *
247
+ * @param userId - Current user ID for RLS
248
+ * @param status - Task status to filter by
249
+ * @returns Array of tasks with the given status
250
+ *
251
+ * @example
252
+ * const todoTasks = await TasksService.getByStatus(currentUserId, 'todo')
253
+ */
254
+ static async getByStatus(
255
+ userId: string,
256
+ status: TaskStatus
257
+ ): Promise<Task[]> {
258
+ try {
259
+ const { tasks } = await this.list(userId, {
260
+ status,
261
+ limit: 1000, // Large limit to get all matching tasks
262
+ orderBy: 'priority',
263
+ orderDir: 'desc',
264
+ })
265
+
266
+ return tasks
267
+ } catch (error) {
268
+ console.error('TasksService.getByStatus error:', error)
269
+ throw new Error(
270
+ error instanceof Error ? error.message : 'Failed to fetch tasks by status'
271
+ )
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Get overdue tasks
277
+ *
278
+ * Fetches tasks that are past their due date and not completed.
279
+ *
280
+ * @param userId - Current user ID for RLS
281
+ * @returns Array of overdue tasks
282
+ *
283
+ * @example
284
+ * const overdueTasks = await TasksService.getOverdue(currentUserId)
285
+ */
286
+ static async getOverdue(userId: string): Promise<Task[]> {
287
+ try {
288
+ if (!userId || userId.trim() === '') {
289
+ throw new Error('User ID is required for authentication')
290
+ }
291
+
292
+ const tasks = await queryWithRLS<DbTask>(
293
+ `
294
+ SELECT
295
+ id,
296
+ title,
297
+ description,
298
+ status,
299
+ priority,
300
+ tags,
301
+ "dueDate",
302
+ "estimatedHours",
303
+ completed,
304
+ "createdAt",
305
+ "updatedAt"
306
+ FROM tasks
307
+ WHERE "dueDate" < CURRENT_DATE
308
+ AND status != 'done'
309
+ AND (completed IS NULL OR completed = false)
310
+ ORDER BY "dueDate" ASC
311
+ `,
312
+ [],
313
+ userId
314
+ )
315
+
316
+ return tasks.map((task) => ({
317
+ id: task.id,
318
+ title: task.title,
319
+ description: task.description ?? undefined,
320
+ status: task.status,
321
+ priority: task.priority,
322
+ tags: task.tags ?? undefined,
323
+ dueDate: task.dueDate ?? undefined,
324
+ estimatedHours: task.estimatedHours ?? undefined,
325
+ completed: task.completed ?? undefined,
326
+ createdAt: task.createdAt,
327
+ updatedAt: task.updatedAt,
328
+ }))
329
+ } catch (error) {
330
+ console.error('TasksService.getOverdue error:', error)
331
+ throw new Error(
332
+ error instanceof Error ? error.message : 'Failed to fetch overdue tasks'
333
+ )
334
+ }
335
+ }
336
+
337
+ // ============================================
338
+ // WRITE METHODS (con RLS)
339
+ // ============================================
340
+
341
+ /**
342
+ * Create a new task
343
+ *
344
+ * @param userId - Current user ID for RLS
345
+ * @param data - Task data
346
+ * @returns Created task
347
+ *
348
+ * @example
349
+ * const task = await TasksService.create(currentUserId, {
350
+ * title: 'New Task',
351
+ * teamId: 'team-123'
352
+ * })
353
+ */
354
+ static async create(
355
+ userId: string,
356
+ data: Partial<Omit<Task, 'id' | 'createdAt' | 'updatedAt'>> & { teamId: string }
357
+ ): Promise<Task> {
358
+ try {
359
+ if (!userId) throw new Error('User ID is required')
360
+ if (!data.title) throw new Error('Title is required')
361
+ if (!data.teamId) throw new Error('Team ID is required')
362
+
363
+ const task = await queryOneWithRLS<DbTask>(
364
+ `
365
+ INSERT INTO tasks (
366
+ id,
367
+ title,
368
+ description,
369
+ status,
370
+ priority,
371
+ tags,
372
+ "dueDate",
373
+ "estimatedHours",
374
+ completed,
375
+ "createdAt",
376
+ "updatedAt",
377
+ "userId",
378
+ "teamId"
379
+ ) VALUES (
380
+ gen_random_uuid(),
381
+ $1,
382
+ $2,
383
+ $3,
384
+ $4,
385
+ $5,
386
+ $6,
387
+ $7,
388
+ $8,
389
+ NOW(),
390
+ NOW(),
391
+ $9,
392
+ $10
393
+ )
394
+ RETURNING *
395
+ `,
396
+ [
397
+ data.title,
398
+ data.description || null,
399
+ data.status || 'todo',
400
+ data.priority || 'medium',
401
+ data.tags || [],
402
+ data.dueDate || null,
403
+ data.estimatedHours || null,
404
+ data.completed || false,
405
+ userId,
406
+ data.teamId
407
+ ],
408
+ userId
409
+ )
410
+
411
+ if (!task) throw new Error('Failed to create task')
412
+
413
+ return {
414
+ id: task.id,
415
+ title: task.title,
416
+ description: task.description ?? undefined,
417
+ status: task.status,
418
+ priority: task.priority,
419
+ tags: task.tags ?? undefined,
420
+ dueDate: task.dueDate ?? undefined,
421
+ estimatedHours: task.estimatedHours ?? undefined,
422
+ completed: task.completed ?? undefined,
423
+ createdAt: task.createdAt,
424
+ updatedAt: task.updatedAt,
425
+ }
426
+ } catch (error) {
427
+ console.error('TasksService.create error:', error)
428
+ throw error
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Update a task
434
+ *
435
+ * @param userId - Current user ID for RLS
436
+ * @param id - Task ID
437
+ * @param data - Data to update
438
+ * @returns Updated task
439
+ *
440
+ * @example
441
+ * const task = await TasksService.update(currentUserId, 'task-123', {
442
+ * status: 'done',
443
+ * completed: true
444
+ * })
445
+ */
446
+ static async update(
447
+ userId: string,
448
+ id: string,
449
+ data: Partial<Omit<Task, 'id' | 'createdAt' | 'updatedAt'>>
450
+ ): Promise<Task> {
451
+ try {
452
+ if (!userId) throw new Error('User ID is required')
453
+ if (!id) throw new Error('Task ID is required')
454
+
455
+ // Build dynamic update query
456
+ const updates: string[] = []
457
+ const params: unknown[] = [id]
458
+ let paramIndex = 2
459
+
460
+ if (data.title !== undefined) {
461
+ updates.push(`title = $${paramIndex}`)
462
+ params.push(data.title)
463
+ paramIndex++
464
+ }
465
+ if (data.description !== undefined) {
466
+ updates.push(`description = $${paramIndex}`)
467
+ params.push(data.description)
468
+ paramIndex++
469
+ }
470
+ if (data.status !== undefined) {
471
+ updates.push(`status = $${paramIndex}`)
472
+ params.push(data.status)
473
+ paramIndex++
474
+ }
475
+ if (data.priority !== undefined) {
476
+ updates.push(`priority = $${paramIndex}`)
477
+ params.push(data.priority)
478
+ paramIndex++
479
+ }
480
+ if (data.tags !== undefined) {
481
+ updates.push(`tags = $${paramIndex}`)
482
+ params.push(data.tags)
483
+ paramIndex++
484
+ }
485
+ if (data.dueDate !== undefined) {
486
+ updates.push(`"dueDate" = $${paramIndex}`)
487
+ params.push(data.dueDate)
488
+ paramIndex++
489
+ }
490
+ if (data.estimatedHours !== undefined) {
491
+ updates.push(`"estimatedHours" = $${paramIndex}`)
492
+ params.push(data.estimatedHours)
493
+ paramIndex++
494
+ }
495
+ if (data.completed !== undefined) {
496
+ updates.push(`completed = $${paramIndex}`)
497
+ params.push(data.completed)
498
+ paramIndex++
499
+ }
500
+
501
+ updates.push(`"updatedAt" = NOW()`)
502
+
503
+ if (updates.length === 1) { // Only updatedAt
504
+ // Nothing to update
505
+ const current = await this.getById(id, userId)
506
+ if (!current) throw new Error('Task not found')
507
+ return current
508
+ }
509
+
510
+ const task = await queryOneWithRLS<DbTask>(
511
+ `
512
+ UPDATE tasks
513
+ SET ${updates.join(', ')}
514
+ WHERE id = $1
515
+ RETURNING *
516
+ `,
517
+ params,
518
+ userId
519
+ )
520
+
521
+ if (!task) throw new Error('Task not found or update failed')
522
+
523
+ return {
524
+ id: task.id,
525
+ title: task.title,
526
+ description: task.description ?? undefined,
527
+ status: task.status,
528
+ priority: task.priority,
529
+ tags: task.tags ?? undefined,
530
+ dueDate: task.dueDate ?? undefined,
531
+ estimatedHours: task.estimatedHours ?? undefined,
532
+ completed: task.completed ?? undefined,
533
+ createdAt: task.createdAt,
534
+ updatedAt: task.updatedAt,
535
+ }
536
+ } catch (error) {
537
+ console.error('TasksService.update error:', error)
538
+ throw error
539
+ }
540
+ }
541
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Tasks Service Types
3
+ *
4
+ * Type definitions for the TasksService.
5
+ * Tasks is a private entity with shared: false - users only see their own tasks.
6
+ *
7
+ * @module TasksTypes
8
+ */
9
+
10
+ /**
11
+ * Task status values
12
+ */
13
+ export type TaskStatus = 'todo' | 'in-progress' | 'review' | 'done' | 'blocked'
14
+
15
+ /**
16
+ * Task priority values
17
+ */
18
+ export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'
19
+
20
+ /**
21
+ * Task entity
22
+ */
23
+ export interface Task {
24
+ id: string
25
+ title: string
26
+ description?: string
27
+ status: TaskStatus
28
+ priority: TaskPriority
29
+ tags?: string[]
30
+ dueDate?: string
31
+ estimatedHours?: number
32
+ completed?: boolean
33
+ createdAt?: string
34
+ updatedAt?: string
35
+ }
36
+
37
+ /**
38
+ * Options for listing tasks
39
+ */
40
+ export interface TaskListOptions {
41
+ limit?: number
42
+ offset?: number
43
+ status?: TaskStatus
44
+ priority?: TaskPriority
45
+ teamId?: string
46
+ orderBy?: 'title' | 'status' | 'priority' | 'dueDate' | 'createdAt'
47
+ orderDir?: 'asc' | 'desc'
48
+ }
49
+
50
+ /**
51
+ * Result of listing tasks with pagination
52
+ */
53
+ export interface TaskListResult {
54
+ tasks: Task[]
55
+ total: number
56
+ }
@@ -0,0 +1,114 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback } from 'react'
4
+ import { useMutation } from '@tanstack/react-query'
5
+ import { useTeamContext } from '@nextsparkjs/core/contexts/TeamContext'
6
+
7
+ export interface Message {
8
+ id: string
9
+ role: 'user' | 'assistant'
10
+ content: string
11
+ timestamp: number
12
+ }
13
+
14
+ interface ChatResponse {
15
+ success: boolean
16
+ data?: {
17
+ message: string
18
+ sessionId: string
19
+ }
20
+ error?: string
21
+ }
22
+
23
+ export function useAiChat() {
24
+ const [messages, setMessages] = useState<Message[]>([])
25
+ const [input, setInput] = useState('')
26
+ const [sessionId, setSessionId] = useState<string | undefined>(undefined)
27
+ const [error, setError] = useState<string | null>(null)
28
+ const { currentTeam } = useTeamContext()
29
+
30
+ const sendMutation = useMutation({
31
+ mutationFn: async (message: string): Promise<ChatResponse> => {
32
+ const headers: Record<string, string> = {
33
+ 'Content-Type': 'application/json'
34
+ }
35
+
36
+ // Include team context if available
37
+ if (currentTeam?.id) {
38
+ headers['x-team-id'] = currentTeam.id
39
+ }
40
+
41
+ const response = await fetch('/api/v1/theme/default/ai/chat', {
42
+ method: 'POST',
43
+ headers,
44
+ body: JSON.stringify({ message, sessionId })
45
+ })
46
+
47
+ if (!response.ok) {
48
+ const errorData = await response.json().catch(() => ({}))
49
+ throw new Error(errorData.error || 'Failed to send message')
50
+ }
51
+
52
+ return response.json()
53
+ },
54
+ onMutate: (message) => {
55
+ setError(null)
56
+ // Optimistic update - add user message immediately
57
+ const userMessage: Message = {
58
+ id: crypto.randomUUID(),
59
+ role: 'user',
60
+ content: message,
61
+ timestamp: Date.now()
62
+ }
63
+ setMessages(prev => [...prev, userMessage])
64
+ setInput('')
65
+ },
66
+ onSuccess: (data) => {
67
+ if (data.success && data.data) {
68
+ setSessionId(data.data.sessionId)
69
+ const aiMessage: Message = {
70
+ id: crypto.randomUUID(),
71
+ role: 'assistant',
72
+ content: data.data.message,
73
+ timestamp: Date.now()
74
+ }
75
+ setMessages(prev => [...prev, aiMessage])
76
+ } else if (data.error) {
77
+ setError(data.error)
78
+ }
79
+ },
80
+ onError: (err: Error) => {
81
+ setError(err.message)
82
+ // Add error message to chat
83
+ const errorMessage: Message = {
84
+ id: crypto.randomUUID(),
85
+ role: 'assistant',
86
+ content: `Error: ${err.message}`,
87
+ timestamp: Date.now()
88
+ }
89
+ setMessages(prev => [...prev, errorMessage])
90
+ }
91
+ })
92
+
93
+ const sendMessage = useCallback(() => {
94
+ if (!input.trim() || sendMutation.isPending) return
95
+ sendMutation.mutate(input)
96
+ }, [input, sendMutation])
97
+
98
+ const clearChat = useCallback(() => {
99
+ setMessages([])
100
+ setSessionId(undefined)
101
+ setError(null)
102
+ setInput('')
103
+ }, [])
104
+
105
+ return {
106
+ messages,
107
+ input,
108
+ setInput,
109
+ error,
110
+ isLoading: sendMutation.isPending,
111
+ sendMessage,
112
+ clearChat
113
+ }
114
+ }