@nextsparkjs/core 0.1.0-beta.184 → 0.1.0-beta.186

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 (420) hide show
  1. package/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
  2. package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
  3. package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
  4. package/dist/components/app/layouts/PublicFooter.js +2 -2
  5. package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
  6. package/dist/components/app/layouts/PublicNavbar.js +2 -2
  7. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  8. package/dist/components/app/misc/ThemeToggle.js +4 -1
  9. package/dist/components/auth/DevKeyring.d.ts.map +1 -1
  10. package/dist/components/auth/DevKeyring.js +10 -2
  11. package/dist/components/auth/SessionCookieRefresher.d.ts +21 -0
  12. package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -0
  13. package/dist/components/auth/SessionCookieRefresher.js +11 -0
  14. package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
  15. package/dist/components/auth/forms/LoginForm.js +300 -6
  16. package/dist/components/auth/index.d.ts +1 -0
  17. package/dist/components/auth/index.d.ts.map +1 -1
  18. package/dist/components/auth/index.js +2 -0
  19. package/dist/components/billing/InvoicesTable.d.ts.map +1 -1
  20. package/dist/components/billing/InvoicesTable.js +4 -3
  21. package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
  22. package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
  23. package/dist/components/dashboard/layouts/AuthGuard.js +2 -2
  24. package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
  25. package/dist/components/dashboard/layouts/DashboardAuthLayout.js +2 -2
  26. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +17 -0
  27. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts.map +1 -0
  28. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.js +52 -0
  29. package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
  30. package/dist/components/dashboard/layouts/Sidebar.js +2 -3
  31. package/dist/components/dashboard/layouts/TopNavbar.js +2 -2
  32. package/dist/components/dashboard/layouts/index.d.ts +1 -0
  33. package/dist/components/dashboard/layouts/index.d.ts.map +1 -1
  34. package/dist/components/dashboard/layouts/index.js +2 -0
  35. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  36. package/dist/components/entities/EntityFieldRenderer.js +3 -0
  37. package/dist/components/entities/EntityTable.d.ts.map +1 -1
  38. package/dist/components/entities/EntityTable.js +2 -1
  39. package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
  40. package/dist/components/entities/wrappers/EntityFormWrapper.js +7 -2
  41. package/dist/components/ui/combobox.d.ts +8 -1
  42. package/dist/components/ui/combobox.d.ts.map +1 -1
  43. package/dist/components/ui/combobox.js +37 -44
  44. package/dist/components/ui/country-select.d.ts +3 -1
  45. package/dist/components/ui/country-select.d.ts.map +1 -1
  46. package/dist/components/ui/country-select.js +3 -1
  47. package/dist/components/ui/currency-select.d.ts +3 -1
  48. package/dist/components/ui/currency-select.d.ts.map +1 -1
  49. package/dist/components/ui/currency-select.js +3 -1
  50. package/dist/components/ui/sheet.d.ts +5 -0
  51. package/dist/components/ui/sheet.d.ts.map +1 -1
  52. package/dist/components/ui/sheet.js +46 -26
  53. package/dist/components/ui/timezone-select.d.ts +3 -1
  54. package/dist/components/ui/timezone-select.d.ts.map +1 -1
  55. package/dist/components/ui/timezone-select.js +3 -1
  56. package/dist/contexts/TeamContext.d.ts +6 -0
  57. package/dist/contexts/TeamContext.d.ts.map +1 -1
  58. package/dist/contexts/TeamContext.js +8 -1
  59. package/dist/emails/otp-verification.d.ts.map +1 -1
  60. package/dist/emails/reset-password.d.ts.map +1 -1
  61. package/dist/emails/team-invitation.d.ts.map +1 -1
  62. package/dist/emails/team-invitation.js +3 -2
  63. package/dist/emails/verify-email.d.ts.map +1 -1
  64. package/dist/hooks/index.d.ts +1 -0
  65. package/dist/hooks/index.d.ts.map +1 -1
  66. package/dist/hooks/index.js +1 -0
  67. package/dist/hooks/useAuth.d.ts +103 -25
  68. package/dist/hooks/useAuth.d.ts.map +1 -1
  69. package/dist/hooks/useAuth.js +24 -0
  70. package/dist/hooks/useEnsureUserMetadata.d.ts +22 -0
  71. package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
  72. package/dist/hooks/useEnsureUserMetadata.js +18 -5
  73. package/dist/hooks/useSessionCookieRefresh.d.ts +35 -0
  74. package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -0
  75. package/dist/hooks/useSessionCookieRefresh.js +49 -0
  76. package/dist/hooks/useValidationSchemas.d.ts +7 -7
  77. package/dist/lib/api/api-error.d.ts +3 -1
  78. package/dist/lib/api/api-error.d.ts.map +1 -1
  79. package/dist/lib/api/api-error.js +4 -2
  80. package/dist/lib/api/auth/dual-auth.d.ts +45 -3
  81. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  82. package/dist/lib/api/auth/dual-auth.js +42 -7
  83. package/dist/lib/api/auth/permissions.d.ts +7 -2
  84. package/dist/lib/api/auth/permissions.d.ts.map +1 -1
  85. package/dist/lib/api/auth/permissions.js +2 -2
  86. package/dist/lib/api/auth/scope-policy.d.ts +71 -0
  87. package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
  88. package/dist/lib/api/auth/scope-policy.js +53 -0
  89. package/dist/lib/api/auth.d.ts +35 -3
  90. package/dist/lib/api/auth.d.ts.map +1 -1
  91. package/dist/lib/api/auth.js +54 -23
  92. package/dist/lib/api/entity/audit-log.d.ts +35 -0
  93. package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
  94. package/dist/lib/api/entity/audit-log.js +35 -0
  95. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  96. package/dist/lib/api/entity/generic-handler.js +333 -155
  97. package/dist/lib/api/helpers.d.ts +29 -4
  98. package/dist/lib/api/helpers.d.ts.map +1 -1
  99. package/dist/lib/api/helpers.js +30 -6
  100. package/dist/lib/api/keys.d.ts +16 -0
  101. package/dist/lib/api/keys.d.ts.map +1 -1
  102. package/dist/lib/api/keys.js +19 -1
  103. package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
  104. package/dist/lib/api/routes/patterns-usages.js +3 -11
  105. package/dist/lib/auth/auth-methods.d.ts +52 -0
  106. package/dist/lib/auth/auth-methods.d.ts.map +1 -0
  107. package/dist/lib/auth/auth-methods.js +63 -0
  108. package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
  109. package/dist/lib/auth/registration-helpers.d.ts +9 -0
  110. package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
  111. package/dist/lib/auth/registration-helpers.js +8 -2
  112. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
  113. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
  114. package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
  115. package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
  116. package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
  117. package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
  118. package/dist/lib/auth/security-notifications/index.d.ts +20 -0
  119. package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
  120. package/dist/lib/auth/security-notifications/index.js +22 -0
  121. package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
  122. package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
  123. package/dist/lib/auth/security-notifications/registry.js +26 -0
  124. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
  125. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
  126. package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
  127. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
  128. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
  129. package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
  130. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
  131. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
  132. package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
  133. package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
  134. package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
  135. package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
  136. package/dist/lib/auth/security-notifications/types.d.ts +74 -0
  137. package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
  138. package/dist/lib/auth/security-notifications/types.js +0 -0
  139. package/dist/lib/auth/session-config.d.ts +37 -0
  140. package/dist/lib/auth/session-config.d.ts.map +1 -0
  141. package/dist/lib/auth/session-config.js +59 -0
  142. package/dist/lib/auth/session-refresh.d.ts +46 -0
  143. package/dist/lib/auth/session-refresh.d.ts.map +1 -0
  144. package/dist/lib/auth/session-refresh.js +11 -0
  145. package/dist/lib/auth-client.d.ts +1489 -3445
  146. package/dist/lib/auth-client.d.ts.map +1 -1
  147. package/dist/lib/auth-client.js +4 -0
  148. package/dist/lib/auth.d.ts +1 -3057
  149. package/dist/lib/auth.d.ts.map +1 -1
  150. package/dist/lib/auth.js +21 -8
  151. package/dist/lib/billing/gateways/factory.d.ts +8 -3
  152. package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
  153. package/dist/lib/billing/gateways/factory.js +37 -23
  154. package/dist/lib/billing/schema.d.ts +1 -1
  155. package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
  156. package/dist/lib/billing/stripe-webhook.js +2 -1
  157. package/dist/lib/config/app.config.d.ts.map +1 -1
  158. package/dist/lib/config/app.config.js +71 -0
  159. package/dist/lib/config/config-sync.d.ts +1 -4
  160. package/dist/lib/config/config-sync.d.ts.map +1 -1
  161. package/dist/lib/config/config-sync.js +6 -2
  162. package/dist/lib/config/types.d.ts +130 -0
  163. package/dist/lib/config/types.d.ts.map +1 -1
  164. package/dist/lib/entities/entity-hooks.d.ts +9 -1
  165. package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
  166. package/dist/lib/entities/entity-hooks.js +77 -4
  167. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  168. package/dist/lib/entities/schema-generator.js +20 -4
  169. package/dist/lib/entities/serialization.d.ts +2 -2
  170. package/dist/lib/entities/serialization.d.ts.map +1 -1
  171. package/dist/lib/entities/serialization.js +3 -2
  172. package/dist/lib/entities/types.d.ts +8 -0
  173. package/dist/lib/entities/types.d.ts.map +1 -1
  174. package/dist/lib/locale.d.ts.map +1 -1
  175. package/dist/lib/locale.js +4 -1
  176. package/dist/lib/mcp/audit.d.ts +21 -0
  177. package/dist/lib/mcp/audit.d.ts.map +1 -0
  178. package/dist/lib/mcp/audit.js +48 -0
  179. package/dist/lib/mcp/engine.d.ts +28 -0
  180. package/dist/lib/mcp/engine.d.ts.map +1 -0
  181. package/dist/lib/mcp/engine.js +158 -0
  182. package/dist/lib/mcp/errors.d.ts +16 -0
  183. package/dist/lib/mcp/errors.d.ts.map +1 -0
  184. package/dist/lib/mcp/errors.js +66 -0
  185. package/dist/lib/mcp/executor.d.ts +15 -0
  186. package/dist/lib/mcp/executor.d.ts.map +1 -0
  187. package/dist/lib/mcp/executor.js +63 -0
  188. package/dist/lib/mcp/index.d.ts +9 -0
  189. package/dist/lib/mcp/index.d.ts.map +1 -0
  190. package/dist/lib/mcp/index.js +32 -0
  191. package/dist/lib/mcp/schema-builder.d.ts +45 -0
  192. package/dist/lib/mcp/schema-builder.d.ts.map +1 -0
  193. package/dist/lib/mcp/schema-builder.js +212 -0
  194. package/dist/lib/mcp/tool-generator.d.ts +31 -0
  195. package/dist/lib/mcp/tool-generator.d.ts.map +1 -0
  196. package/dist/lib/mcp/tool-generator.js +271 -0
  197. package/dist/lib/mcp/transport.d.ts +31 -0
  198. package/dist/lib/mcp/transport.d.ts.map +1 -0
  199. package/dist/lib/mcp/transport.js +58 -0
  200. package/dist/lib/mcp/types.d.ts +184 -0
  201. package/dist/lib/mcp/types.d.ts.map +1 -0
  202. package/dist/lib/mcp/types.js +11 -0
  203. package/dist/lib/plugins/hook-system.d.ts +13 -0
  204. package/dist/lib/plugins/hook-system.d.ts.map +1 -1
  205. package/dist/lib/plugins/hook-system.js +32 -2
  206. package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
  207. package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
  208. package/dist/lib/scheduled-actions/handlers/index.js +3 -1
  209. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
  210. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
  211. package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
  212. package/dist/lib/selectors/core-selectors.d.ts +12 -0
  213. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  214. package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
  215. package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
  216. package/dist/lib/selectors/domains/auth.selectors.js +14 -1
  217. package/dist/lib/selectors/selectors.d.ts +24 -0
  218. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  219. package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
  220. package/dist/lib/services/generic-entity.service.js +16 -32
  221. package/dist/lib/services/membership.service.d.ts +10 -2
  222. package/dist/lib/services/membership.service.d.ts.map +1 -1
  223. package/dist/lib/services/membership.service.js +37 -16
  224. package/dist/lib/services/subscription.service.d.ts.map +1 -1
  225. package/dist/lib/services/subscription.service.js +2 -1
  226. package/dist/lib/services/team-member.service.d.ts +15 -3
  227. package/dist/lib/services/team-member.service.d.ts.map +1 -1
  228. package/dist/lib/services/team-member.service.js +27 -6
  229. package/dist/lib/services/team.service.d.ts.map +1 -1
  230. package/dist/lib/teams/permissions.d.ts +3 -1
  231. package/dist/lib/teams/permissions.d.ts.map +1 -1
  232. package/dist/lib/teams/permissions.js +6 -0
  233. package/dist/lib/teams/schema.d.ts.map +1 -1
  234. package/dist/lib/teams/schema.js +3 -0
  235. package/dist/lib/theme/forced-theme.d.ts +34 -0
  236. package/dist/lib/theme/forced-theme.d.ts.map +1 -0
  237. package/dist/lib/theme/forced-theme.js +27 -0
  238. package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
  239. package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
  240. package/dist/lib/theme/get-default-theme-mode.js +5 -3
  241. package/dist/messages/de/auth.json +25 -2
  242. package/dist/messages/de/index.d.ts +81 -0
  243. package/dist/messages/de/index.d.ts.map +1 -1
  244. package/dist/messages/de/index.js +2 -0
  245. package/dist/messages/de/index.ts +2 -0
  246. package/dist/messages/en/auth.json +25 -2
  247. package/dist/messages/en/index.d.ts +81 -0
  248. package/dist/messages/en/index.d.ts.map +1 -1
  249. package/dist/messages/en/index.js +2 -0
  250. package/dist/messages/en/index.ts +2 -0
  251. package/dist/messages/es/auth.json +25 -2
  252. package/dist/messages/es/index.d.ts +81 -0
  253. package/dist/messages/es/index.d.ts.map +1 -1
  254. package/dist/messages/es/index.js +2 -0
  255. package/dist/messages/es/index.ts +2 -0
  256. package/dist/messages/fr/auth.json +25 -2
  257. package/dist/messages/fr/index.d.ts +81 -0
  258. package/dist/messages/fr/index.d.ts.map +1 -1
  259. package/dist/messages/fr/index.js +2 -0
  260. package/dist/messages/fr/index.ts +2 -0
  261. package/dist/messages/it/auth.json +25 -2
  262. package/dist/messages/it/index.d.ts +81 -0
  263. package/dist/messages/it/index.d.ts.map +1 -1
  264. package/dist/messages/it/index.js +2 -0
  265. package/dist/messages/it/index.ts +2 -0
  266. package/dist/messages/pt/auth.json +25 -2
  267. package/dist/messages/pt/index.d.ts +81 -0
  268. package/dist/messages/pt/index.d.ts.map +1 -1
  269. package/dist/messages/pt/index.js +2 -0
  270. package/dist/messages/pt/index.ts +2 -0
  271. package/dist/migrations/005_api_audit_log.sql +2 -1
  272. package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  273. package/dist/migrations/026_login_events.sql +91 -0
  274. package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  275. package/dist/providers/DashboardProviders.d.ts +12 -3
  276. package/dist/providers/DashboardProviders.d.ts.map +1 -1
  277. package/dist/providers/DashboardProviders.js +6 -2
  278. package/dist/providers/theme-provider.d.ts +19 -2
  279. package/dist/providers/theme-provider.d.ts.map +1 -1
  280. package/dist/providers/theme-provider.js +6 -2
  281. package/dist/templates/app/(auth)/signup/page.tsx +8 -0
  282. package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
  283. package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
  284. package/dist/templates/app/api/health/route.ts +4 -2
  285. package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
  286. package/dist/templates/app/api/mcp/route.ts +176 -0
  287. package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  288. package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  289. package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  290. package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  291. package/dist/templates/app/api/v1/api-keys/route.ts +35 -45
  292. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  293. package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
  294. package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  295. package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
  296. package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
  297. package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
  298. package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
  299. package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  300. package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  301. package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
  302. package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
  303. package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  304. package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  305. package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
  306. package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
  307. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  308. package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  309. package/dist/templates/app/api/v1/media/route.ts +5 -9
  310. package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
  311. package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
  312. package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  313. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  314. package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
  315. package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  316. package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  317. package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  318. package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
  319. package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  320. package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  321. package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  322. package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  323. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  324. package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  325. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  326. package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  327. package/dist/templates/app/api/v1/teams/route.ts +33 -27
  328. package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
  329. package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  330. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  331. package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
  332. package/dist/templates/app/api/v1/users/route.ts +21 -25
  333. package/dist/templates/app/dashboard/layout.tsx +2 -6
  334. package/dist/templates/app/layout.ppr.tsx +9 -14
  335. package/dist/templates/app/layout.tsx +39 -19
  336. package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
  337. package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
  338. package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
  339. package/dist/templates/proxy.ts +46 -10
  340. package/dist/templates/tsconfig.json +4 -0
  341. package/dist/types/theme.d.ts +9 -0
  342. package/dist/types/theme.d.ts.map +1 -1
  343. package/migrations/005_api_audit_log.sql +2 -1
  344. package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  345. package/migrations/026_login_events.sql +91 -0
  346. package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  347. package/package.json +20 -12
  348. package/scripts/build/registry/__tests__/mcp-registry.test.mjs +64 -0
  349. package/scripts/build/registry/__tests__/scope-registry.test.mjs +106 -0
  350. package/scripts/build/registry/discovery/mcp-overrides.mjs +72 -0
  351. package/scripts/build/registry/discovery/mcp-overrides.test.mjs +86 -0
  352. package/scripts/build/registry/generators/mcp-registry.mjs +61 -0
  353. package/scripts/build/registry/generators/scope-registry.mjs +140 -88
  354. package/scripts/build/registry.mjs +8 -4
  355. package/templates/app/(auth)/signup/page.tsx +8 -0
  356. package/templates/app/(public)/[...slug]/page.tsx +9 -14
  357. package/templates/app/api/auth/[...all]/route.ts +23 -1
  358. package/templates/app/api/health/route.ts +4 -2
  359. package/templates/app/api/internal/user-metadata/route.ts +5 -3
  360. package/templates/app/api/mcp/route.ts +176 -0
  361. package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  362. package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  363. package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  364. package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  365. package/templates/app/api/v1/api-keys/route.ts +35 -45
  366. package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  367. package/templates/app/api/v1/billing/cancel/route.ts +7 -6
  368. package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  369. package/templates/app/api/v1/billing/check-action/route.ts +6 -4
  370. package/templates/app/api/v1/billing/checkout/route.ts +7 -5
  371. package/templates/app/api/v1/billing/plans/route.ts +8 -12
  372. package/templates/app/api/v1/billing/portal/route.ts +7 -5
  373. package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  374. package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  375. package/templates/app/api/v1/devtools/features/route.ts +5 -4
  376. package/templates/app/api/v1/devtools/flows/route.ts +5 -4
  377. package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  378. package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  379. package/templates/app/api/v1/devtools/testing/route.ts +5 -4
  380. package/templates/app/api/v1/media/[id]/route.ts +13 -25
  381. package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  382. package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  383. package/templates/app/api/v1/media/route.ts +5 -9
  384. package/templates/app/api/v1/media/upload/route.ts +9 -21
  385. package/templates/app/api/v1/media-tags/route.ts +9 -13
  386. package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  387. package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  388. package/templates/app/api/v1/post-categories/route.ts +5 -4
  389. package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  390. package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  391. package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  392. package/templates/app/api/v1/team-invitations/route.ts +9 -11
  393. package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  394. package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  395. package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  396. package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  397. package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  398. package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  399. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  400. package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  401. package/templates/app/api/v1/teams/route.ts +33 -27
  402. package/templates/app/api/v1/teams/switch/route.ts +11 -13
  403. package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  404. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  405. package/templates/app/api/v1/users/[id]/route.ts +35 -68
  406. package/templates/app/api/v1/users/route.ts +21 -25
  407. package/templates/app/dashboard/layout.tsx +2 -6
  408. package/templates/app/layout.ppr.tsx +9 -14
  409. package/templates/app/layout.tsx +39 -19
  410. package/templates/contents/themes/starter/config/app.config.ts +19 -0
  411. package/templates/contents/themes/starter/config/theme.config.ts +6 -0
  412. package/templates/contents/themes/starter/styles/globals.css +6 -2
  413. package/templates/proxy.ts +46 -10
  414. package/templates/tsconfig.json +4 -0
  415. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
  416. package/tests/jest/__mocks__/@nextsparkjs/registries/scope-registry.ts +56 -6
  417. package/tests/jest/__mocks__/next-server.js +24 -0
  418. package/dist/styles/classes.json +0 -1093
  419. package/dist/styles/ui.css +0 -2
  420. package/scripts/build/build-ui-css.mjs +0 -351
