@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,147 @@
1
+ import type { FieldDefinition } from '@nextsparkjs/core/types/blocks'
2
+ import {
3
+ baseContentFields,
4
+ baseDesignFields,
5
+ baseAdvancedFields,
6
+ } from '@nextsparkjs/core/types/blocks'
7
+
8
+ /**
9
+ * Timeline Block Field Definitions
10
+ *
11
+ * Organized into 3 tabs:
12
+ * - Content: title (from base), subtitle, items array
13
+ * - Design: backgroundColor (from base), layout, alternating, showConnector, variant
14
+ * - Advanced: className, id (from base)
15
+ */
16
+
17
+ // Timeline-specific content fields
18
+ const timelineContentFields: FieldDefinition[] = [
19
+ {
20
+ name: 'subtitle',
21
+ label: 'Subtitle',
22
+ type: 'textarea',
23
+ tab: 'content',
24
+ required: false,
25
+ placeholder: 'A brief description of the timeline...',
26
+ helpText: 'Optional section description displayed below the title',
27
+ rows: 2,
28
+ },
29
+ {
30
+ name: 'items',
31
+ label: 'Timeline Items',
32
+ type: 'array',
33
+ tab: 'content',
34
+ required: true,
35
+ description: 'Events or steps to display in the timeline',
36
+ helpText: 'Add up to 20 timeline items with date, title, description, and optional icon',
37
+ minItems: 1,
38
+ maxItems: 20,
39
+ itemFields: [
40
+ {
41
+ name: 'date',
42
+ label: 'Date / Step',
43
+ type: 'text',
44
+ tab: 'content',
45
+ required: true,
46
+ placeholder: '2024, Step 1, Q1 2024',
47
+ helpText: 'Date, step number, or time period label',
48
+ },
49
+ {
50
+ name: 'title',
51
+ label: 'Title',
52
+ type: 'text',
53
+ tab: 'content',
54
+ required: true,
55
+ placeholder: 'Event or milestone title',
56
+ maxLength: 100,
57
+ },
58
+ {
59
+ name: 'description',
60
+ label: 'Description',
61
+ type: 'textarea',
62
+ tab: 'content',
63
+ required: false,
64
+ placeholder: 'Describe this event or step...',
65
+ rows: 3,
66
+ },
67
+ {
68
+ name: 'icon',
69
+ label: 'Icon Name (optional)',
70
+ type: 'text',
71
+ tab: 'content',
72
+ required: false,
73
+ placeholder: 'Rocket, Check, Star, Calendar',
74
+ helpText: 'Lucide icon name (optional)',
75
+ },
76
+ ],
77
+ },
78
+ ]
79
+
80
+ // Timeline-specific design fields
81
+ const timelineDesignFields: FieldDefinition[] = [
82
+ {
83
+ name: 'layout',
84
+ label: 'Layout Direction',
85
+ type: 'select',
86
+ tab: 'design',
87
+ required: false,
88
+ default: 'vertical',
89
+ description: 'Timeline orientation',
90
+ helpText: 'Horizontal layout becomes vertical on mobile',
91
+ options: [
92
+ { label: 'Vertical', value: 'vertical' },
93
+ { label: 'Horizontal', value: 'horizontal' },
94
+ ],
95
+ },
96
+ {
97
+ name: 'alternating',
98
+ label: 'Alternating Sides',
99
+ type: 'checkbox',
100
+ tab: 'design',
101
+ required: false,
102
+ default: true,
103
+ helpText: 'Alternate items between left and right (vertical layout only)',
104
+ },
105
+ {
106
+ name: 'showConnector',
107
+ label: 'Show Connector Line',
108
+ type: 'checkbox',
109
+ tab: 'design',
110
+ required: false,
111
+ default: true,
112
+ helpText: 'Display line connecting timeline items',
113
+ },
114
+ {
115
+ name: 'variant',
116
+ label: 'Visual Style',
117
+ type: 'select',
118
+ tab: 'design',
119
+ required: false,
120
+ default: 'default',
121
+ description: 'Timeline item appearance',
122
+ options: [
123
+ { label: 'Default', value: 'default' },
124
+ { label: 'Minimal', value: 'minimal' },
125
+ { label: 'Cards', value: 'cards' },
126
+ ],
127
+ },
128
+ ]
129
+
130
+ /**
131
+ * Complete field definitions organized by tab
132
+ */
133
+ export const fieldDefinitions: FieldDefinition[] = [
134
+ // Content tab: base fields + timeline-specific
135
+ ...baseContentFields,
136
+ ...timelineContentFields,
137
+
138
+ // Design tab: base fields + timeline-specific
139
+ ...baseDesignFields,
140
+ ...timelineDesignFields,
141
+
142
+ // Advanced tab: base fields only
143
+ ...baseAdvancedFields,
144
+ ]
145
+
146
+ // Alias for compatibility
147
+ export const fields = fieldDefinitions
@@ -0,0 +1,6 @@
1
+ export { config } from './config'
2
+ export { schema } from './schema'
3
+ export { fields } from './fields'
4
+ export { TimelineBlock as Component } from './component'
5
+
6
+ export type { TimelineBlockProps, TimelineItem } from './schema'
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod'
2
+ import { baseBlockSchema } from '@nextsparkjs/core/types/blocks'
3
+
4
+ /**
5
+ * Timeline Item Schema
6
+ * Individual event/step in the timeline
7
+ */
8
+ const timelineItemSchema = z.object({
9
+ date: z.string().min(1, 'Date or step is required'),
10
+ title: z.string().min(1, 'Title is required').max(100),
11
+ description: z.string().optional().default(''),
12
+ icon: z.string().optional().default(''),
13
+ })
14
+
15
+ export type TimelineItem = z.infer<typeof timelineItemSchema>
16
+
17
+ /**
18
+ * Timeline Block Schema
19
+ *
20
+ * Extends base schema with:
21
+ * - subtitle: Section description (in addition to title)
22
+ * - items: Array of timeline items (date, title, description, icon)
23
+ * - layout: Vertical or horizontal direction
24
+ * - alternating: Alternate items left/right (vertical only)
25
+ * - showConnector: Show connecting line between items
26
+ * - variant: Visual style (default, minimal, cards)
27
+ *
28
+ * Note: Uses base schema title, backgroundColor, className, id
29
+ */
30
+ export const timelineSpecificSchema = z.object({
31
+ // Content fields
32
+ subtitle: z.string().optional().default(''),
33
+ items: z.array(timelineItemSchema)
34
+ .min(1, 'At least one timeline item is required')
35
+ .max(20, 'Maximum 20 timeline items allowed'),
36
+
37
+ // Design fields
38
+ layout: z.enum(['vertical', 'horizontal']).default('vertical'),
39
+ alternating: z.boolean().default(true),
40
+ showConnector: z.boolean().default(true),
41
+ variant: z.enum(['default', 'minimal', 'cards']).default('default'),
42
+ })
43
+
44
+ /**
45
+ * Complete Timeline Block Schema
46
+ */
47
+ export const schema = baseBlockSchema.merge(timelineSpecificSchema)
48
+
49
+ export type TimelineBlockProps = z.infer<typeof schema>
@@ -0,0 +1,270 @@
1
+ import React from 'react'
2
+ import { Button } from '@nextsparkjs/core/components/ui/button'
3
+ import { cn } from '@nextsparkjs/core/lib/utils'
4
+ import { buildSectionClasses } from '@nextsparkjs/core/types/blocks'
5
+ import { sel } from '../../lib/selectors'
6
+ import type { VideoHeroBlockProps } from './schema'
7
+
8
+ /**
9
+ * Video Hero Block Component
10
+ *
11
+ * Props from 3-tab structure:
12
+ * - Content: title, content (subtitle), cta, videoUrl, videoThumbnail
13
+ * - Design: backgroundColor, layout, autoplay, overlayOpacity
14
+ * - Advanced: className, id
15
+ */
16
+
17
+ /**
18
+ * Extract video ID from YouTube or Vimeo URL
19
+ */
20
+ function parseVideoUrl(url: string): { platform: 'youtube' | 'vimeo' | null; videoId: string | null } {
21
+ // YouTube patterns
22
+ const youtubePatterns = [
23
+ /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]+)/,
24
+ /youtube\.com\/watch\?.*v=([a-zA-Z0-9_-]+)/,
25
+ ]
26
+
27
+ for (const pattern of youtubePatterns) {
28
+ const match = url.match(pattern)
29
+ if (match) {
30
+ return { platform: 'youtube', videoId: match[1] }
31
+ }
32
+ }
33
+
34
+ // Vimeo pattern
35
+ const vimeoPattern = /vimeo\.com\/(\d+)/
36
+ const vimeoMatch = url.match(vimeoPattern)
37
+ if (vimeoMatch) {
38
+ return { platform: 'vimeo', videoId: vimeoMatch[1] }
39
+ }
40
+
41
+ return { platform: null, videoId: null }
42
+ }
43
+
44
+ /**
45
+ * Get embed URL for video
46
+ */
47
+ function getEmbedUrl(videoUrl: string, autoplay: boolean): string | null {
48
+ const { platform, videoId } = parseVideoUrl(videoUrl)
49
+
50
+ if (!platform || !videoId) return null
51
+
52
+ if (platform === 'youtube') {
53
+ const params = new URLSearchParams({
54
+ autoplay: autoplay ? '1' : '0',
55
+ mute: autoplay ? '1' : '0', // Mute if autoplay (browser requirement)
56
+ rel: '0', // Don't show related videos
57
+ modestbranding: '1', // Minimal YouTube branding
58
+ })
59
+ return `https://www.youtube.com/embed/${videoId}?${params.toString()}`
60
+ }
61
+
62
+ if (platform === 'vimeo') {
63
+ const params = new URLSearchParams({
64
+ autoplay: autoplay ? '1' : '0',
65
+ muted: autoplay ? '1' : '0',
66
+ title: '0',
67
+ byline: '0',
68
+ portrait: '0',
69
+ })
70
+ return `https://player.vimeo.com/video/${videoId}?${params.toString()}`
71
+ }
72
+
73
+ return null
74
+ }
75
+
76
+ export function VideoHeroBlock({
77
+ // Base content props
78
+ title,
79
+ content,
80
+ cta,
81
+ // Video-specific content
82
+ videoUrl,
83
+ videoThumbnail,
84
+ // Base design props
85
+ backgroundColor,
86
+ // Video-specific design
87
+ layout = 'inline',
88
+ autoplay = false,
89
+ overlayOpacity = '40',
90
+ // Base advanced props
91
+ className,
92
+ id,
93
+ }: VideoHeroBlockProps) {
94
+ const embedUrl = getEmbedUrl(videoUrl, autoplay)
95
+
96
+ // Build base section classes
97
+ const baseSectionClasses = buildSectionClasses(
98
+ cn('relative overflow-hidden'),
99
+ { backgroundColor, className }
100
+ )
101
+
102
+ // Overlay opacity class for background layout
103
+ const overlayClass = overlayOpacity === '0'
104
+ ? 'bg-transparent'
105
+ : `bg-black/${overlayOpacity}`
106
+
107
+ // Video container with 16:9 aspect ratio
108
+ const VideoEmbed = () => {
109
+ if (!embedUrl) {
110
+ return (
111
+ <div className="flex items-center justify-center bg-gray-200 text-gray-600 p-8 rounded-lg">
112
+ <p>Invalid video URL. Please provide a valid YouTube or Vimeo link.</p>
113
+ </div>
114
+ )
115
+ }
116
+
117
+ return (
118
+ <div className="relative w-full pb-[56.25%]">
119
+ <iframe
120
+ src={embedUrl}
121
+ className="absolute inset-0 w-full h-full"
122
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
123
+ allowFullScreen
124
+ title={title || 'Video'}
125
+ />
126
+ </div>
127
+ )
128
+ }
129
+
130
+ // LAYOUT: Background (fullscreen video with overlay)
131
+ if (layout === 'background') {
132
+ return (
133
+ <section
134
+ id={id}
135
+ className={cn(baseSectionClasses, 'min-h-[600px] flex items-center justify-center')}
136
+ data-cy={sel('blocks.videoHero.container')}
137
+ >
138
+ {/* Background Video */}
139
+ <div className="absolute inset-0 z-0">
140
+ {embedUrl ? (
141
+ <iframe
142
+ src={embedUrl}
143
+ className="absolute inset-0 w-full h-full object-cover"
144
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
145
+ title={title || 'Background Video'}
146
+ />
147
+ ) : null}
148
+ <div className={cn('absolute inset-0', overlayClass)} />
149
+ </div>
150
+
151
+ {/* Content Overlay */}
152
+ <div className="container relative z-10 mx-auto max-w-4xl text-center px-4 py-20 text-white">
153
+ {title && (
154
+ <h1 className="mb-6 text-5xl font-bold leading-tight md:text-6xl lg:text-7xl">
155
+ {title}
156
+ </h1>
157
+ )}
158
+
159
+ {content && (
160
+ <p className="mb-8 text-xl md:text-2xl opacity-90">
161
+ {content}
162
+ </p>
163
+ )}
164
+
165
+ {cta && (
166
+ <Button asChild size="lg" className="text-lg px-8 py-6">
167
+ <a
168
+ href={cta.link}
169
+ target={cta.target}
170
+ rel={cta.target === '_blank' ? 'noopener noreferrer' : undefined}
171
+ >
172
+ {cta.text}
173
+ </a>
174
+ </Button>
175
+ )}
176
+ </div>
177
+ </section>
178
+ )
179
+ }
180
+
181
+ // LAYOUT: Side-by-side (text left, video right)
182
+ if (layout === 'side-by-side') {
183
+ return (
184
+ <section
185
+ id={id}
186
+ className={cn(baseSectionClasses, 'py-16 px-4')}
187
+ data-cy={sel('blocks.videoHero.container')}
188
+ >
189
+ <div className="container mx-auto">
190
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
191
+ {/* Text Content */}
192
+ <div className="space-y-6">
193
+ {title && (
194
+ <h1 className="text-4xl font-bold leading-tight md:text-5xl lg:text-6xl">
195
+ {title}
196
+ </h1>
197
+ )}
198
+
199
+ {content && (
200
+ <p className="text-lg md:text-xl text-gray-600">
201
+ {content}
202
+ </p>
203
+ )}
204
+
205
+ {cta && (
206
+ <Button asChild size="lg" className="text-lg px-8 py-6">
207
+ <a
208
+ href={cta.link}
209
+ target={cta.target}
210
+ rel={cta.target === '_blank' ? 'noopener noreferrer' : undefined}
211
+ >
212
+ {cta.text}
213
+ </a>
214
+ </Button>
215
+ )}
216
+ </div>
217
+
218
+ {/* Video */}
219
+ <div>
220
+ <VideoEmbed />
221
+ </div>
222
+ </div>
223
+ </div>
224
+ </section>
225
+ )
226
+ }
227
+
228
+ // LAYOUT: Inline (default - text above, video below)
229
+ return (
230
+ <section
231
+ id={id}
232
+ className={cn(baseSectionClasses, 'py-16 px-4')}
233
+ data-cy="block-video-hero"
234
+ >
235
+ <div className="container mx-auto max-w-5xl">
236
+ {/* Text Content */}
237
+ <div className="text-center mb-10 space-y-6">
238
+ {title && (
239
+ <h1 className="text-4xl font-bold leading-tight md:text-5xl lg:text-6xl">
240
+ {title}
241
+ </h1>
242
+ )}
243
+
244
+ {content && (
245
+ <p className="text-lg md:text-xl text-gray-600 max-w-3xl mx-auto">
246
+ {content}
247
+ </p>
248
+ )}
249
+
250
+ {cta && (
251
+ <Button asChild size="lg" className="text-lg px-8 py-6">
252
+ <a
253
+ href={cta.link}
254
+ target={cta.target}
255
+ rel={cta.target === '_blank' ? 'noopener noreferrer' : undefined}
256
+ >
257
+ {cta.text}
258
+ </a>
259
+ </Button>
260
+ )}
261
+ </div>
262
+
263
+ {/* Video */}
264
+ <div className="max-w-4xl mx-auto">
265
+ <VideoEmbed />
266
+ </div>
267
+ </div>
268
+ </section>
269
+ )
270
+ }
@@ -0,0 +1,11 @@
1
+ import type { BlockConfig } from '@nextsparkjs/core/types/blocks'
2
+
3
+ export const config: Omit<BlockConfig, 'schema' | 'fieldDefinitions' | 'Component' | 'examples'> = {
4
+ slug: 'video-hero',
5
+ name: 'Video Hero',
6
+ description: 'A hero section with embedded video (YouTube/Vimeo) either as background or inline, with title, subtitle, and optional CTA',
7
+ category: 'hero',
8
+ icon: 'Video',
9
+ thumbnail: '/theme/blocks/video-hero/thumbnail.png',
10
+ scope: ['pages', 'posts']
11
+ }
@@ -0,0 +1,24 @@
1
+ import type { BlockExample } from '@nextsparkjs/core/types/blocks'
2
+
3
+ export const examples: BlockExample[] = [
4
+ {
5
+ name: 'Default',
6
+ description: 'Video hero with poster image',
7
+ props: {
8
+ title: 'See Our Platform in Action',
9
+ content: 'Watch how leading teams are transforming their workflows with our powerful tools.',
10
+ videoUrl: 'https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
11
+ posterImage: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=1920',
12
+ cta: {
13
+ text: 'Start Free Trial',
14
+ link: '/signup',
15
+ target: '_self',
16
+ },
17
+ backgroundColor: 'gray-900',
18
+ textColor: 'light',
19
+ autoplay: false,
20
+ muted: true,
21
+ loop: true,
22
+ },
23
+ },
24
+ ]
@@ -0,0 +1,98 @@
1
+ import type { FieldDefinition } from '@nextsparkjs/core/types/blocks'
2
+ import {
3
+ baseContentFields,
4
+ baseDesignFields,
5
+ baseAdvancedFields,
6
+ } from '@nextsparkjs/core/types/blocks'
7
+
8
+ /**
9
+ * Video Hero Block Field Definitions
10
+ *
11
+ * Organized into 3 tabs:
12
+ * - Content: title, content (subtitle), cta (from base) + videoUrl, videoThumbnail
13
+ * - Design: backgroundColor (from base) + layout, autoplay, overlayOpacity
14
+ * - Advanced: className, id (from base)
15
+ */
16
+
17
+ // Video Hero-specific content fields
18
+ const videoHeroContentFields: FieldDefinition[] = [
19
+ {
20
+ name: 'videoUrl',
21
+ label: 'Video URL',
22
+ type: 'url',
23
+ tab: 'content',
24
+ required: true,
25
+ placeholder: 'https://www.youtube.com/watch?v=... or https://vimeo.com/...',
26
+ helpText: 'YouTube or Vimeo video URL',
27
+ },
28
+ {
29
+ name: 'videoThumbnail',
30
+ label: 'Custom Thumbnail',
31
+ type: 'image',
32
+ tab: 'content',
33
+ required: false,
34
+ helpText: 'Optional custom thumbnail shown before video plays (recommended: 1920x1080px)',
35
+ },
36
+ ]
37
+
38
+ // Video Hero-specific design fields
39
+ const videoHeroDesignFields: FieldDefinition[] = [
40
+ {
41
+ name: 'layout',
42
+ label: 'Video Layout',
43
+ type: 'select',
44
+ tab: 'design',
45
+ required: false,
46
+ default: 'inline',
47
+ helpText: 'How the video is displayed',
48
+ options: [
49
+ { label: 'Inline (video centered with text above)', value: 'inline' },
50
+ { label: 'Background (video as fullscreen background)', value: 'background' },
51
+ { label: 'Side by Side (text left, video right)', value: 'side-by-side' },
52
+ ],
53
+ },
54
+ {
55
+ name: 'autoplay',
56
+ label: 'Autoplay Video',
57
+ type: 'checkbox',
58
+ tab: 'design',
59
+ required: false,
60
+ default: false,
61
+ checkboxLabel: 'Automatically play video when loaded (muted)',
62
+ helpText: 'Note: Most browsers require videos to be muted for autoplay',
63
+ },
64
+ {
65
+ name: 'overlayOpacity',
66
+ label: 'Overlay Opacity',
67
+ type: 'select',
68
+ tab: 'design',
69
+ required: false,
70
+ default: '40',
71
+ helpText: 'Darkness of overlay for background layout (helps text readability)',
72
+ options: [
73
+ { label: 'None (0%)', value: '0' },
74
+ { label: 'Light (20%)', value: '20' },
75
+ { label: 'Medium (40%)', value: '40' },
76
+ { label: 'Dark (60%)', value: '60' },
77
+ ],
78
+ },
79
+ ]
80
+
81
+ /**
82
+ * Complete field definitions organized by tab
83
+ */
84
+ export const fieldDefinitions: FieldDefinition[] = [
85
+ // Content tab: base fields + video-specific
86
+ ...baseContentFields,
87
+ ...videoHeroContentFields,
88
+
89
+ // Design tab: base fields + video-specific
90
+ ...baseDesignFields,
91
+ ...videoHeroDesignFields,
92
+
93
+ // Advanced tab: base fields only
94
+ ...baseAdvancedFields,
95
+ ]
96
+
97
+ // Alias for compatibility
98
+ export const fields = fieldDefinitions
@@ -0,0 +1,6 @@
1
+ export { config } from './config'
2
+ export { schema } from './schema'
3
+ export { fields } from './fields'
4
+ export { VideoHeroBlock as Component } from './component'
5
+
6
+ export type { VideoHeroBlockProps } from './schema'
@@ -0,0 +1,39 @@
1
+ import { z } from 'zod'
2
+ import {
3
+ baseBlockSchema,
4
+ type BaseBlockProps,
5
+ } from '@nextsparkjs/core/types/blocks'
6
+
7
+ /**
8
+ * Video Hero Block Schema
9
+ *
10
+ * Extends base schema with video-specific fields:
11
+ * - videoUrl: YouTube or Vimeo URL (required)
12
+ * - videoThumbnail: Custom thumbnail image
13
+ * - layout: How video is displayed (inline, background, side-by-side)
14
+ * - autoplay: Auto-play video when loaded (muted)
15
+ * - overlayOpacity: Opacity for background layout overlay
16
+ *
17
+ * Note: Uses base schema title, content (as subtitle), cta, backgroundColor, className, id
18
+ */
19
+ export const videoHeroSpecificSchema = z.object({
20
+ // Content fields
21
+ videoUrl: z.string().url('Must be a valid YouTube or Vimeo URL'),
22
+ videoThumbnail: z.string().url('Must be a valid URL').optional(),
23
+
24
+ // Design fields
25
+ layout: z.enum(['inline', 'background', 'side-by-side']).default('inline'),
26
+ autoplay: z.boolean().default(false),
27
+ overlayOpacity: z.enum(['0', '20', '40', '60']).default('40'),
28
+ })
29
+
30
+ /**
31
+ * Complete Video Hero Block Schema
32
+ * Combines base fields + video-hero-specific fields
33
+ */
34
+ export const schema = baseBlockSchema.merge(videoHeroSpecificSchema)
35
+
36
+ export type VideoHeroBlockProps = z.infer<typeof schema>
37
+
38
+ // Also export for type-only imports
39
+ export type { BaseBlockProps }