@nextsparkjs/core 0.1.0-beta.187 → 0.1.0-beta.189

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 (414) hide show
  1. package/dist/components/api/keys/ApiKeyDisplay.js +19 -19
  2. package/dist/components/api/keys/CreateApiKeyDialog.js +21 -21
  3. package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
  4. package/dist/components/app/layouts/PublicNavbar.js +34 -16
  5. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  6. package/dist/components/app/misc/ThemeToggle.js +3 -4
  7. package/dist/components/auth/SessionCookieRefresher.d.ts +5 -0
  8. package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -1
  9. package/dist/components/auth/SessionCookieRefresher.js +7 -1
  10. package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
  11. package/dist/components/auth/forms/LoginForm.js +49 -12
  12. package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
  13. package/dist/components/auth/forms/SignupForm.js +4 -3
  14. package/dist/components/dashboard/block-editor/block-settings-panel.js +1 -1
  15. package/dist/components/dashboard/block-editor/floating-block-toolbar.js +1 -1
  16. package/dist/components/dashboard/block-editor/sortable-block.js +1 -1
  17. package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
  18. package/dist/components/dashboard/layouts/Sidebar.js +8 -6
  19. package/dist/components/dashboard/layouts/TopNavbar.d.ts.map +1 -1
  20. package/dist/components/dashboard/layouts/TopNavbar.js +51 -54
  21. package/dist/components/dashboard/misc/NotificationsDropdown.d.ts.map +1 -1
  22. package/dist/components/dashboard/misc/NotificationsDropdown.js +17 -15
  23. package/dist/components/dashboard/misc/SearchDropdown.d.ts.map +1 -1
  24. package/dist/components/dashboard/misc/SearchDropdown.js +19 -22
  25. package/dist/components/dashboard/mobile/MobileBottomNav.js +1 -1
  26. package/dist/components/dashboard/mobile/MobileTopBar.d.ts +10 -1
  27. package/dist/components/dashboard/mobile/MobileTopBar.d.ts.map +1 -1
  28. package/dist/components/dashboard/mobile/MobileTopBar.js +4 -3
  29. package/dist/components/dashboard/navigation/DynamicNavigation.js +5 -5
  30. package/dist/components/devtools/BlockDetailViewer.js +1 -1
  31. package/dist/components/devtools/BlocksViewer.js +1 -1
  32. package/dist/components/entities/EntityChildManager.js +5 -5
  33. package/dist/components/entities/EntityDetailPage.d.ts.map +1 -1
  34. package/dist/components/entities/EntityDetailPage.js +16 -15
  35. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  36. package/dist/components/entities/EntityFieldRenderer.js +12 -7
  37. package/dist/components/entities/EntityPageHeader.js +5 -5
  38. package/dist/components/entities/ErrorBoundary.d.ts.map +1 -1
  39. package/dist/components/entities/ErrorBoundary.js +20 -21
  40. package/dist/components/entities/wrappers/EntityDetailWrapper.js +5 -5
  41. package/dist/components/entities/wrappers/EntityFormWrapper.js +2 -2
  42. package/dist/components/entities/wrappers/EntityListWrapper.js +3 -3
  43. package/dist/components/permissions/NoPermission.js +10 -10
  44. package/dist/components/public/pageBuilder/PageRenderer.d.ts.map +1 -1
  45. package/dist/components/public/pageBuilder/PageRenderer.js +2 -1
  46. package/dist/components/settings/SettingsPageSkeleton.js +4 -4
  47. package/dist/components/settings/layouts/SettingsSidebar.js +6 -6
  48. package/dist/components/shared/MultiSelectFilter.d.ts +3 -1
  49. package/dist/components/shared/MultiSelectFilter.d.ts.map +1 -1
  50. package/dist/components/shared/MultiSelectFilter.js +7 -3
  51. package/dist/components/shared/SearchInput.d.ts +2 -0
  52. package/dist/components/shared/SearchInput.d.ts.map +1 -1
  53. package/dist/components/shared/SearchInput.js +64 -6
  54. package/dist/components/ui/address-input.js +18 -18
  55. package/dist/components/ui/audio-upload.js +9 -9
  56. package/dist/components/ui/combobox.d.ts.map +1 -1
  57. package/dist/components/ui/combobox.js +4 -4
  58. package/dist/components/ui/country-select.d.ts.map +1 -1
  59. package/dist/components/ui/country-select.js +3 -3
  60. package/dist/components/ui/currency-select.d.ts.map +1 -1
  61. package/dist/components/ui/currency-select.js +3 -3
  62. package/dist/components/ui/dynamic-icon.d.ts.map +1 -1
  63. package/dist/components/ui/dynamic-icon.js +3 -2
  64. package/dist/components/ui/file-upload.js +7 -7
  65. package/dist/components/ui/image-upload.js +8 -8
  66. package/dist/components/ui/multi-select.d.ts +2 -1
  67. package/dist/components/ui/multi-select.d.ts.map +1 -1
  68. package/dist/components/ui/multi-select.js +4 -3
  69. package/dist/components/ui/phone-input.d.ts.map +1 -1
  70. package/dist/components/ui/phone-input.js +3 -3
  71. package/dist/components/ui/relation-display.js +1 -1
  72. package/dist/components/ui/simple-relation-select.d.ts +2 -1
  73. package/dist/components/ui/simple-relation-select.d.ts.map +1 -1
  74. package/dist/components/ui/simple-relation-select.js +7 -6
  75. package/dist/components/ui/tags-input.d.ts.map +1 -1
  76. package/dist/components/ui/tags-input.js +1 -1
  77. package/dist/components/ui/timezone-select.d.ts.map +1 -1
  78. package/dist/components/ui/timezone-select.js +3 -3
  79. package/dist/components/ui/user-display.js +4 -4
  80. package/dist/components/ui/user-select.d.ts.map +1 -1
  81. package/dist/components/ui/user-select.js +9 -9
  82. package/dist/components/ui/video-upload.js +9 -9
  83. package/dist/contexts/SubscriptionContext.d.ts.map +1 -1
  84. package/dist/contexts/SubscriptionContext.js +5 -1
  85. package/dist/contexts/TeamContext.d.ts.map +1 -1
  86. package/dist/contexts/TeamContext.js +83 -26
  87. package/dist/emails/otp-verification.d.ts.map +1 -1
  88. package/dist/emails/otp-verification.js +3 -2
  89. package/dist/hooks/useAccountPreferencesSync.d.ts +13 -0
  90. package/dist/hooks/useAccountPreferencesSync.d.ts.map +1 -0
  91. package/dist/hooks/useAccountPreferencesSync.js +95 -0
  92. package/dist/hooks/useAuth.d.ts +202 -15
  93. package/dist/hooks/useAuth.d.ts.map +1 -1
  94. package/dist/hooks/useAuth.js +49 -13
  95. package/dist/hooks/useEntityConfig.js +2 -2
  96. package/dist/hooks/useEntitySearch.js +10 -10
  97. package/dist/hooks/useNotifications.js +12 -12
  98. package/dist/hooks/usePrefetch.d.ts.map +1 -1
  99. package/dist/hooks/usePrefetch.js +6 -2
  100. package/dist/hooks/useSearch.js +15 -15
  101. package/dist/hooks/useSessionCookieRefresh.d.ts +18 -1
  102. package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -1
  103. package/dist/hooks/useSessionCookieRefresh.js +17 -4
  104. package/dist/hooks/useSessionHint.d.ts +12 -0
  105. package/dist/hooks/useSessionHint.d.ts.map +1 -0
  106. package/dist/hooks/useSessionHint.js +15 -0
  107. package/dist/hooks/useTranslationPreload.js +6 -6
  108. package/dist/i18n.d.ts +13 -0
  109. package/dist/i18n.d.ts.map +1 -1
  110. package/dist/i18n.js +2 -27
  111. package/dist/lib/actions/entity.actions.d.ts.map +1 -1
  112. package/dist/lib/actions/entity.actions.js +3 -2
  113. package/dist/lib/api/auth/dual-auth.d.ts +4 -1
  114. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  115. package/dist/lib/api/auth/dual-auth.js +5 -16
  116. package/dist/lib/api/entities.js +33 -33
  117. package/dist/lib/api/keys.d.ts +27 -27
  118. package/dist/lib/api/keys.js +27 -27
  119. package/dist/lib/auth/callback-url.d.ts +11 -0
  120. package/dist/lib/auth/callback-url.d.ts.map +1 -0
  121. package/dist/lib/auth/callback-url.js +14 -0
  122. package/dist/lib/auth/otp-config.d.ts +55 -0
  123. package/dist/lib/auth/otp-config.d.ts.map +1 -0
  124. package/dist/lib/auth/otp-config.js +48 -0
  125. package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
  126. package/dist/lib/auth/registration-helpers.js +3 -1
  127. package/dist/lib/auth/session-hint.d.ts +31 -0
  128. package/dist/lib/auth/session-hint.d.ts.map +1 -0
  129. package/dist/lib/auth/session-hint.js +37 -0
  130. package/dist/lib/auth.d.ts.map +1 -1
  131. package/dist/lib/auth.js +6 -4
  132. package/dist/lib/blocks/loader.d.ts +3 -11
  133. package/dist/lib/blocks/loader.d.ts.map +1 -1
  134. package/dist/lib/blocks/loader.js +1 -9
  135. package/dist/lib/blocks/loader.server.d.ts +18 -0
  136. package/dist/lib/blocks/loader.server.d.ts.map +1 -0
  137. package/dist/lib/blocks/loader.server.js +11 -0
  138. package/dist/lib/blocks/sanitize-html.d.ts +31 -0
  139. package/dist/lib/blocks/sanitize-html.d.ts.map +1 -0
  140. package/dist/lib/blocks/sanitize-html.js +60 -0
  141. package/dist/lib/config/config-sync.d.ts +1 -0
  142. package/dist/lib/config/config-sync.d.ts.map +1 -1
  143. package/dist/lib/config/config-sync.js +4 -1
  144. package/dist/lib/config/types.d.ts +48 -0
  145. package/dist/lib/config/types.d.ts.map +1 -1
  146. package/dist/lib/email/types.d.ts +6 -0
  147. package/dist/lib/email/types.d.ts.map +1 -1
  148. package/dist/lib/entities/serialization.d.ts.map +1 -1
  149. package/dist/lib/entities/serialization.js +3 -6
  150. package/dist/lib/icons.d.ts +12 -0
  151. package/dist/lib/icons.d.ts.map +1 -0
  152. package/dist/lib/icons.js +30 -0
  153. package/dist/lib/locale.d.ts +25 -1
  154. package/dist/lib/locale.d.ts.map +1 -1
  155. package/dist/lib/locale.js +49 -35
  156. package/dist/lib/mcp/engine.js +1 -1
  157. package/dist/lib/mcp/errors.js +17 -17
  158. package/dist/lib/mcp/schema-builder.js +16 -16
  159. package/dist/lib/mcp/tool-generator.js +18 -18
  160. package/dist/lib/search-highlighting.d.ts +7 -0
  161. package/dist/lib/search-highlighting.d.ts.map +1 -1
  162. package/dist/lib/search-highlighting.js +6 -2
  163. package/dist/lib/selectors/core-selectors.d.ts +2 -0
  164. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  165. package/dist/lib/selectors/domains/auth.selectors.d.ts +1 -0
  166. package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
  167. package/dist/lib/selectors/domains/auth.selectors.js +1 -0
  168. package/dist/lib/selectors/domains/entities.selectors.d.ts +2 -0
  169. package/dist/lib/selectors/domains/entities.selectors.d.ts.map +1 -1
  170. package/dist/lib/selectors/domains/entities.selectors.js +2 -0
  171. package/dist/lib/selectors/selectors.d.ts +4 -0
  172. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  173. package/dist/lib/services/block.service.js +1 -1
  174. package/dist/lib/teams/active-team-cookie.d.ts +24 -0
  175. package/dist/lib/teams/active-team-cookie.d.ts.map +1 -0
  176. package/dist/lib/teams/active-team-cookie.js +17 -0
  177. package/dist/lib/teams/dashboard-team.d.ts +20 -0
  178. package/dist/lib/teams/dashboard-team.d.ts.map +1 -0
  179. package/dist/lib/teams/dashboard-team.js +38 -0
  180. package/dist/lib/teams/schema.d.ts.map +1 -1
  181. package/dist/lib/teams/schema.js +1 -1
  182. package/dist/lib/template-resolver.d.ts.map +1 -1
  183. package/dist/lib/template-resolver.js +0 -1
  184. package/dist/lib/theme/get-default-theme-mode.d.ts +7 -4
  185. package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
  186. package/dist/lib/theme/get-default-theme-mode.js +2 -44
  187. package/dist/messages/de/auth.json +4 -2
  188. package/dist/messages/de/common.json +83 -3
  189. package/dist/messages/de/email.json +1 -1
  190. package/dist/messages/de/index.d.ts +87 -0
  191. package/dist/messages/de/index.d.ts.map +1 -1
  192. package/dist/messages/de/settings.json +5 -0
  193. package/dist/messages/en/auth.json +4 -2
  194. package/dist/messages/en/common.json +83 -3
  195. package/dist/messages/en/email.json +1 -1
  196. package/dist/messages/en/index.d.ts +86 -0
  197. package/dist/messages/en/index.d.ts.map +1 -1
  198. package/dist/messages/en/settings.json +4 -0
  199. package/dist/messages/es/auth.json +4 -2
  200. package/dist/messages/es/common.json +83 -3
  201. package/dist/messages/es/email.json +1 -1
  202. package/dist/messages/es/index.d.ts +86 -0
  203. package/dist/messages/es/index.d.ts.map +1 -1
  204. package/dist/messages/es/settings.json +4 -0
  205. package/dist/messages/fr/auth.json +4 -2
  206. package/dist/messages/fr/common.json +83 -3
  207. package/dist/messages/fr/email.json +1 -1
  208. package/dist/messages/fr/index.d.ts +87 -0
  209. package/dist/messages/fr/index.d.ts.map +1 -1
  210. package/dist/messages/fr/settings.json +5 -0
  211. package/dist/messages/it/auth.json +4 -2
  212. package/dist/messages/it/common.json +83 -3
  213. package/dist/messages/it/email.json +1 -1
  214. package/dist/messages/it/index.d.ts +87 -0
  215. package/dist/messages/it/index.d.ts.map +1 -1
  216. package/dist/messages/it/settings.json +5 -0
  217. package/dist/messages/pt/auth.json +4 -2
  218. package/dist/messages/pt/common.json +83 -3
  219. package/dist/messages/pt/email.json +1 -1
  220. package/dist/messages/pt/index.d.ts +87 -0
  221. package/dist/messages/pt/index.d.ts.map +1 -1
  222. package/dist/messages/pt/settings.json +5 -0
  223. package/dist/nextspark-registries.d.ts +54 -0
  224. package/dist/providers/DashboardProviders.d.ts +13 -10
  225. package/dist/providers/DashboardProviders.d.ts.map +1 -1
  226. package/dist/providers/DashboardProviders.js +7 -1
  227. package/dist/templates/app/(auth)/forgot-password/page.tsx +2 -2
  228. package/dist/templates/app/(auth)/reset-password/page.tsx +6 -6
  229. package/dist/templates/app/403/page.tsx +6 -6
  230. package/dist/templates/app/api/v1/teams/switch/route.ts +24 -5
  231. package/dist/templates/app/dashboard/(main)/[entity]/layout.tsx +9 -7
  232. package/dist/templates/app/dashboard/(main)/layout.tsx +4 -3
  233. package/dist/templates/app/dashboard/settings/api-keys/page.tsx +36 -34
  234. package/dist/templates/app/dashboard/settings/profile/page.tsx +7 -6
  235. package/dist/templates/app/dashboard/settings/security/page.tsx +5 -4
  236. package/dist/templates/app/layout.ppr.tsx +8 -8
  237. package/dist/templates/app/layout.tsx +12 -17
  238. package/dist/templates/blocks/cta-section/component.tsx +5 -2
  239. package/dist/templates/blocks/cta-section/config.ts +3 -2
  240. package/dist/templates/blocks/cta-section/fields.ts +2 -2
  241. package/dist/templates/blocks/cta-section/schema.ts +2 -2
  242. package/dist/templates/blocks/features-grid/component.tsx +10 -2
  243. package/dist/templates/blocks/features-grid/config.ts +3 -2
  244. package/dist/templates/blocks/features-grid/fields.ts +2 -2
  245. package/dist/templates/blocks/features-grid/schema.ts +2 -2
  246. package/dist/templates/blocks/hero/component.tsx +6 -3
  247. package/dist/templates/blocks/hero/config.ts +3 -2
  248. package/dist/templates/blocks/hero/fields.ts +2 -2
  249. package/dist/templates/blocks/hero/schema.ts +2 -2
  250. package/dist/templates/blocks/testimonials/component.tsx +6 -3
  251. package/dist/templates/blocks/testimonials/config.ts +3 -2
  252. package/dist/templates/blocks/testimonials/fields.ts +2 -2
  253. package/dist/templates/blocks/testimonials/schema.ts +2 -2
  254. package/dist/templates/blocks/text-content/component.tsx +7 -3
  255. package/dist/templates/blocks/text-content/config.ts +3 -2
  256. package/dist/templates/blocks/text-content/fields.ts +2 -2
  257. package/dist/templates/blocks/text-content/schema.ts +2 -2
  258. package/dist/templates/contents/themes/starter/blocks/cta-section/component.tsx +102 -0
  259. package/dist/templates/contents/themes/starter/blocks/cta-section/config.ts +11 -0
  260. package/dist/templates/contents/themes/starter/blocks/cta-section/fields.ts +89 -0
  261. package/dist/templates/contents/themes/starter/blocks/cta-section/index.ts +6 -0
  262. package/dist/templates/contents/themes/starter/blocks/cta-section/schema.ts +32 -0
  263. package/dist/templates/contents/themes/starter/blocks/cta-section/thumbnail.png +0 -0
  264. package/dist/templates/contents/themes/starter/blocks/features-grid/component.tsx +119 -0
  265. package/dist/templates/contents/themes/starter/blocks/features-grid/config.ts +11 -0
  266. package/dist/templates/contents/themes/starter/blocks/features-grid/fields.ts +97 -0
  267. package/dist/templates/contents/themes/starter/blocks/features-grid/index.ts +6 -0
  268. package/dist/templates/contents/themes/starter/blocks/features-grid/schema.ts +40 -0
  269. package/dist/templates/contents/themes/starter/blocks/features-grid/thumbnail.png +0 -0
  270. package/dist/templates/contents/themes/starter/blocks/hero/component.tsx +105 -0
  271. package/dist/templates/contents/themes/starter/blocks/hero/config.ts +11 -0
  272. package/dist/templates/contents/themes/starter/blocks/hero/fields.ts +60 -0
  273. package/dist/templates/contents/themes/starter/blocks/hero/index.ts +6 -0
  274. package/dist/templates/contents/themes/starter/blocks/hero/schema.ts +33 -0
  275. package/dist/templates/contents/themes/starter/blocks/hero/thumbnail.png +0 -0
  276. package/dist/templates/contents/themes/starter/blocks/testimonials/component.tsx +116 -0
  277. package/dist/templates/contents/themes/starter/blocks/testimonials/config.ts +11 -0
  278. package/dist/templates/contents/themes/starter/blocks/testimonials/fields.ts +105 -0
  279. package/dist/templates/contents/themes/starter/blocks/testimonials/index.ts +6 -0
  280. package/dist/templates/contents/themes/starter/blocks/testimonials/schema.ts +41 -0
  281. package/dist/templates/contents/themes/starter/blocks/testimonials/thumbnail.png +0 -0
  282. package/dist/templates/contents/themes/starter/blocks/text-content/component.tsx +100 -0
  283. package/dist/templates/contents/themes/starter/blocks/text-content/config.ts +11 -0
  284. package/dist/templates/contents/themes/starter/blocks/text-content/fields.ts +88 -0
  285. package/dist/templates/contents/themes/starter/blocks/text-content/index.ts +6 -0
  286. package/dist/templates/contents/themes/starter/blocks/text-content/schema.ts +30 -0
  287. package/dist/templates/contents/themes/starter/blocks/text-content/thumbnail.png +0 -0
  288. package/dist/templates/contents/themes/starter/config/app.config.ts +8 -0
  289. package/dist/templates/contents/themes/starter/entities/tasks/tasks.config.ts +7 -0
  290. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/devtools/access.cy.ts +9 -1
  291. package/dist/templates/contents/themes/starter/tests/cypress/e2e/api/entities/tasks/TaskAPIController.js +2 -2
  292. package/dist/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +45 -38
  293. package/dist/templates/contents/themes/starter/tests/cypress/fixtures/entities.json +16 -0
  294. package/dist/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +3 -1
  295. package/dist/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +3 -3
  296. package/dist/templates/contents/themes/starter/tests/cypress/src/entities/PagesPOM.ts +137 -0
  297. package/dist/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +2 -0
  298. package/dist/templates/contents/themes/starter/tests/cypress/src/index.ts +1 -0
  299. package/dist/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +42 -11
  300. package/dist/templates/contents/themes/starter/tests/cypress/support/commands.ts +29 -18
  301. package/dist/templates/features/blog/blocks/post-content/component.tsx +5 -1
  302. package/dist/templates/features/blog/blocks/post-content/schema.ts +1 -1
  303. package/dist/templates/features/pages/blocks/hero/component.tsx +3 -0
  304. package/dist/templates/features/pages/blocks/hero/schema.ts +1 -1
  305. package/dist/templates/proxy.ts +138 -53
  306. package/dist/types/blocks.d.ts.map +1 -1
  307. package/dist/types/blocks.js +1 -1
  308. package/dist/types/task.types.d.ts +1 -1
  309. package/dist/types/task.types.js +7 -7
  310. package/dist/utils/dev/TranslationDebugger.js +2 -2
  311. package/package.json +14 -5
  312. package/scripts/build/registry/__tests__/block-registry.test.mjs +73 -0
  313. package/scripts/build/registry/__tests__/icons-discovery.test.mjs +130 -0
  314. package/scripts/build/registry/__tests__/identifier.test.mjs +65 -0
  315. package/scripts/build/registry/__tests__/page-generator.test.mjs +430 -0
  316. package/scripts/build/registry/__tests__/scheduled-actions-registry.test.mjs +41 -0
  317. package/scripts/build/registry/__tests__/template-registry.test.mjs +89 -0
  318. package/scripts/build/registry/discovery/icons.mjs +275 -0
  319. package/scripts/build/registry/discovery/index.mjs +1 -0
  320. package/scripts/build/registry/generators/block-registry.mjs +203 -55
  321. package/scripts/build/registry/generators/feature-registry.mjs +9 -2
  322. package/scripts/build/registry/generators/icon-registry.mjs +56 -0
  323. package/scripts/build/registry/generators/identifier.mjs +28 -0
  324. package/scripts/build/registry/generators/index.mjs +1 -0
  325. package/scripts/build/registry/generators/plugin-registry.mjs +4 -3
  326. package/scripts/build/registry/generators/scheduled-actions-registry.mjs +4 -2
  327. package/scripts/build/registry/generators/template-registry.mjs +49 -41
  328. package/scripts/build/registry/generators/theme-registry.mjs +6 -7
  329. package/scripts/build/registry/post-build/page-generator.mjs +380 -59
  330. package/scripts/build/registry.mjs +9 -1
  331. package/templates/app/(auth)/forgot-password/page.tsx +2 -2
  332. package/templates/app/(auth)/reset-password/page.tsx +6 -6
  333. package/templates/app/403/page.tsx +6 -6
  334. package/templates/app/api/v1/teams/switch/route.ts +24 -5
  335. package/templates/app/dashboard/(main)/[entity]/layout.tsx +9 -7
  336. package/templates/app/dashboard/(main)/layout.tsx +4 -3
  337. package/templates/app/dashboard/settings/api-keys/page.tsx +36 -34
  338. package/templates/app/dashboard/settings/profile/page.tsx +7 -6
  339. package/templates/app/dashboard/settings/security/page.tsx +5 -4
  340. package/templates/app/layout.ppr.tsx +8 -8
  341. package/templates/app/layout.tsx +12 -17
  342. package/templates/blocks/cta-section/component.tsx +5 -2
  343. package/templates/blocks/cta-section/config.ts +3 -2
  344. package/templates/blocks/cta-section/fields.ts +2 -2
  345. package/templates/blocks/cta-section/schema.ts +2 -2
  346. package/templates/blocks/features-grid/component.tsx +10 -2
  347. package/templates/blocks/features-grid/config.ts +3 -2
  348. package/templates/blocks/features-grid/fields.ts +2 -2
  349. package/templates/blocks/features-grid/schema.ts +2 -2
  350. package/templates/blocks/hero/component.tsx +6 -3
  351. package/templates/blocks/hero/config.ts +3 -2
  352. package/templates/blocks/hero/fields.ts +2 -2
  353. package/templates/blocks/hero/schema.ts +2 -2
  354. package/templates/blocks/testimonials/component.tsx +6 -3
  355. package/templates/blocks/testimonials/config.ts +3 -2
  356. package/templates/blocks/testimonials/fields.ts +2 -2
  357. package/templates/blocks/testimonials/schema.ts +2 -2
  358. package/templates/blocks/text-content/component.tsx +7 -3
  359. package/templates/blocks/text-content/config.ts +3 -2
  360. package/templates/blocks/text-content/fields.ts +2 -2
  361. package/templates/blocks/text-content/schema.ts +2 -2
  362. package/templates/contents/themes/starter/blocks/cta-section/component.tsx +102 -0
  363. package/templates/contents/themes/starter/blocks/cta-section/config.ts +11 -0
  364. package/templates/contents/themes/starter/blocks/cta-section/fields.ts +89 -0
  365. package/templates/contents/themes/starter/blocks/cta-section/index.ts +6 -0
  366. package/templates/contents/themes/starter/blocks/cta-section/schema.ts +32 -0
  367. package/templates/contents/themes/starter/blocks/cta-section/thumbnail.png +0 -0
  368. package/templates/contents/themes/starter/blocks/features-grid/component.tsx +119 -0
  369. package/templates/contents/themes/starter/blocks/features-grid/config.ts +11 -0
  370. package/templates/contents/themes/starter/blocks/features-grid/fields.ts +97 -0
  371. package/templates/contents/themes/starter/blocks/features-grid/index.ts +6 -0
  372. package/templates/contents/themes/starter/blocks/features-grid/schema.ts +40 -0
  373. package/templates/contents/themes/starter/blocks/features-grid/thumbnail.png +0 -0
  374. package/templates/contents/themes/starter/blocks/hero/component.tsx +105 -0
  375. package/templates/contents/themes/starter/blocks/hero/config.ts +11 -0
  376. package/templates/contents/themes/starter/blocks/hero/fields.ts +60 -0
  377. package/templates/contents/themes/starter/blocks/hero/index.ts +6 -0
  378. package/templates/contents/themes/starter/blocks/hero/schema.ts +33 -0
  379. package/templates/contents/themes/starter/blocks/hero/thumbnail.png +0 -0
  380. package/templates/contents/themes/starter/blocks/testimonials/component.tsx +116 -0
  381. package/templates/contents/themes/starter/blocks/testimonials/config.ts +11 -0
  382. package/templates/contents/themes/starter/blocks/testimonials/fields.ts +105 -0
  383. package/templates/contents/themes/starter/blocks/testimonials/index.ts +6 -0
  384. package/templates/contents/themes/starter/blocks/testimonials/schema.ts +41 -0
  385. package/templates/contents/themes/starter/blocks/testimonials/thumbnail.png +0 -0
  386. package/templates/contents/themes/starter/blocks/text-content/component.tsx +100 -0
  387. package/templates/contents/themes/starter/blocks/text-content/config.ts +11 -0
  388. package/templates/contents/themes/starter/blocks/text-content/fields.ts +88 -0
  389. package/templates/contents/themes/starter/blocks/text-content/index.ts +6 -0
  390. package/templates/contents/themes/starter/blocks/text-content/schema.ts +30 -0
  391. package/templates/contents/themes/starter/blocks/text-content/thumbnail.png +0 -0
  392. package/templates/contents/themes/starter/config/app.config.ts +8 -0
  393. package/templates/contents/themes/starter/entities/tasks/tasks.config.ts +7 -0
  394. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/devtools/access.cy.ts +9 -1
  395. package/templates/contents/themes/starter/tests/cypress/e2e/api/entities/tasks/TaskAPIController.js +2 -2
  396. package/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +45 -38
  397. package/templates/contents/themes/starter/tests/cypress/fixtures/entities.json +16 -0
  398. package/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +3 -1
  399. package/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +3 -3
  400. package/templates/contents/themes/starter/tests/cypress/src/entities/PagesPOM.ts +137 -0
  401. package/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +2 -0
  402. package/templates/contents/themes/starter/tests/cypress/src/index.ts +1 -0
  403. package/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +42 -11
  404. package/templates/contents/themes/starter/tests/cypress/support/commands.ts +29 -18
  405. package/templates/features/blog/blocks/post-content/component.tsx +5 -1
  406. package/templates/features/blog/blocks/post-content/schema.ts +1 -1
  407. package/templates/features/pages/blocks/hero/component.tsx +3 -0
  408. package/templates/features/pages/blocks/hero/schema.ts +1 -1
  409. package/templates/proxy.ts +138 -53
  410. package/tests/jest/__mocks__/@nextsparkjs/registries/block-registry.client.ts +9 -0
  411. package/tests/jest/__mocks__/@nextsparkjs/registries/block-registry.lazy.ts +5 -0
  412. package/tests/jest/__mocks__/@nextsparkjs/registries/icon-registry.ts +42 -0
  413. package/tests/jest/__mocks__/next-intl-server.js +85 -5
  414. package/tests/jest/__mocks__/next-server.js +32 -0