@@ -11,7 +11,7 @@ import {
11
11
  handleEntityMetadataInResponse,
12
12
  processEntityMetadata
13
13
  } from '@nextsparkjs/core/lib/api/helpers';
14
- import { authenticateRequest, hasRequiredScope } from '@nextsparkjs/core/lib/api/auth/dual-auth';
14
+ import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth';
15
15
  import { z } from 'zod';
16
16
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit';
17
17
 
@@ -24,8 +24,8 @@ const updateUserSchema = z.object({
24
24
  });
25
25
 
26
26
  // Handle CORS preflight
27
- export async function OPTIONS() {
28
- return handleCorsPreflightRequest();
27
+ export async function OPTIONS(request: NextRequest) {
28
+ return handleCorsPreflightRequest(request);
29
29
  }
30
30
 
31
31
  // GET /api/v1/users/:id - Get specific user
@@ -34,35 +34,24 @@ export const GET = withRateLimitTier(withApiLogging(async (
34
34
  { params }: { params: Promise<{ id: string }> }
35
35
  ): Promise<NextResponse> => {
36
36
  try {
37
- // Authenticate using dual auth
38
- const authResult = await authenticateRequest(req);
39
-
37
+ // Authenticate using dual auth; the API-key scope is declared at the entry
38
+ // point, which fails closed for keys that lack it (#93).
39
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:read' });
40
+
40
41
  if (!authResult.success) {
41
- return NextResponse.json(
42
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
43
- { status: 401 }
44
- );
42
+ return createAuthFailureResponse(authResult);
45
43
  }
46
44
 
47
45
  if (authResult.rateLimitResponse) {
48
46
  return authResult.rateLimitResponse as NextResponse;
49
47
  }
50
48
 
51
- // Check required permissions - session users need admin role, API key users need specific scope
52
- const hasPermission = authResult.type === 'session' ||
53
- (authResult.type === 'api-key' && hasRequiredScope(authResult, 'users:read'));
54
-
55
- if (!hasPermission) {
56
- const response = createApiError('Insufficient permissions. Admin access required for user management.', 403);
57
- return addCorsHeaders(response);
58
- }
59
-
60
49
  const { id } = await params;
61
50
 
62
51
  // Validate that id is not empty
63
52
  if (!id || id.trim() === '') {
64
53
  const response = createApiError('User ID or email is required', 400, null, 'MISSING_IDENTIFIER');
65
- return addCorsHeaders(response);
54
+ return addCorsHeaders(response, req);
66
55
  }
67
56
 
68
57
  // Search by ID or email
@@ -74,7 +63,7 @@ export const GET = withRateLimitTier(withApiLogging(async (
74
63
 
75
64
  if (!user) {
76
65
  const response = createApiError('User not found', 404, null, 'USER_NOT_FOUND');
77
- return addCorsHeaders(response);
66
+ return addCorsHeaders(response, req);
78
67
  }
79
68
 
80
69
  // Handle metadata if requested (usando helper compartido)
@@ -82,11 +71,11 @@ export const GET = withRateLimitTier(withApiLogging(async (
82
71
  const userWithMeta = await includeEntityMetadataForSingle('user', user as { id: string }, metaParams, authResult.user!.id);
83
72
 
84
73
  const response = createApiResponse(userWithMeta);
85
- return addCorsHeaders(response);
74
+ return addCorsHeaders(response, req);
86
75
  } catch (error) {
87
76
  console.error('Error fetching user:', error);
88
77
  const response = createApiError('Internal server error', 500);
89
- return addCorsHeaders(response);
78
+ return addCorsHeaders(response, req);
90
79
  }
91
80
  }), 'read');
92
81
 
@@ -96,35 +85,24 @@ export const PATCH = withRateLimitTier(withApiLogging(async (
96
85
  { params }: { params: Promise<{ id: string }> }
97
86
  ): Promise<NextResponse> => {
98
87
  try {
99
- // Authenticate using dual auth
100
- const authResult = await authenticateRequest(req);
101
-
88
+ // Authenticate using dual auth; the API-key scope is declared at the entry
89
+ // point, which fails closed for keys that lack it (#93).
90
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:write' });
91
+
102
92
  if (!authResult.success) {
103
- return NextResponse.json(
104
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
105
- { status: 401 }
106
- );
93
+ return createAuthFailureResponse(authResult);
107
94
  }
108
95
 
109
96
  if (authResult.rateLimitResponse) {
110
97
  return authResult.rateLimitResponse as NextResponse;
111
98
  }
112
99
 
113
- // Check required permissions - session users need admin role, API key users need specific scope
114
- const hasPermission = authResult.type === 'session' ||
115
- (authResult.type === 'api-key' && hasRequiredScope(authResult, 'users:write'));
116
-
117
- if (!hasPermission) {
118
- const response = createApiError('Insufficient permissions. Admin access required for user management.', 403);
119
- return addCorsHeaders(response);
120
- }
121
-
122
100
  const { id } = await params;
123
101
 
124
102
  // Validate that id is not empty
125
103
  if (!id || id.trim() === '') {
126
104
  const response = createApiError('User ID or email is required', 400, null, 'MISSING_IDENTIFIER');
127
- return addCorsHeaders(response);
105
+ return addCorsHeaders(response, req);
128
106
  }
129
107
 
130
108
  const body = await req.json();
@@ -162,7 +140,7 @@ export const PATCH = withRateLimitTier(withApiLogging(async (
162
140
 
163
141
  if (!hasEntityFieldsToUpdate && !hasMetadataToUpdate) {
164
142
  const response = createApiError('No fields to update', 400, null, 'NO_FIELDS');
165
- return addCorsHeaders(response);
143
+ return addCorsHeaders(response, req);
166
144
  }
167
145
 
168
146
  let updatedUser;
@@ -183,7 +161,7 @@ export const PATCH = withRateLimitTier(withApiLogging(async (
183
161
 
184
162
  if (result.rows.length === 0) {
185
163
  const response = createApiError('User not found', 404, null, 'USER_NOT_FOUND');
186
- return addCorsHeaders(response);
164
+ return addCorsHeaders(response, req);
187
165
  }
188
166
 
189
167
  updatedUser = result.rows[0];
@@ -197,7 +175,7 @@ export const PATCH = withRateLimitTier(withApiLogging(async (
197
175
 
198
176
  if (!user) {
199
177
  const response = createApiError('User not found', 404, null, 'USER_NOT_FOUND');
200
- return addCorsHeaders(response);
178
+ return addCorsHeaders(response, req);
201
179
  }
202
180
 
203
181
  updatedUser = user;
@@ -214,16 +192,16 @@ export const PATCH = withRateLimitTier(withApiLogging(async (
214
192
  const responseData = await handleEntityMetadataInResponse('user', updatedUser as { id: string }, metadataWasProvided, authResult.user!.id);
215
193
 
216
194
  const response = createApiResponse(responseData);
217
- return addCorsHeaders(response);
195
+ return addCorsHeaders(response, req);
218
196
  } catch (error) {
219
197
  if (error instanceof z.ZodError) {
220
198
  const response = createApiError('Validation error', 400, error.issues, 'VALIDATION_ERROR');
221
- return addCorsHeaders(response);
199
+ return addCorsHeaders(response, req);
222
200
  }
223
201
 
224
202
  console.error('Error updating user:', error);
225
203
  const response = createApiError('Internal server error', 500);
226
- return addCorsHeaders(response);
204
+ return addCorsHeaders(response, req);
227
205
  }
228
206
  }), 'write');
229
207
 
@@ -233,35 +211,24 @@ export const DELETE = withRateLimitTier(withApiLogging(async (
233
211
  { params }: { params: Promise<{ id: string }> }
234
212
  ): Promise<NextResponse> => {
235
213
  try {
236
- // Authenticate using dual auth
237
- const authResult = await authenticateRequest(req);
238
-
214
+ // Authenticate using dual auth; the API-key scope is declared at the entry
215
+ // point, which fails closed for keys that lack it (#93).
216
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:delete' });
217
+
239
218
  if (!authResult.success) {
240
- return NextResponse.json(
241
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
242
- { status: 401 }
243
- );
219
+ return createAuthFailureResponse(authResult);
244
220
  }
245
221
 
246
222
  if (authResult.rateLimitResponse) {
247
223
  return authResult.rateLimitResponse as NextResponse;
248
224
  }
249
225
 
250
- // Check required permissions - session users need admin role, API key users need specific scope
251
- const hasPermission = authResult.type === 'session' ||
252
- (authResult.type === 'api-key' && hasRequiredScope(authResult, 'users:delete'));
253
-
254
- if (!hasPermission) {
255
- const response = createApiError('Insufficient permissions. Admin access required for user management.', 403);
256
- return addCorsHeaders(response);
257
- }
258
-
259
226
  const { id } = await params;
260
227
 
261
228
  // Validate that id is not empty
262
229
  if (!id || id.trim() === '') {
263
230
  const response = createApiError('User ID or email is required', 400, null, 'MISSING_IDENTIFIER');
264
- return addCorsHeaders(response);
231
+ return addCorsHeaders(response, req);
265
232
  }
266
233
 
267
234
  // First, get the user to check if it exists and prevent self-deletion
@@ -273,13 +240,13 @@ export const DELETE = withRateLimitTier(withApiLogging(async (
273
240
 
274
241
  if (!targetUser) {
275
242
  const response = createApiError('User not found', 404, null, 'USER_NOT_FOUND');
276
- return addCorsHeaders(response);
243
+ return addCorsHeaders(response, req);
277
244
  }
278
245
 
279
246
  // Prevent self-deletion
280
247
  if ((targetUser as Record<string, unknown>).id === authResult.user!.id) {
281
248
  const response = createApiError('Cannot delete your own account via API', 403, null, 'SELF_DELETE_FORBIDDEN');
282
- return addCorsHeaders(response);
249
+ return addCorsHeaders(response, req);
283
250
  }
284
251
 
285
252
  const result = await mutateWithRLS(
@@ -290,14 +257,14 @@ export const DELETE = withRateLimitTier(withApiLogging(async (
290
257
 
291
258
  if (result.rows.length === 0) {
292
259
  const response = createApiError('User not found', 404, null, 'USER_NOT_FOUND');
293
- return addCorsHeaders(response);
260
+ return addCorsHeaders(response, req);
294
261
  }
295
262
 
296
263
  const response = createApiResponse({ deleted: true, id });
297
- return addCorsHeaders(response);
264
+ return addCorsHeaders(response, req);
298
265
  } catch (error) {
299
266
  console.error('Error deleting user:', error);
300
267
  const response = createApiError('Internal server error', 500);
301
- return addCorsHeaders(response);
268
+ return addCorsHeaders(response, req);
302
269
  }
303
270
  }), 'write');
@@ -14,7 +14,7 @@ import {
14
14
  handleEntityMetadataInResponse,
15
15
  processEntityMetadata
16
16
  } from '@nextsparkjs/core/lib/api/helpers';
17
- import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth';
17
+ import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth';
18
18
  import { hasAdminPermission } from '@nextsparkjs/core/lib/api/auth/permissions';
19
19
  import { z } from 'zod';
20
20
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit';
@@ -34,21 +34,19 @@ const createUserSchema = z.object({
34
34
 
35
35
 
36
36
  // Handle CORS preflight
37
- export async function OPTIONS() {
38
- return handleCorsPreflightRequest();
37
+ export async function OPTIONS(request: NextRequest) {
38
+ return handleCorsPreflightRequest(request);
39
39
  }
40
40
 
41
41
  // GET /api/v1/users - List users with dual auth
42
42
  export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
43
43
  try {
44
- // Authenticate using dual auth
45
- const authResult = await authenticateRequest(req);
46
-
44
+ // Authenticate using dual auth; the API-key scope is declared at the entry
45
+ // point, which fails closed for keys that lack it (#93).
46
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:read' });
47
+
47
48
  if (!authResult.success) {
48
- return NextResponse.json(
49
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
50
- { status: 401 }
51
- );
49
+ return createAuthFailureResponse(authResult);
52
50
  }
53
51
 
54
52
  if (authResult.rateLimitResponse) {
@@ -58,7 +56,7 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
58
56
  // SECURITY: Only superadmins can list all users
59
57
  if (!hasAdminPermission(authResult, 'users:read')) {
60
58
  const response = createApiError('Insufficient permissions. Superadmin access required.', 403);
61
- return addCorsHeaders(response);
59
+ return addCorsHeaders(response, req);
62
60
  }
63
61
 
64
62
  const metaParams = parseMetaParams(req);
@@ -103,25 +101,23 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
103
101
  const usersWithMeta = await includeEntityMetadata('user', users as { id: string }[], metaParams, authResult.user!.id);
104
102
 
105
103
  const response = createApiResponse(usersWithMeta, paginationMeta);
106
- return addCorsHeaders(response);
104
+ return addCorsHeaders(response, req);
107
105
  } catch (error) {
108
106
  console.error('Error fetching users:', error);
109
107
  const response = createApiError('Internal server error', 500);
110
- return addCorsHeaders(response);
108
+ return addCorsHeaders(response, req);
111
109
  }
112
110
  }), 'read');
113
111
 
114
112
  // POST /api/v1/users - Create user with dual auth
115
113
  export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
116
114
  try {
117
- // Authenticate using dual auth
118
- const authResult = await authenticateRequest(req);
119
-
115
+ // Authenticate using dual auth; the API-key scope is declared at the entry
116
+ // point, which fails closed for keys that lack it (#93).
117
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:write' });
118
+
120
119
  if (!authResult.success) {
121
- return NextResponse.json(
122
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
123
- { status: 401 }
124
- );
120
+ return createAuthFailureResponse(authResult);
125
121
  }
126
122
 
127
123
  if (authResult.rateLimitResponse) {
@@ -131,7 +127,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
131
127
  // SECURITY: Only superadmins can create users
132
128
  if (!hasAdminPermission(authResult, 'users:write')) {
133
129
  const response = createApiError('Insufficient permissions. Superadmin access required.', 403);
134
- return addCorsHeaders(response);
130
+ return addCorsHeaders(response, req);
135
131
  }
136
132
 
137
133
  const body = await req.json();
@@ -147,7 +143,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
147
143
 
148
144
  if (existingUser.length > 0) {
149
145
  const response = createApiError('Email already exists', 409, null, 'EMAIL_EXISTS');
150
- return addCorsHeaders(response);
146
+ return addCorsHeaders(response, req);
151
147
  }
152
148
 
153
149
  const newUserId = globalThis.crypto.randomUUID();
@@ -183,16 +179,16 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
183
179
  const responseData = await handleEntityMetadataInResponse('user', createdUser as { id: string }, metadataWasProvided, authResult.user!.id);
184
180
 
185
181
  const response = createApiResponse(responseData, { created: true }, 201);
186
- return addCorsHeaders(response);
182
+ return addCorsHeaders(response, req);
187
183
  } catch (error) {
188
184
  if (error instanceof z.ZodError) {
189
185
  const response = createApiError('Validation error', 400, error.issues, 'VALIDATION_ERROR');
190
- return addCorsHeaders(response);
186
+ return addCorsHeaders(response, req);
191
187
  }
192
188
 
193
189
  console.error('Error creating user:', error);
194
190
  const response = createApiError('Internal server error', 500);
195
- return addCorsHeaders(response);
191
+ return addCorsHeaders(response, req);
196
192
  }
197
193
  }), 'write');
198
194
 
@@ -3,7 +3,7 @@
3
3
  import { useAuth } from '@nextsparkjs/core/hooks/useAuth'
4
4
  import { useRouter } from 'next/navigation'
5
5
  import { useEffect, Suspense } from 'react'
6
- import { Loader2 } from 'lucide-react'
6
+ import { DashboardAuthSkeleton } from '@nextsparkjs/core/components/dashboard/layouts/DashboardAuthSkeleton'
7
7
  import { DashboardProviders } from '@nextsparkjs/core/providers/DashboardProviders'
8
8
  import { DashboardTranslationPreloader } from '@nextsparkjs/core/lib/i18n/DashboardTranslationPreloader'
9
9
  import { TranslationDebugger } from '@nextsparkjs/core/utils/dev/TranslationDebugger'
@@ -35,11 +35,7 @@ function DashboardLayoutContent({ children }: { children: React.ReactNode }) {
35
35
  }, [user, isLoading, router])
36
36
 
37
37
  if (isLoading) {
38
- return (
39
- <div className="min-h-screen flex items-center justify-center">
40
- <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
41
- </div>
42
- )
38
+ return <DashboardAuthSkeleton />
43
39
  }
44
40
 
45
41
  if (!user) {
@@ -55,27 +55,22 @@ export const metadata: Metadata = getMetadataOrDefault(
55
55
  defaultMetadata
56
56
  )
57
57
 
58
- export default function RootLayout({
58
+ export default async function RootLayout({
59
59
  children,
60
60
  }: Readonly<{
61
61
  children: React.ReactNode
62
62
  }>) {
63
+ const billingHints = await getBillingResourceHints()
64
+
63
65
  return (
64
66
  <html lang={DEFAULT_LOCALE} suppressHydrationWarning>
65
67
  <head>
66
- {(() => {
67
- const hints = getBillingResourceHints()
68
- return (
69
- <>
70
- {hints.preconnect.map((domain) => (
71
- <link key={`pre-${domain}`} rel="preconnect" href={domain} />
72
- ))}
73
- {[...hints.preconnect, ...hints.dnsPrefetch].map((domain) => (
74
- <link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
75
- ))}
76
- </>
77
- )
78
- })()}
68
+ {billingHints.preconnect.map((domain) => (
69
+ <link key={`pre-${domain}`} rel="preconnect" href={domain} />
70
+ ))}
71
+ {[...billingHints.preconnect, ...billingHints.dnsPrefetch].map((domain) => (
72
+ <link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
73
+ ))}
79
74
  </head>
80
75
  <body
81
76
  className={`${geistSans.variable} ${geistMono.variable} antialiased`}
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { Metadata } from "next"
8
+ import { Suspense } from "react"
8
9
  import { Geist, Geist_Mono } from "next/font/google"
9
10
  import { NextIntlClientProvider } from 'next-intl'
10
11
  import { getMessages } from 'next-intl/server'
@@ -13,11 +14,16 @@ import "./globals.css"
13
14
  import { getBillingResourceHints } from "@nextsparkjs/core/lib/billing/gateways/factory"
14
15
  import { ThemeProvider as NextThemeProvider } from "@nextsparkjs/core/providers/theme-provider"
15
16
  import { ThemeProvider as CustomThemeProvider } from "@nextsparkjs/core/lib/theme/ThemeProvider"
17
+ import { Toaster } from "@nextsparkjs/core/components/ui/sonner"
18
+ import { QueryProvider } from "@nextsparkjs/core/providers/query-provider"
19
+ import { TeamProvider } from "@nextsparkjs/core/contexts/TeamContext"
20
+ import { SubscriptionProvider } from "@nextsparkjs/core/contexts/SubscriptionContext"
16
21
  import { getUserLocale } from '@nextsparkjs/core/lib/locale'
17
22
  import { TranslationContextManager } from "@nextsparkjs/core/providers/TranslationContextManager"
23
+ import { SessionCookieRefresher } from "@nextsparkjs/core/components/auth/SessionCookieRefresher"
18
24
  import { PluginService } from '@nextsparkjs/core/lib/services'
19
25
  import { getMetadataOrDefault } from '@nextsparkjs/core/lib/template-resolver'
20
- import { getDefaultThemeMode } from '@nextsparkjs/core/lib/theme/get-default-theme-mode'
26
+ import { getThemeSettings } from '@nextsparkjs/core/lib/theme/get-default-theme-mode'
21
27
 
22
28
  const geistSans = Geist({
23
29
  variable: "--font-geist-sans",
@@ -53,24 +59,18 @@ export default async function RootLayout({
53
59
 
54
60
  const locale = await getUserLocale()
55
61
  const messages = await getMessages({ locale })
56
- const defaultTheme = await getDefaultThemeMode()
62
+ const { defaultMode, allowUserToggle, forcedThemeRoutes } = await getThemeSettings()
63
+ const billingHints = await getBillingResourceHints()
57
64
 
58
65
  return (
59
66
  <html lang={locale} suppressHydrationWarning>
60
67
  <head>
61
- {(() => {
62
- const hints = getBillingResourceHints()
63
- return (
64
- <>
65
- {hints.preconnect.map((domain) => (
66
- <link key={`pre-${domain}`} rel="preconnect" href={domain} />
67
- ))}
68
- {[...hints.preconnect, ...hints.dnsPrefetch].map((domain) => (
69
- <link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
70
- ))}
71
- </>
72
- )
73
- })()}
68
+ {billingHints.preconnect.map((domain) => (
69
+ <link key={`pre-${domain}`} rel="preconnect" href={domain} />
70
+ ))}
71
+ {[...billingHints.preconnect, ...billingHints.dnsPrefetch].map((domain) => (
72
+ <link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
73
+ ))}
74
74
  </head>
75
75
  <body
76
76
  className={`${geistSans.variable} ${geistMono.variable} antialiased`}
@@ -78,13 +78,33 @@ export default async function RootLayout({
78
78
  <NextIntlClientProvider locale={locale} messages={messages}>
79
79
  <NextThemeProvider
80
80
  attribute="class"
81
- defaultTheme={defaultTheme}
82
- enableSystem
81
+ defaultTheme={defaultMode}
82
+ // When allowUserToggle is false, force the theme and ignore localStorage/system
83
+ forcedTheme={!allowUserToggle ? defaultMode : undefined}
84
+ // Only detect OS preference when theme configures defaultMode: 'system' AND user can toggle
85
+ enableSystem={allowUserToggle && defaultMode === 'system'}
86
+ // Force a theme on the routes declared in theme.config.ts (forcedThemeRoutes)
87
+ forcedThemeRoutes={forcedThemeRoutes}
83
88
  disableTransitionOnChange
84
89
  >
85
90
  <CustomThemeProvider>
86
- <TranslationContextManager />
87
- <main>{children}</main>
91
+ {/* Team providers live here (not only in DashboardProviders) so the activeTeamId
92
+ cookie is synced from any route, matching layout.ppr.tsx — see #115.
93
+ DashboardProviders skips re-mounting them when already present. */}
94
+ <QueryProvider>
95
+ <TeamProvider>
96
+ <SubscriptionProvider>
97
+ <TranslationContextManager />
98
+ {/* Real session-cookie renewal for installed PWAs: the render-time
99
+ session reads above (getUserLocale/getThemeSettings) cannot
100
+ write cookies, so the rolling refresh is triggered from the
101
+ client through the auth Route Handler instead. */}
102
+ <SessionCookieRefresher />
103
+ <main>{children}</main>
104
+ <Suspense><Toaster position="bottom-left" /></Suspense>
105
+ </SubscriptionProvider>
106
+ </TeamProvider>
107
+ </QueryProvider>
88
108
  </CustomThemeProvider>
89
109
  </NextThemeProvider>
90
110
  </NextIntlClientProvider>
@@ -115,6 +115,25 @@ export const APP_CONFIG_OVERRIDES = {
115
115
  enabled: false,
116
116
  },
117
117
  },
118
+ /**
119
+ * Login methods, in priority order. Core default = passwordless preset:
120
+ * a one-time code by email + Google, no password field (needs an email
121
+ * provider — RESEND_API_KEY — and GOOGLE_CLIENT_ID/SECRET for Google).
122
+ *
123
+ * Uncomment to switch to the classic email + password preset, or list both
124
+ * (the first email method is the form shown first):
125
+ */
126
+ // methods: ['email-password', 'google'],
127
+ // methods: ['email-otp', 'email-password', 'google'],
128
+
129
+ /**
130
+ * Session duration / renewal in seconds (defaults: 7 days, renewed daily).
131
+ * Long-lived sessions for an installed PWA, for example:
132
+ */
133
+ // session: {
134
+ // expiresIn: 60 * 60 * 24 * 90, // 90 days
135
+ // updateAge: 60 * 60 * 24 * 7, // renew weekly
136
+ // },
118
137
  /**
119
138
  * Whether Better Auth automatically sends the verification email on signup.
120
139
  *
@@ -22,6 +22,12 @@ export const starterThemeConfig: ThemeConfig = {
22
22
  // ==========================================
23
23
  plugins: [],
24
24
 
25
+ // ==========================================
26
+ // THEME MODE (Optional)
27
+ // ==========================================
28
+ // defaultMode: 'system', // 'light' | 'dark' | 'system'
29
+ // forcedThemeRoutes: { '/login': 'light' }, // force a theme on route prefixes (longest wins)
30
+
25
31
  // ==========================================
26
32
  // STYLES CONFIGURATION
27
33
  // ==========================================
@@ -200,8 +200,12 @@
200
200
  /* =============================================
201
201
  BASE STYLES
202
202
  ============================================= */
203
- * {
204
- border-color: var(--border);
203
+ /* Must live in @layer base: un-layered rules beat every Tailwind v4 utility
204
+ (border-primary, border-destructive, ...) regardless of specificity. */
205
+ @layer base {
206
+ * {
207
+ border-color: var(--border);
208
+ }
205
209
  }
206
210
 
207
211
  body {