@@ -187,6 +187,39 @@ declare module '@nextsparkjs/registries/block-registry' {
187
187
  export function getAllBlocks(): BlockConfig[]
188
188
  }
189
189
 
190
+ // ============================================================================
191
+ // Block Registry (client-safe) — the configs, with no component imports.
192
+ //
193
+ // A block component is a server component and may import server-only code, so
194
+ // a client component reading the registry has to take this half: importing the
195
+ // other one pulls every block component into the browser build, which fails as
196
+ // soon as one of them is server-only.
197
+ // ============================================================================
198
+ declare module '@nextsparkjs/registries/block-registry.client' {
199
+ export type {
200
+ FieldType,
201
+ FieldTab,
202
+ BlockCategory,
203
+ FieldDefinition,
204
+ BlockExample,
205
+ BlockConfig,
206
+ } from '@nextsparkjs/registries/block-registry'
207
+
208
+ export const BLOCK_REGISTRY: Record<string, import('@nextsparkjs/registries/block-registry').BlockConfig>
209
+ export const BLOCK_CATEGORIES: import('@nextsparkjs/registries/block-registry').BlockCategory[]
210
+ export const BLOCK_METADATA: {
211
+ totalBlocks: number
212
+ categories: string[]
213
+ blocksByCategory: Record<string, number>
214
+ generatedAt: string
215
+ blocks: string[]
216
+ }
217
+ }
218
+
219
+ declare module '@nextsparkjs/registries/block-registry.lazy' {
220
+ export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<React.ComponentType<any>> | React.ComponentType<any>>
221
+ }
222
+
190
223
  // ============================================================================
191
224
  // Entity Registry (Server) — declared for completeness; no direct import
192
225
  // found in packages/core/src today, but kept in case a consumer-facing
@@ -871,3 +904,24 @@ declare module '@nextsparkjs/registries/scheduled-actions-registry' {
871
904
 
872
905
  export const SCHEDULED_ACTIONS_REGISTRY: Record<string, ScheduledActionsModule | undefined>
873
906
  }
907
+
908
+ // ============================================================================
909
+ // Icon Registry
910
+ //
911
+ // Generated from every config that names an icon by string (entity configs,
912
+ // a theme's sidebar sections, block configs) so the app can resolve those
913
+ // names without a namespace import of lucide-react, which would keep the
914
+ // whole icon set in the bundle (#182). Inline `import(...)` type query for
915
+ // the same reason as middleware-registry below: this file must stay free of
916
+ // file-level imports.
917
+ // ============================================================================
918
+ declare module '@nextsparkjs/registries/icon-registry' {
919
+ export const ICON_REGISTRY: Record<string, import('lucide-react').LucideIcon>
920
+
921
+ export type IconName = string
922
+
923
+ export const ICON_METADATA: {
924
+ totalIcons: number
925
+ generatedAt: string
926
+ }
927
+ }
@@ -1,17 +1,20 @@
1
1
  /**
2
2
  * DashboardProviders — Client providers needed by authenticated routes.
3
3
  *
4
- * Since #115 the generated root layout mounts QueryProvider, TeamProvider,
5
- * SubscriptionProvider and Toaster itself (matching layout.ppr.tsx) so the
6
- * activeTeamId cookie is synced from any route, not only from the dashboard.
7
- * When a TeamProvider is already mounted above, this component renders its
8
- * children as-is: nesting a second TeamProvider would duplicate the teams
9
- * fetch, the cookie sync and the TeamSwitchModal, and a second Toaster would
10
- * show every toast twice.
4
+ * TeamProvider and SubscriptionProvider live here rather than in the root
5
+ * layout, so public pages never fetch a signed-in visitor's teams or
6
+ * subscription, nor re-sync the activeTeamId cookie. The areas that need a
7
+ * team (dashboard, superadmin, devtools) mount them through this component,
8
+ * which also keeps that cookie in sync.
11
9
  *
12
- * The full provider stack is kept as a fallback for apps whose generated root
13
- * layout predates that change (it originally lived here to keep ~500KB of
14
- * client JS off public landing pages).
10
+ * What gets mounted depends on the root layout above:
11
+ * - A root layout that provides QueryProvider and Toaster: only the team and
12
+ * subscription providers, so the app keeps one query cache and toasts are
13
+ * not shown twice.
14
+ * - An older root layout that already mounts TeamProvider: nothing, since a
15
+ * second TeamProvider would duplicate the teams fetch, the cookie sync and
16
+ * the TeamSwitchModal.
17
+ * - A root layout with no providers at all: the full stack, Toaster included.
15
18
  *
16
19
  * Used by: dashboard/layout.tsx, superadmin/layout.tsx, devtools/layout.tsx
17
20
  */
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardProviders.d.ts","sourceRoot":"","sources":["../../src/providers/DashboardProviders.tsx"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,2CAiB7E"}
1
+ {"version":3,"file":"DashboardProviders.d.ts","sourceRoot":"","sources":["../../src/providers/DashboardProviders.tsx"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,2CA4B7E"}
@@ -1,15 +1,21 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { Suspense } from "react";
3
+ import { Suspense, useContext } from "react";
4
+ import { QueryClientContext } from "@tanstack/react-query";
4
5
  import { QueryProvider } from "./query-provider.js";
5
6
  import { TeamProvider, useOptionalTeamContext } from "../contexts/TeamContext.js";
6
7
  import { SubscriptionProvider } from "../contexts/SubscriptionContext.js";
7
8
  import { Toaster } from "../components/ui/sonner.js";
8
9
  function DashboardProviders({ children }) {
9
10
  const rootTeamContext = useOptionalTeamContext();
11
+ const rootQueryClient = useContext(QueryClientContext);
10
12
  if (rootTeamContext) {
11
13
  return /* @__PURE__ */ jsx(Fragment, { children });
12
14
  }
15
+ const teamProviders = /* @__PURE__ */ jsx(TeamProvider, { children: /* @__PURE__ */ jsx(SubscriptionProvider, { children }) });
16
+ if (rootQueryClient) {
17
+ return teamProviders;
18
+ }
13
19
  return /* @__PURE__ */ jsx(QueryProvider, { children: /* @__PURE__ */ jsx(TeamProvider, { children: /* @__PURE__ */ jsxs(SubscriptionProvider, { children: [
14
20
  children,
15
21
  /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(Toaster, { position: "bottom-left" }) })
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { resetPasswordSchema, type ResetPasswordFormData } from '@nextsparkjs/core/lib/validation';
4
- import { useAuth } from '@nextsparkjs/core/hooks/useAuth';
4
+ import { useAuthActions } from '@nextsparkjs/core/hooks/useAuth';
5
5
  import { zodResolver } from '@hookform/resolvers/zod';
6
6
  import { AlertCircle, ArrowLeft, CheckCircle, Loader2, Mail } from 'lucide-react';
7
7
  import Link from 'next/link';
@@ -18,7 +18,7 @@ import { getTemplateOrDefaultClient } from '@nextsparkjs/registries/template-reg
18
18
 
19
19
 
20
20
  function ForgotPasswordPage() {
21
- const { resetPassword } = useAuth();
21
+ const { resetPassword } = useAuthActions();
22
22
  const [loading, setLoading] = useState(false);
23
23
  const [error, setError] = useState<string | null>(null);
24
24
  const [success, setSuccess] = useState(false);
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { newPasswordSchema, type NewPasswordFormData } from "@nextsparkjs/core/lib/validation";
4
- import { useAuth } from "@nextsparkjs/core/hooks/useAuth";
4
+ import { useAuthActions } from "@nextsparkjs/core/hooks/useAuth";
5
5
  import { zodResolver } from "@hookform/resolvers/zod";
6
6
  import {
7
7
  AlertCircle,
@@ -24,7 +24,7 @@ import { sel } from '@nextsparkjs/core/selectors'
24
24
  function ResetPasswordContent() {
25
25
  const searchParams = useSearchParams()!;
26
26
  const token = searchParams.get("token") || "";
27
- const { updatePassword } = useAuth();
27
+ const { updatePassword } = useAuthActions();
28
28
 
29
29
  const [loading, setLoading] = useState(false);
30
30
  const [error, setError] = useState<string | null>(null);
@@ -57,13 +57,13 @@ function ResetPasswordContent() {
57
57
 
58
58
  const onSubmit = useCallback(async (data: NewPasswordFormData) => {
59
59
  if (!token) {
60
- setError("Token de recuperación inválido");
60
+ setError("Invalid recovery token");
61
61
  return;
62
62
  }
63
63
 
64
64
  setLoading(true);
65
65
  setError(null);
66
- console.log('Actualizando contraseña...');
66
+ console.log('Updating password...');
67
67
 
68
68
  try {
69
69
  const result = await updatePassword(data.password, token);
@@ -73,10 +73,10 @@ function ResetPasswordContent() {
73
73
  setError(errorMsg);
74
74
  } else {
75
75
  setSuccess(true);
76
- console.log('Contraseña actualizada exitosamente');
76
+ console.log('Password updated successfully');
77
77
  }
78
78
  } catch {
79
- setError('Error inesperado al actualizar contraseña');
79
+ setError('Unexpected error updating password');
80
80
  } finally {
81
81
  setLoading(false);
82
82
  }
@@ -10,7 +10,7 @@ import { getTemplateOrDefaultClient } from '@nextsparkjs/registries/template-reg
10
10
 
11
11
  function ForbiddenContent() {
12
12
  const searchParams = useSearchParams()!
13
- const reason = searchParams.get('reason') || 'No tienes permisos para acceder a este recurso'
13
+ const reason = searchParams.get('reason') || 'You do not have permission to access this resource'
14
14
  const upgrade = searchParams.get('upgrade') === 'true'
15
15
 
16
16
  return (
@@ -21,7 +21,7 @@ function ForbiddenContent() {
21
21
  <AlertTriangle className="h-8 w-8 text-yellow-600" />
22
22
  </div>
23
23
  <CardTitle className="text-2xl">
24
- ⚠️ Ups, no tienes permisos para hacer esto
24
+ ⚠️ Oops, you do not have permission to do this
25
25
  </CardTitle>
26
26
  <CardDescription className="text-gray-600">
27
27
  {reason}
@@ -32,11 +32,11 @@ function ForbiddenContent() {
32
32
  {upgrade && (
33
33
  <div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
34
34
  <p className="text-sm text-blue-800">
35
- Esta funcionalidad requiere una mejora de tu plan actual.
35
+ This feature requires an upgrade to your current plan.
36
36
  </p>
37
37
  <Link href="/dashboard/settings/billing" className="block mt-2">
38
38
  <Button variant="outline" size="sm" className="w-full">
39
- Ver planes
39
+ View plans
40
40
  </Button>
41
41
  </Link>
42
42
  </div>
@@ -49,7 +49,7 @@ function ForbiddenContent() {
49
49
  onClick={() => window.history.back()}
50
50
  >
51
51
  <ArrowLeft className="h-4 w-4 mr-2" />
52
- Volver
52
+ Back
53
53
  </Button>
54
54
 
55
55
  <Link href="/dashboard" className="flex-1">
@@ -75,7 +75,7 @@ function ForbiddenPage() {
75
75
  <AlertTriangle className="h-8 w-8 text-yellow-600" />
76
76
  </div>
77
77
  <CardTitle className="text-2xl">
78
- ⚠️ Ups, no tienes permisos para hacer esto
78
+ ⚠️ Oops, you do not have permission to do this
79
79
  </CardTitle>
80
80
  </CardHeader>
81
81
  </Card>
@@ -9,6 +9,11 @@ import {
9
9
  import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth'
10
10
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
11
11
  import { TeamService } from '@nextsparkjs/core/lib/services'
12
+ import {
13
+ ACTIVE_TEAM_COOKIE,
14
+ ACTIVE_TEAM_COOKIE_MAX_AGE,
15
+ activeTeamCookieValue,
16
+ } from '@nextsparkjs/core/lib/teams/active-team-cookie'
12
17
  import { z } from 'zod'
13
18
 
14
19
  const switchTeamSchema = z.object({
@@ -35,6 +40,19 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
35
40
  return authResult.rateLimitResponse as NextResponse
36
41
  }
37
42
 
43
+ // The active team is remembered per browser session, in the activeTeamId
44
+ // cookie. An API key chooses the team on each request with x-team-id, so for
45
+ // it there is nothing to switch.
46
+ if (authResult.type !== 'session' || !authResult.sessionId) {
47
+ const response = createApiError(
48
+ 'API keys choose the team on each request with the x-team-id header',
49
+ 400,
50
+ null,
51
+ 'SESSION_REQUIRED'
52
+ )
53
+ return addCorsHeaders(response, req)
54
+ }
55
+
38
56
  const body = await req.json()
39
57
  const validatedData = switchTeamSchema.parse(body)
40
58
 
@@ -48,15 +66,16 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
48
66
  message: 'Active team switched successfully',
49
67
  })
50
68
 
51
- // Set cookie for server-side access (layouts, server components)
52
- // This enables permission validation in [entity]/layout.tsx
69
+ // Remember the team for server-side reads (the dashboard layouts through
70
+ // the proxy, server actions, resolveTeamContext), bound to this session so
71
+ // a later session on the same browser does not inherit it.
53
72
  // SECURITY: httpOnly prevents client-side JS manipulation
54
- response.cookies.set('activeTeamId', validatedData.teamId, {
73
+ response.cookies.set(ACTIVE_TEAM_COOKIE, activeTeamCookieValue(authResult.sessionId, validatedData.teamId), {
55
74
  path: '/',
56
- httpOnly: true, // Security: prevent client-side manipulation
75
+ httpOnly: true,
57
76
  secure: process.env.NODE_ENV === 'production',
58
77
  sameSite: 'lax',
59
- maxAge: 60 * 60 * 24 * 365 // 1 year
78
+ maxAge: ACTIVE_TEAM_COOKIE_MAX_AGE,
60
79
  })
61
80
 
62
81
  return addCorsHeaders(response, req)
@@ -8,14 +8,17 @@
8
8
  *
9
9
  * Flow:
10
10
  * 1. User navigates to /dashboard/companies/create
11
- * 2. Browser sends activeTeamId cookie automatically
12
- * 3. This layout reads cookie and checks permission via checkPermission()
11
+ * 2. The proxy forwards the session's active team, taken from the
12
+ * activeTeamId cookie only when this session wrote it; without one, the
13
+ * user's default team is checked
14
+ * 3. This layout takes that team (getDashboardTeamId) and checks permission via checkPermission()
13
15
  * 4. If denied, redirects to /dashboard/permission-denied
14
16
  * 5. If allowed, renders the page (children)
15
17
  */
16
- import { headers, cookies } from 'next/headers'
18
+ import { headers } from 'next/headers'
17
19
  import { redirect } from 'next/navigation'
18
20
  import { checkPermission } from '@nextsparkjs/core/lib/permissions/check'
21
+ import { getDashboardTeamId } from '@nextsparkjs/core/lib/teams/dashboard-team'
19
22
  import { isValidPermission } from '@nextsparkjs/core/lib/permissions/init'
20
23
  import type { Permission } from '@nextsparkjs/core/lib/permissions/types'
21
24
 
@@ -59,7 +62,6 @@ export default async function EntityPermissionLayout({
59
62
  }: EntityLayoutProps) {
60
63
  const { entity } = await params
61
64
  const headersList = await headers()
62
- const cookieStore = await cookies()
63
65
 
64
66
  // Get pathname from middleware header
65
67
  const pathname = headersList.get('x-pathname') || ''
@@ -67,12 +69,12 @@ export default async function EntityPermissionLayout({
67
69
  // Get userId from middleware header (set for all authenticated routes)
68
70
  const userId = headersList.get('x-user-id')
69
71
 
70
- // Get teamId from cookie (set by TeamContext and /api/v1/teams/switch)
71
- const teamId = cookieStore.get('activeTeamId')?.value
72
+ // The team this session chose, or the user's default team until it has one
73
+ const teamId = userId ? await getDashboardTeamId(headersList, userId) : null
72
74
 
73
75
  // Skip validation if missing required data
74
76
  // - No userId: middleware will redirect to login (shouldn't happen for dashboard routes)
75
- // - No teamId: user hasn't selected a team yet, let page handle it
77
+ // - No teamId: the user belongs to no team, let page handle it
76
78
  if (!userId || !teamId) {
77
79
  console.log('[EntityPermissionLayout] Skipping validation - missing data:', {
78
80
  entity,
@@ -9,7 +9,7 @@
9
9
  * IMPORTANT: Permission checking MUST be in this layout (not in [entity]/layout.tsx)
10
10
  * because (templates) routes with specific paths take precedence over dynamic [entity] routes.
11
11
  */
12
- import { headers, cookies } from 'next/headers'
12
+ import { headers } from 'next/headers'
13
13
  import { redirect } from 'next/navigation'
14
14
  import { DashboardShell } from '@nextsparkjs/core/components/dashboard/layouts/DashboardShell'
15
15
  import { serializeEntityConfig, type SerializableEntityConfig } from '@nextsparkjs/core/lib/entities/serialization'
@@ -17,6 +17,7 @@ import { setEntityRegistry } from '@nextsparkjs/core/lib/entities/queries'
17
17
  import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
18
18
  import type { EntityConfig, ChildEntityDefinition } from '@nextsparkjs/core/lib/entities/types'
19
19
  import { checkPermission } from '@nextsparkjs/core/lib/permissions/check'
20
+ import { getDashboardTeamId } from '@nextsparkjs/core/lib/teams/dashboard-team'
20
21
  import { isValidPermission } from '@nextsparkjs/core/lib/permissions/init'
21
22
  import type { Permission } from '@nextsparkjs/core/lib/permissions/types'
22
23
  // Import registry directly - webpack resolves @nextsparkjs/registries alias at compile time
@@ -64,11 +65,11 @@ async function DefaultMainDashboardLayout({
64
65
 
65
66
  // === PERMISSION CHECK ===
66
67
  const headersList = await headers()
67
- const cookieStore = await cookies()
68
68
 
69
69
  const pathname = headersList.get('x-pathname') || ''
70
70
  const userId = headersList.get('x-user-id')
71
- const teamId = cookieStore.get('activeTeamId')?.value
71
+ // The team this session chose, or the user's default team until it has one
72
+ const teamId = userId ? await getDashboardTeamId(headersList, userId) : null
72
73
 
73
74
  if (userId && teamId && pathname) {
74
75
  const parsed = parseEntityFromPathname(pathname)
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState } from 'react';
4
+ import { useLocale } from 'next-intl';
4
5
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
5
6
  import { Button } from '@nextsparkjs/core/components/ui/button';
6
7
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nextsparkjs/core/components/ui/card';
@@ -66,6 +67,7 @@ function ApiKeysPage() {
66
67
  const [newApiKey, setNewApiKey] = useState<NewApiKeyResponse | null>(null);
67
68
  const [selectedKey, setSelectedKey] = useState<string | null>(null);
68
69
  const queryClient = useQueryClient();
70
+ const locale = useLocale();
69
71
 
70
72
  // Fetch API keys
71
73
  const { data: apiKeys, isLoading, error } = useQuery<ApiKey[]>({
@@ -138,7 +140,7 @@ function ApiKeysPage() {
138
140
  };
139
141
 
140
142
  const formatDate = (dateString: string) => {
141
- return new Date(dateString).toLocaleDateString('es-ES', {
143
+ return new Date(dateString).toLocaleDateString(locale, {
142
144
  year: 'numeric',
143
145
  month: 'short',
144
146
  day: 'numeric',
@@ -162,7 +164,7 @@ function ApiKeysPage() {
162
164
  <div>
163
165
  <h1 className="text-2xl font-bold">API Keys</h1>
164
166
  <p className="text-muted-foreground">
165
- Gestiona las API keys para integración externa
167
+ Manage API keys for external integration
166
168
  </p>
167
169
  </div>
168
170
  <Alert variant={isPermissionError ? "default" : "destructive"}>
@@ -170,10 +172,10 @@ function ApiKeysPage() {
170
172
  <AlertDescription>
171
173
  {isPermissionError ? (
172
174
  <div>
173
- <strong>Acceso restringido</strong>
175
+ <strong>Restricted access</strong>
174
176
  <p className="mt-1">
175
- Solo los administradores pueden gestionar API keys.
176
- Contacta a un administrador si necesitas acceso a esta funcionalidad.
177
+ Only administrators can manage API keys.
178
+ Contact an administrator if you need access to this feature.
177
179
  </p>
178
180
  </div>
179
181
  ) : (
@@ -191,12 +193,12 @@ function ApiKeysPage() {
191
193
  <div>
192
194
  <h1 className="text-2xl font-bold" data-cy={sel('settings.apiKeys.header')}>API Keys</h1>
193
195
  <p className="text-muted-foreground">
194
- Gestiona las API keys para integración externa
196
+ Manage API keys for external integration
195
197
  </p>
196
198
  </div>
197
199
  <Button onClick={() => setShowCreateDialog(true)} data-cy={sel('settings.apiKeys.createButton')}>
198
200
  <Plus className="h-4 w-4 mr-2" />
199
- Crear API Key
201
+ Create API Key
200
202
  </Button>
201
203
  </div>
202
204
 
@@ -239,11 +241,11 @@ function ApiKeysPage() {
239
241
  <Key className="h-12 w-12 text-muted-foreground mb-4" />
240
242
  <h3 className="text-lg font-semibold mb-2">No API Keys</h3>
241
243
  <p className="text-muted-foreground text-center mb-4">
242
- No tienes API keys creadas. Crea una para empezar a usar la API externa.
244
+ You don&apos;t have any API keys yet. Create one to start using the external API.
243
245
  </p>
244
246
  <Button onClick={() => setShowCreateDialog(true)} data-cy={sel('settings.apiKeys.createButton')}>
245
247
  <Plus className="h-4 w-4 mr-2" />
246
- Crear primera API Key
248
+ Create first API Key
247
249
  </Button>
248
250
  </CardContent>
249
251
  </Card>
@@ -257,7 +259,7 @@ function ApiKeysPage() {
257
259
  {apiKey.name}
258
260
  {apiKey.status !== 'active' && (
259
261
  <Badge variant="secondary" data-cy={sel('settings.apiKeys.row.status', { id: apiKey.id })}>
260
- {apiKey.status === 'inactive' ? 'Inactiva' : 'Expirada'}
262
+ {apiKey.status === 'inactive' ? 'Inactive' : 'Expired'}
261
263
  </Badge>
262
264
  )}
263
265
  </CardTitle>
@@ -276,7 +278,7 @@ function ApiKeysPage() {
276
278
  </div>
277
279
  <div className="flex items-center gap-2">
278
280
  <Badge variant={apiKey.status === 'active' ? "default" : "secondary"} data-cy={sel('settings.apiKeys.row.status', { id: apiKey.id })}>
279
- {apiKey.status === 'active' ? 'Activa' : apiKey.status === 'inactive' ? 'Inactiva' : 'Expirada'}
281
+ {apiKey.status === 'active' ? 'Active' : apiKey.status === 'inactive' ? 'Inactive' : 'Expired'}
280
282
  </Badge>
281
283
  <DropdownMenu>
282
284
  <DropdownMenuTrigger asChild>
@@ -290,7 +292,7 @@ function ApiKeysPage() {
290
292
  data-cy={sel('settings.apiKeys.row.menu.viewDetails', { id: apiKey.id })}
291
293
  >
292
294
  <Eye className="h-4 w-4 mr-2" />
293
- Ver detalles
295
+ View details
294
296
  </DropdownMenuItem>
295
297
  <DropdownMenuItem
296
298
  onClick={() => toggleApiKey.mutate({
@@ -303,12 +305,12 @@ function ApiKeysPage() {
303
305
  {apiKey.status === 'active' ? (
304
306
  <>
305
307
  <EyeOff className="h-4 w-4 mr-2" />
306
- Desactivar
308
+ Deactivate
307
309
  </>
308
310
  ) : (
309
311
  <>
310
312
  <Eye className="h-4 w-4 mr-2" />
311
- Activar
313
+ Activate
312
314
  </>
313
315
  )}
314
316
  </DropdownMenuItem>
@@ -319,7 +321,7 @@ function ApiKeysPage() {
319
321
  data-cy={sel('settings.apiKeys.row.menu.revoke', { id: apiKey.id })}
320
322
  >
321
323
  <Trash2 className="h-4 w-4 mr-2" />
322
- Revocar
324
+ Revoke
323
325
  </DropdownMenuItem>
324
326
  </DropdownMenuContent>
325
327
  </DropdownMenu>
@@ -330,7 +332,7 @@ function ApiKeysPage() {
330
332
  <div className="space-y-4">
331
333
  {/* Scopes */}
332
334
  <div data-cy={sel('settings.apiKeys.row.scopes', { id: apiKey.id })}>
333
- <strong className="text-sm">Permisos:</strong>
335
+ <strong className="text-sm">Permissions:</strong>
334
336
  <div className="flex flex-wrap gap-1 mt-1">
335
337
  {apiKey.scopes.map((scope) => (
336
338
  <Badge
@@ -352,11 +354,11 @@ function ApiKeysPage() {
352
354
  <div className="font-semibold">{apiKey.usage_stats.total_requests.toLocaleString()}</div>
353
355
  </div>
354
356
  <div data-cy={sel('settings.apiKeys.row.stats.last24h', { id: apiKey.id })}>
355
- <div className="text-muted-foreground">Últimas 24h</div>
357
+ <div className="text-muted-foreground">Last 24h</div>
356
358
  <div className="font-semibold">{apiKey.usage_stats.last_24h.toLocaleString()}</div>
357
359
  </div>
358
360
  <div data-cy={sel('settings.apiKeys.row.stats.avgTime', { id: apiKey.id })}>
359
- <div className="text-muted-foreground">Tiempo promedio</div>
361
+ <div className="text-muted-foreground">Avg time</div>
360
362
  <div className="font-semibold">
361
363
  {apiKey.usage_stats.avg_response_time
362
364
  ? `${Math.round(apiKey.usage_stats.avg_response_time)}ms`
@@ -368,12 +370,12 @@ function ApiKeysPage() {
368
370
 
369
371
  {/* Metadata */}
370
372
  <div className="text-sm text-muted-foreground space-y-1" data-cy={sel('settings.apiKeys.row.metadata.container', { id: apiKey.id })}>
371
- <div data-cy={sel('settings.apiKeys.row.metadata.createdAt', { id: apiKey.id })}>Creada: {formatDate(apiKey.createdAt)}</div>
373
+ <div data-cy={sel('settings.apiKeys.row.metadata.createdAt', { id: apiKey.id })}>Created: {formatDate(apiKey.createdAt)}</div>
372
374
  {apiKey.lastUsedAt && (
373
- <div data-cy={sel('settings.apiKeys.row.metadata.lastUsed', { id: apiKey.id })}>Ultimo uso: {formatDate(apiKey.lastUsedAt)}</div>
375
+ <div data-cy={sel('settings.apiKeys.row.metadata.lastUsed', { id: apiKey.id })}>Last used: {formatDate(apiKey.lastUsedAt)}</div>
374
376
  )}
375
377
  {apiKey.expiresAt && (
376
- <div data-cy={sel('settings.apiKeys.row.metadata.expiresAt', { id: apiKey.id })}>Expira: {formatDate(apiKey.expiresAt)}</div>
378
+ <div data-cy={sel('settings.apiKeys.row.metadata.expiresAt', { id: apiKey.id })}>Expires: {formatDate(apiKey.expiresAt)}</div>
377
379
  )}
378
380
  </div>
379
381
  </div>
@@ -427,9 +429,9 @@ function ApiKeyDetailsDialog({
427
429
  <Dialog open={open} onOpenChange={onClose}>
428
430
  <DialogContent className="max-w-2xl" data-cy={sel('settings.apiKeys.detailsDialog.container')}>
429
431
  <DialogHeader>
430
- <DialogTitle data-cy={sel('settings.apiKeys.detailsDialog.title')}>Detalles de API Key</DialogTitle>
432
+ <DialogTitle data-cy={sel('settings.apiKeys.detailsDialog.title')}>API Key Details</DialogTitle>
431
433
  <DialogDescription>
432
- Estadísticas de uso y configuración
434
+ Usage statistics and configuration
433
435
  </DialogDescription>
434
436
  </DialogHeader>
435
437
 
@@ -443,17 +445,17 @@ function ApiKeyDetailsDialog({
443
445
  <div className="space-y-6" data-cy={sel('settings.apiKeys.detailsDialog.content')}>
444
446
  {/* Basic Info */}
445
447
  <div data-cy={sel('settings.apiKeys.detailsDialog.basicInfo.container')}>
446
- <h4 className="font-semibold mb-2">Información básica</h4>
448
+ <h4 className="font-semibold mb-2">Basic information</h4>
447
449
  <div className="grid grid-cols-2 gap-4 text-sm">
448
450
  <div>
449
- <span className="text-muted-foreground">Nombre:</span>
451
+ <span className="text-muted-foreground">Name:</span>
450
452
  <div className="font-medium" data-cy={sel('settings.apiKeys.detailsDialog.basicInfo.name')}>{keyDetails.name}</div>
451
453
  </div>
452
454
  <div>
453
- <span className="text-muted-foreground">Estado:</span>
455
+ <span className="text-muted-foreground">Status:</span>
454
456
  <div>
455
457
  <Badge variant={keyDetails.status === 'active' ? "default" : "secondary"} data-cy={sel('settings.apiKeys.detailsDialog.basicInfo.status')}>
456
- {keyDetails.status === 'active' ? 'Activa' : keyDetails.status === 'inactive' ? 'Inactiva' : 'Expirada'}
458
+ {keyDetails.status === 'active' ? 'Active' : keyDetails.status === 'inactive' ? 'Inactive' : 'Expired'}
457
459
  </Badge>
458
460
  </div>
459
461
  </div>
@@ -464,27 +466,27 @@ function ApiKeyDetailsDialog({
464
466
  <div data-cy={sel('settings.apiKeys.detailsDialog.stats.container')}>
465
467
  <h4 className="font-semibold mb-2 flex items-center gap-2">
466
468
  <Activity className="h-4 w-4" />
467
- Estadísticas de uso
469
+ Usage statistics
468
470
  </h4>
469
471
  <div className="grid grid-cols-2 gap-4 text-sm">
470
472
  <div>
471
- <span className="text-muted-foreground">Total de requests:</span>
473
+ <span className="text-muted-foreground">Total requests:</span>
472
474
  <div className="font-medium" data-cy={sel('settings.apiKeys.detailsDialog.stats.totalRequests')}>{keyDetails.usage_stats.total_requests.toLocaleString()}</div>
473
475
  </div>
474
476
  <div>
475
- <span className="text-muted-foreground">Ultimas 24 horas:</span>
477
+ <span className="text-muted-foreground">Last 24 hours:</span>
476
478
  <div className="font-medium" data-cy={sel('settings.apiKeys.detailsDialog.stats.last24h')}>{keyDetails.usage_stats.last_24h.toLocaleString()}</div>
477
479
  </div>
478
480
  <div>
479
- <span className="text-muted-foreground">Ultimos 7 dias:</span>
481
+ <span className="text-muted-foreground">Last 7 days:</span>
480
482
  <div className="font-medium" data-cy={sel('settings.apiKeys.detailsDialog.stats.last7d')}>{keyDetails.usage_stats.last_7d.toLocaleString()}</div>
481
483
  </div>
482
484
  <div>
483
- <span className="text-muted-foreground">Ultimos 30 dias:</span>
485
+ <span className="text-muted-foreground">Last 30 days:</span>
484
486
  <div className="font-medium" data-cy={sel('settings.apiKeys.detailsDialog.stats.last30d')}>{keyDetails.usage_stats.last_30d.toLocaleString()}</div>
485
487
  </div>
486
488
  <div>
487
- <span className="text-muted-foreground">Tiempo promedio:</span>
489
+ <span className="text-muted-foreground">Avg response time:</span>
488
490
  <div className="font-medium" data-cy={sel('settings.apiKeys.detailsDialog.stats.avgTime')}>
489
491
  {keyDetails.usage_stats.avg_response_time
490
492
  ? `${Math.round(keyDetails.usage_stats.avg_response_time)}ms`
@@ -493,7 +495,7 @@ function ApiKeyDetailsDialog({
493
495
  </div>
494
496
  </div>
495
497
  <div>
496
- <span className="text-muted-foreground">Tasa de exito:</span>
498
+ <span className="text-muted-foreground">Success rate:</span>
497
499
  <div className="font-medium" data-cy={sel('settings.apiKeys.detailsDialog.stats.successRate')}>
498
500
  {keyDetails.usage_stats.success_rate
499
501
  ? `${Math.round(keyDetails.usage_stats.success_rate)}%`