@nextsparkjs/core 0.1.0-beta.104 → 0.1.0-beta.106

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 (87) hide show
  1. package/dist/components/dashboard/misc/QuickCreateDropdown.d.ts.map +1 -1
  2. package/dist/components/dashboard/misc/QuickCreateDropdown.js +12 -9
  3. package/dist/lib/entities/registry.client.d.ts +7 -1
  4. package/dist/lib/entities/registry.client.d.ts.map +1 -1
  5. package/dist/lib/entities/registry.client.js +12 -1
  6. package/dist/lib/entities/types.d.ts +6 -3
  7. package/dist/lib/entities/types.d.ts.map +1 -1
  8. package/dist/styles/classes.json +1 -1
  9. package/dist/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -1
  10. package/dist/templates/app/(auth)/signup/page.tsx +1 -1
  11. package/dist/templates/app/api/auth/[...all]/route.ts +39 -10
  12. package/dist/templates/app/api/v1/billing/cancel/route.ts +8 -5
  13. package/dist/templates/app/api/v1/billing/checkout/route.ts +3 -3
  14. package/dist/templates/app/api/v1/billing/portal/route.ts +2 -2
  15. package/dist/templates/app/dashboard/(main)/[entity]/loading.tsx +5 -2
  16. package/dist/templates/app/dashboard/(main)/loading.tsx +4 -1
  17. package/dist/templates/app/dashboard/(main)/patterns/[id]/edit/page.tsx +4 -1
  18. package/dist/templates/app/dashboard/(main)/patterns/[id]/page.tsx +4 -1
  19. package/dist/templates/app/dashboard/(main)/patterns/[id]/reports/page.tsx +4 -1
  20. package/dist/templates/app/dashboard/(main)/patterns/create/page.tsx +4 -1
  21. package/dist/templates/app/dashboard/(main)/patterns/page.tsx +4 -1
  22. package/dist/templates/app/dashboard/features/analytics/page.tsx +4 -1
  23. package/dist/templates/app/dashboard/features/automation/page.tsx +4 -1
  24. package/dist/templates/app/dashboard/features/layout.tsx +5 -2
  25. package/dist/templates/app/dashboard/features/loading.tsx +4 -1
  26. package/dist/templates/app/dashboard/features/webhooks/page.tsx +4 -1
  27. package/dist/templates/app/dashboard/permission-denied/page.tsx +4 -1
  28. package/dist/templates/app/dashboard/settings/api-keys/loading.tsx +4 -1
  29. package/dist/templates/app/dashboard/settings/billing/loading.tsx +4 -1
  30. package/dist/templates/app/dashboard/settings/invoices/loading.tsx +4 -1
  31. package/dist/templates/app/dashboard/settings/loading.tsx +4 -1
  32. package/dist/templates/app/dashboard/settings/notifications/loading.tsx +4 -1
  33. package/dist/templates/app/dashboard/settings/page.tsx +2 -1
  34. package/dist/templates/app/dashboard/settings/password/loading.tsx +4 -1
  35. package/dist/templates/app/dashboard/settings/plans/loading.tsx +4 -1
  36. package/dist/templates/app/dashboard/settings/profile/loading.tsx +4 -1
  37. package/dist/templates/app/dashboard/settings/security/loading.tsx +4 -1
  38. package/dist/templates/app/dashboard/settings/teams/loading.tsx +4 -1
  39. package/dist/templates/app/dashboard/settings/teams/permissions/page.tsx +4 -1
  40. package/dist/templates/contents/themes/starter/messages/de/navigation.json +1 -0
  41. package/dist/templates/contents/themes/starter/messages/en/navigation.json +1 -0
  42. package/dist/templates/contents/themes/starter/messages/es/navigation.json +1 -0
  43. package/dist/templates/contents/themes/starter/messages/fr/navigation.json +1 -0
  44. package/dist/templates/contents/themes/starter/messages/it/navigation.json +1 -0
  45. package/dist/templates/contents/themes/starter/messages/pt/navigation.json +1 -0
  46. package/package.json +2 -2
  47. package/scripts/build/registry/generators/entity-registry.mjs +13 -1
  48. package/scripts/build/registry/generators/template-registry.mjs +9 -2
  49. package/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -1
  50. package/templates/app/(auth)/signup/page.tsx +1 -1
  51. package/templates/app/api/auth/[...all]/route.ts +39 -10
  52. package/templates/app/api/v1/billing/cancel/route.ts +8 -5
  53. package/templates/app/api/v1/billing/checkout/route.ts +3 -3
  54. package/templates/app/api/v1/billing/portal/route.ts +2 -2
  55. package/templates/app/dashboard/(main)/[entity]/loading.tsx +5 -2
  56. package/templates/app/dashboard/(main)/loading.tsx +4 -1
  57. package/templates/app/dashboard/(main)/patterns/[id]/edit/page.tsx +4 -1
  58. package/templates/app/dashboard/(main)/patterns/[id]/page.tsx +4 -1
  59. package/templates/app/dashboard/(main)/patterns/[id]/reports/page.tsx +4 -1
  60. package/templates/app/dashboard/(main)/patterns/create/page.tsx +4 -1
  61. package/templates/app/dashboard/(main)/patterns/page.tsx +4 -1
  62. package/templates/app/dashboard/features/analytics/page.tsx +4 -1
  63. package/templates/app/dashboard/features/automation/page.tsx +4 -1
  64. package/templates/app/dashboard/features/layout.tsx +5 -2
  65. package/templates/app/dashboard/features/loading.tsx +4 -1
  66. package/templates/app/dashboard/features/webhooks/page.tsx +4 -1
  67. package/templates/app/dashboard/permission-denied/page.tsx +4 -1
  68. package/templates/app/dashboard/settings/api-keys/loading.tsx +4 -1
  69. package/templates/app/dashboard/settings/billing/loading.tsx +4 -1
  70. package/templates/app/dashboard/settings/invoices/loading.tsx +4 -1
  71. package/templates/app/dashboard/settings/loading.tsx +4 -1
  72. package/templates/app/dashboard/settings/notifications/loading.tsx +4 -1
  73. package/templates/app/dashboard/settings/page.tsx +2 -1
  74. package/templates/app/dashboard/settings/password/loading.tsx +4 -1
  75. package/templates/app/dashboard/settings/plans/loading.tsx +4 -1
  76. package/templates/app/dashboard/settings/profile/loading.tsx +4 -1
  77. package/templates/app/dashboard/settings/security/loading.tsx +4 -1
  78. package/templates/app/dashboard/settings/teams/loading.tsx +4 -1
  79. package/templates/app/dashboard/settings/teams/permissions/page.tsx +4 -1
  80. package/templates/contents/themes/starter/messages/de/navigation.json +1 -0
  81. package/templates/contents/themes/starter/messages/en/navigation.json +1 -0
  82. package/templates/contents/themes/starter/messages/es/navigation.json +1 -0
  83. package/templates/contents/themes/starter/messages/fr/navigation.json +1 -0
  84. package/templates/contents/themes/starter/messages/it/navigation.json +1 -0
  85. package/templates/contents/themes/starter/messages/pt/navigation.json +1 -0
  86. package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +0 -410
  87. package/templates/app/api/v1/billing/webhooks/polar/route.ts +0 -410
@@ -1,5 +1,8 @@
1
1
  import { SkeletonApiKeysPage } from '@nextsparkjs/core/components/ui/skeleton-settings'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function ApiKeysLoading() {
4
+ function ApiKeysLoading() {
4
5
  return <SkeletonApiKeysPage />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/settings/api-keys/loading.tsx', ApiKeysLoading)
@@ -1,5 +1,8 @@
1
1
  import { SkeletonBillingPage } from '@nextsparkjs/core/components/ui/skeleton-settings'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function BillingLoading() {
4
+ function BillingLoading() {
4
5
  return <SkeletonBillingPage />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/settings/billing/loading.tsx', BillingLoading)
@@ -1,5 +1,8 @@
1
1
  import { SkeletonInvoicesPage } from '@nextsparkjs/core/components/ui/skeleton-settings'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function InvoicesLoading() {
4
+ function InvoicesLoading() {
4
5
  return <SkeletonInvoicesPage />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/settings/invoices/loading.tsx', InvoicesLoading)
@@ -1,5 +1,8 @@
1
1
  import { SkeletonSettingsOverview } from '@nextsparkjs/core/components/ui/skeleton-settings'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function SettingsLoading() {
4
+ function SettingsLoading() {
4
5
  return <SkeletonSettingsOverview />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/settings/loading.tsx', SettingsLoading)
@@ -1,5 +1,8 @@
1
1
  import { SkeletonNotificationsPage } from '@nextsparkjs/core/components/ui/skeleton-settings'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function NotificationsLoading() {
4
+ function NotificationsLoading() {
4
5
  return <SkeletonNotificationsPage />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/settings/notifications/loading.tsx', NotificationsLoading)
@@ -14,6 +14,7 @@ import {
14
14
  Key,
15
15
  type LucideIcon
16
16
  } from 'lucide-react'
17
+ import { getTemplateOrDefaultClient } from '@nextsparkjs/registries/template-registry.client'
17
18
 
18
19
  // Icon mapping for settings pages
19
20
  const SETTINGS_ICONS: Record<string, LucideIcon> = {
@@ -89,4 +90,4 @@ function SettingsPage() {
89
90
  )
90
91
  }
91
92
 
92
- export default SettingsPage
93
+ export default getTemplateOrDefaultClient('app/dashboard/settings/page.tsx', SettingsPage)
@@ -1,5 +1,8 @@
1
1
  import { SkeletonPasswordPage } from '@nextsparkjs/core/components/ui/skeleton-settings'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function PasswordLoading() {
4
+ function PasswordLoading() {
4
5
  return <SkeletonPasswordPage />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/settings/password/loading.tsx', PasswordLoading)
@@ -1,5 +1,8 @@
1
1
  import { SkeletonPlansPage } from '@nextsparkjs/core/components/ui/skeleton-settings'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function PlansLoading() {
4
+ function PlansLoading() {
4
5
  return <SkeletonPlansPage />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/settings/plans/loading.tsx', PlansLoading)
@@ -1,5 +1,8 @@
1
1
  import { SkeletonProfileForm } from '@nextsparkjs/core/components/ui/skeleton-settings'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function ProfileLoading() {
4
+ function ProfileLoading() {
4
5
  return <SkeletonProfileForm />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/settings/profile/loading.tsx', ProfileLoading)
@@ -1,5 +1,8 @@
1
1
  import { SkeletonSecurityPage } from '@nextsparkjs/core/components/ui/skeleton-settings'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function SecurityLoading() {
4
+ function SecurityLoading() {
4
5
  return <SkeletonSecurityPage />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/settings/security/loading.tsx', SecurityLoading)
@@ -1,5 +1,8 @@
1
1
  import { SkeletonTeamsPage } from '@nextsparkjs/core/components/ui/skeleton-settings'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function TeamsLoading() {
4
+ function TeamsLoading() {
4
5
  return <SkeletonTeamsPage />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/settings/teams/loading.tsx', TeamsLoading)
@@ -4,6 +4,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@nextsparkjs/core/comp
4
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nextsparkjs/core/components/ui/card'
5
5
  // Use PermissionService which reads from the build-time generated registry
6
6
  import { PermissionService } from '@nextsparkjs/core/lib/services/permission.service'
7
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
7
8
 
8
9
  /**
9
10
  * Team Permissions Page
@@ -11,7 +12,7 @@ import { PermissionService } from '@nextsparkjs/core/lib/services/permission.ser
11
12
  * Displays a visual permissions matrix showing what each team role can do.
12
13
  * Organized by categories with tabs for easy navigation.
13
14
  */
14
- export default function TeamPermissionsPage() {
15
+ function TeamPermissionsPage() {
15
16
  const t = useTranslations('permissions')
16
17
 
17
18
  // Get all unique categories from registry
@@ -90,3 +91,5 @@ function RoleCard({ role }: { role: 'owner' | 'admin' | 'member' | 'viewer' }) {
90
91
  </div>
91
92
  )
92
93
  }
94
+
95
+ export default getTemplateOrDefault('app/dashboard/settings/teams/permissions/page.tsx', TeamPermissionsPage)
@@ -12,5 +12,6 @@
12
12
  "notifications": "Benachrichtigungen",
13
13
  "search": "Suchen",
14
14
  "quickCreate": "Schnellerstellung",
15
+ "quickCreateNew": "Neu: {entity}",
15
16
  "viewAll": "Alle anzeigen"
16
17
  }
@@ -12,5 +12,6 @@
12
12
  "notifications": "Notifications",
13
13
  "search": "Search",
14
14
  "quickCreate": "Quick Create",
15
+ "quickCreateNew": "Create new {entity}",
15
16
  "viewAll": "View All"
16
17
  }
@@ -12,5 +12,6 @@
12
12
  "notifications": "Notificaciones",
13
13
  "search": "Buscar",
14
14
  "quickCreate": "Crear rápido",
15
+ "quickCreateNew": "Nuevo {entity}",
15
16
  "viewAll": "Ver todo"
16
17
  }
@@ -12,5 +12,6 @@
12
12
  "notifications": "Notifications",
13
13
  "search": "Rechercher",
14
14
  "quickCreate": "Création rapide",
15
+ "quickCreateNew": "Nouveau {entity}",
15
16
  "viewAll": "Voir tout"
16
17
  }
@@ -12,5 +12,6 @@
12
12
  "notifications": "Notifiche",
13
13
  "search": "Cerca",
14
14
  "quickCreate": "Creazione Rapida",
15
+ "quickCreateNew": "Nuovo {entity}",
15
16
  "viewAll": "Vedi Tutto"
16
17
  }
@@ -12,5 +12,6 @@
12
12
  "notifications": "Notificações",
13
13
  "search": "Pesquisar",
14
14
  "quickCreate": "Criação Rápida",
15
+ "quickCreateNew": "Novo {entity}",
15
16
  "viewAll": "Ver Tudo"
16
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/core",
3
- "version": "0.1.0-beta.104",
3
+ "version": "0.1.0-beta.106",
4
4
  "description": "NextSpark - The complete SaaS framework for Next.js",
5
5
  "license": "MIT",
6
6
  "author": "NextSpark <hello@nextspark.dev>",
@@ -453,7 +453,7 @@
453
453
  "tailwind-merge": "^3.3.1",
454
454
  "uuid": "^13.0.0",
455
455
  "zod": "^4.1.5",
456
- "@nextsparkjs/testing": "0.1.0-beta.104"
456
+ "@nextsparkjs/testing": "0.1.0-beta.106"
457
457
  },
458
458
  "scripts": {
459
459
  "postinstall": "node scripts/postinstall.mjs || true",
@@ -212,7 +212,15 @@ export function generateEntityRegistryClient(entities, config) {
212
212
  return ` '${slug}': '${entity.name}'`
213
213
  }).join(',\n')
214
214
 
215
- return `/**
215
+ // Generate icon registration calls
216
+ const iconRegistrations = entities
217
+ .filter(entity => entity.exportName)
218
+ .map(entity => `registerEntityIcon('${entity.name}', ${entity.exportName}.icon)`)
219
+ .join('\n')
220
+
221
+ return `'use client'
222
+
223
+ /**
216
224
  * Client-Safe Entity Registry
217
225
  *
218
226
  * Generated at: ${new Date().toISOString()}
@@ -226,6 +234,10 @@ export function generateEntityRegistryClient(entities, config) {
226
234
  */
227
235
 
228
236
  ${imports}
237
+ import { registerEntityIcon } from '${convertCorePath('@/core/lib/entities/registry.client', outputFilePath, config)}'
238
+
239
+ // Pre-register entity icons so non-lucide icons survive server→client serialization
240
+ ${iconRegistrations}
229
241
 
230
242
  export interface ClientEntityConfig {
231
243
  name: string
@@ -171,9 +171,16 @@ async function hasServerOnlyExports(filePath) {
171
171
  /export\s+const\s+dynamic\s*=/,
172
172
  /export\s+const\s+fetchCache\s*=/,
173
173
  /export\s+const\s+runtime\s*=/,
174
+ /export\s+const\s+metadata\s*[=:]/,
174
175
  ]
175
176
 
176
- for (const pattern of [...serverFunctionExports, ...serverConstExports]) {
177
+ // Check for server-only module imports
178
+ const serverOnlyImports = [
179
+ /import\s+.*from\s+['"]next\/headers['"]/,
180
+ /import\s+.*from\s+['"]server-only['"]/,
181
+ ]
182
+
183
+ for (const pattern of [...serverFunctionExports, ...serverConstExports, ...serverOnlyImports]) {
177
184
  if (pattern.test(content)) {
178
185
  return true
179
186
  }
@@ -215,7 +222,7 @@ export async function generateTemplateRegistryClient(templates, config) {
215
222
  .filter(([appPath, pathTemplates]) => {
216
223
  const template = pathTemplates[0]
217
224
  // Only include page templates (not layouts) that can override components
218
- return template.templateType === 'page' && canOverrideComponent(appPath)
225
+ return (template.templateType === 'page' || template.templateType === 'layout') && canOverrideComponent(appPath)
219
226
  })
220
227
  .map(async ([appPath, pathTemplates]) => {
221
228
  const template = pathTemplates[0]
@@ -9,6 +9,7 @@ import { Loader2, CheckCircle, XCircle, Users, LogIn, UserPlus } from 'lucide-re
9
9
  import { toast } from 'sonner'
10
10
  import Link from 'next/link'
11
11
  import { sel } from '@nextsparkjs/core/selectors'
12
+ import { getTemplateOrDefaultClient } from '@nextsparkjs/registries/template-registry.client'
12
13
 
13
14
  type InvitationStatus = 'loading' | 'valid' | 'accepting' | 'accepted' | 'error' | 'expired' | 'not_found' | 'email_mismatch' | 'already_member' | 'requires_auth'
14
15
 
@@ -19,7 +20,7 @@ interface InvitationInfo {
19
20
  email: string
20
21
  }
21
22
 
22
- export default function AcceptInvitePage() {
23
+ function AcceptInvitePage() {
23
24
  const params = useParams()
24
25
  const router = useRouter()
25
26
  const { user, isLoading: authLoading } = useAuth()
@@ -333,3 +334,5 @@ export default function AcceptInvitePage() {
333
334
  </div>
334
335
  )
335
336
  }
337
+
338
+ export default getTemplateOrDefaultClient('app/(auth)/accept-invite/[token]/page.tsx', AcceptInvitePage)
@@ -40,4 +40,4 @@ async function SignupPage() {
40
40
 
41
41
  export const dynamic = 'force-dynamic'
42
42
 
43
- export default getTemplateOrDefault('app/(auth)/signup/page.tsx', SignupPage)
43
+ export default getTemplateOrDefault('app/(auth)/signup/page.tsx', SignupPage)
@@ -1,8 +1,10 @@
1
1
  import { auth } from "@nextsparkjs/core/lib/auth";
2
2
  import { toNextJsHandler } from "better-auth/next-js";
3
3
  import { NextRequest, NextResponse } from "next/server";
4
- import { TEAMS_CONFIG } from "@nextsparkjs/core/lib/config";
4
+ import { TEAMS_CONFIG, AUTH_CONFIG } from "@nextsparkjs/core/lib/config";
5
5
  import { isPublicSignupRestricted } from "@nextsparkjs/core/lib/teams/helpers";
6
+ // Registration helpers available if needed: shouldBlockSignup, isDomainAllowed
7
+ // Currently domain validation happens in auth.ts databaseHooks
6
8
  import { TeamService } from "@nextsparkjs/core/lib/services";
7
9
  import { wrapAuthHandlerWithCors, handleCorsPreflightRequest, addCorsHeaders } from "@nextsparkjs/core/lib/api/helpers";
8
10
 
@@ -41,25 +43,52 @@ export async function GET(req: NextRequest, context: { params: Promise<{ all: st
41
43
  return wrapAuthHandlerWithCors(() => handlers.GET(req), req);
42
44
  }
43
45
 
44
- // Intercept signup requests to validate single-tenant mode
46
+ // Intercept signup requests to validate registration mode
45
47
  export async function POST(req: NextRequest) {
46
48
  const pathname = req.nextUrl.pathname;
47
49
 
48
- // Check if this is a signup request (email or OAuth)
49
- const isSignupRequest =
50
- pathname === '/api/auth/sign-up/email' ||
51
- pathname.includes('/api/auth/callback/'); // OAuth callbacks can create users
50
+ // Determine request type
51
+ // Comprehensive signup endpoint detection to prevent bypasses
52
+ const signupEndpoints = [
53
+ '/sign-up/email',
54
+ '/sign-up/credentials',
55
+ '/signup',
56
+ '/register',
57
+ ];
58
+
59
+ const isSignupAttempt = signupEndpoints.some(endpoint => pathname.includes(endpoint));
60
+ const isOAuthCallback = pathname.includes('/api/auth/callback/');
61
+ const isSignupRequest = isSignupAttempt || isOAuthCallback;
52
62
 
53
63
  if (isSignupRequest) {
64
+ const registrationMode = AUTH_CONFIG?.registration?.mode ?? 'open';
54
65
  const teamsMode = TEAMS_CONFIG.mode;
55
66
 
56
- // In single-tenant mode, block public signup if team already exists
57
- if (isPublicSignupRestricted(teamsMode)) {
67
+ // --- Registration mode enforcement ---
68
+
69
+ // 1. Domain-restricted mode: block email signup, allow OAuth (validated in database hooks)
70
+ if (registrationMode === 'domain-restricted' && isSignupAttempt && !isOAuthCallback) {
71
+ // Block direct email/password signup in domain-restricted mode
72
+ // Only Google OAuth is allowed (domain validation happens in database hooks)
73
+ const errorResponse = NextResponse.json(
74
+ {
75
+ error: 'Email signup disabled',
76
+ message: 'Please sign up with Google using an authorized email domain.',
77
+ code: 'EMAIL_SIGNUP_DISABLED',
78
+ },
79
+ { status: 403 }
80
+ );
81
+ return await addCorsHeaders(errorResponse, req);
82
+ }
83
+
84
+ // Note: OAuth domain validation happens in auth.ts databaseHooks (user.create.before)
85
+ // The hook throws an error if the email domain is not in allowedDomains
86
+
87
+ // 2. Invitation-only mode OR single-tenant teams mode: existing behavior
88
+ if (registrationMode === 'invitation-only' || isPublicSignupRestricted(teamsMode)) {
58
89
  const teamExists = await TeamService.hasGlobal();
59
90
 
60
91
  if (teamExists) {
61
- // Block public signup - users must be invited
62
- // Add CORS headers so mobile apps can read the error message
63
92
  const errorResponse = NextResponse.json(
64
93
  {
65
94
  error: 'Registration is closed',
@@ -11,7 +11,11 @@ import { NextRequest, NextResponse } from 'next/server'
11
11
  import { z } from 'zod'
12
12
  import { authenticateRequest, createAuthError } from '@nextsparkjs/core/lib/api/auth/dual-auth'
13
13
  import { SubscriptionService, MembershipService } from '@nextsparkjs/core/lib/services'
14
- import { getBillingGateway } from '@nextsparkjs/core/lib/billing/gateways/factory'
14
+ import {
15
+ cancelSubscriptionAtPeriodEnd,
16
+ cancelSubscriptionImmediately,
17
+ reactivateSubscription
18
+ } from '@nextsparkjs/core/lib/billing/gateways/stripe'
15
19
  import { queryWithRLS } from '@nextsparkjs/core/lib/db'
16
20
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
17
21
 
@@ -104,11 +108,10 @@ export const POST = withRateLimitTier(async (request: NextRequest) => {
104
108
 
105
109
  // 6. Cancel via Stripe
106
110
  try {
107
- const gateway = getBillingGateway()
108
111
  if (immediate) {
109
- await gateway.cancelSubscriptionImmediately(subscription.externalSubscriptionId)
112
+ await cancelSubscriptionImmediately(subscription.externalSubscriptionId)
110
113
  } else {
111
- await gateway.cancelSubscriptionAtPeriodEnd(subscription.externalSubscriptionId)
114
+ await cancelSubscriptionAtPeriodEnd(subscription.externalSubscriptionId)
112
115
  }
113
116
 
114
117
  // 7. Update local DB
@@ -171,7 +174,7 @@ async function handleReactivation(teamId: string) {
171
174
  }
172
175
 
173
176
  try {
174
- await getBillingGateway().reactivateSubscription(subscription.externalSubscriptionId)
177
+ await reactivateSubscription(subscription.externalSubscriptionId)
175
178
 
176
179
  // Update local DB
177
180
  await queryWithRLS(
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { NextRequest, NextResponse } from 'next/server'
13
13
  import { authenticateRequest, createAuthError } from '@nextsparkjs/core/lib/api/auth/dual-auth'
14
- import { getBillingGateway } from '@nextsparkjs/core/lib/billing/gateways/factory'
14
+ import { createCheckoutSession } from '@nextsparkjs/core/lib/billing/gateways/stripe'
15
15
  import { SubscriptionService, MembershipService } from '@nextsparkjs/core/lib/services'
16
16
  import { z } from 'zod'
17
17
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
@@ -94,8 +94,8 @@ export const POST = withRateLimitTier(async (request: NextRequest) => {
94
94
  const successUrl = `${appUrl}/dashboard/settings/billing?success=true`
95
95
  const cancelUrl = `${appUrl}/dashboard/settings/billing?canceled=true`
96
96
 
97
- // Create checkout session via billing gateway
98
- const session = await getBillingGateway().createCheckoutSession({
97
+ // Create Stripe Checkout session
98
+ const session = await createCheckoutSession({
99
99
  teamId,
100
100
  planSlug,
101
101
  billingPeriod,
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { NextRequest, NextResponse } from 'next/server'
11
11
  import { authenticateRequest, createAuthError } from '@nextsparkjs/core/lib/api/auth/dual-auth'
12
- import { getBillingGateway } from '@nextsparkjs/core/lib/billing/gateways/factory'
12
+ import { createPortalSession } from '@nextsparkjs/core/lib/billing/gateways/stripe'
13
13
  import { SubscriptionService, MembershipService } from '@nextsparkjs/core/lib/services'
14
14
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
15
15
 
@@ -69,7 +69,7 @@ export const POST = withRateLimitTier(async (request: NextRequest) => {
69
69
  const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:5173'
70
70
  const returnUrl = `${appUrl}/dashboard/settings/billing`
71
71
 
72
- const session = await getBillingGateway().createPortalSession({
72
+ const session = await createPortalSession({
73
73
  customerId: subscription.externalCustomerId,
74
74
  returnUrl
75
75
  })
@@ -1,6 +1,7 @@
1
1
  import { Skeleton } from '@nextsparkjs/core/components/ui/skeleton'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function EntityLoading() {
4
+ function EntityLoading() {
4
5
  return (
5
6
  <div className="space-y-6 p-6">
6
7
  {/* Header skeleton */}
@@ -58,4 +59,6 @@ export default function EntityLoading() {
58
59
  </div>
59
60
  </div>
60
61
  )
61
- }
62
+ }
63
+
64
+ export default getTemplateOrDefault('app/dashboard/(main)/[entity]/loading.tsx', EntityLoading)
@@ -1,5 +1,8 @@
1
1
  import { SkeletonDashboardHome } from '@nextsparkjs/core/components/ui/skeleton-dashboard'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
- export default function DashboardLoading() {
4
+ function DashboardLoading() {
4
5
  return <SkeletonDashboardHome />
5
6
  }
7
+
8
+ export default getTemplateOrDefault('app/dashboard/(main)/loading.tsx', DashboardLoading)
@@ -14,8 +14,9 @@ import { EntityFormWrapper } from '@nextsparkjs/core/components/entities/wrapper
14
14
  import { BuilderEditorView } from '@nextsparkjs/core/components/dashboard/block-editor/builder-editor-view'
15
15
  import { Alert, AlertDescription } from '@nextsparkjs/core/components/ui/alert'
16
16
  import { getEntityData } from '@nextsparkjs/core/lib/api/entities'
17
+ import { getTemplateOrDefaultClient } from '@nextsparkjs/registries/template-registry.client'
17
18
 
18
- export default function PatternEditPage() {
19
+ function PatternEditPage() {
19
20
  const params = useParams()
20
21
  const router = useRouter()
21
22
  const [entityConfig, setEntityConfig] = useState<ClientEntityConfig | null>(null)
@@ -112,3 +113,5 @@ export default function PatternEditPage() {
112
113
  />
113
114
  )
114
115
  }
116
+
117
+ export default getTemplateOrDefaultClient('app/dashboard/(main)/patterns/[id]/edit/page.tsx', PatternEditPage)
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { redirect } from 'next/navigation'
8
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
8
9
 
9
10
  interface PageProps {
10
11
  params: Promise<{
@@ -12,9 +13,11 @@ interface PageProps {
12
13
  }>
13
14
  }
14
15
 
15
- export default async function PatternDetailPage({ params }: PageProps) {
16
+ async function PatternDetailPage({ params }: PageProps) {
16
17
  const resolvedParams = await params
17
18
 
18
19
  // Patterns are builder-enabled, so redirect to edit view
19
20
  redirect(`/dashboard/patterns/${resolvedParams.id}/edit`)
20
21
  }
22
+
23
+ export default getTemplateOrDefault('app/dashboard/(main)/patterns/[id]/page.tsx', PatternDetailPage)
@@ -17,6 +17,7 @@ import { Button } from '@nextsparkjs/core/components/ui/button'
17
17
  import { Alert, AlertDescription, AlertTitle } from '@nextsparkjs/core/components/ui/alert'
18
18
  import { Skeleton } from '@nextsparkjs/core/components/ui/skeleton'
19
19
  import { getEntityData } from '@nextsparkjs/core/lib/api/entities'
20
+ import { getTemplateOrDefaultClient } from '@nextsparkjs/registries/template-registry.client'
20
21
 
21
22
  interface PatternData {
22
23
  id: string
@@ -24,7 +25,7 @@ interface PatternData {
24
25
  title?: string
25
26
  }
26
27
 
27
- export default function PatternReportsPage() {
28
+ function PatternReportsPage() {
28
29
  const params = useParams()
29
30
  const router = useRouter()
30
31
  const patternId = params.id as string
@@ -169,3 +170,5 @@ export default function PatternReportsPage() {
169
170
  </div>
170
171
  )
171
172
  }
173
+
174
+ export default getTemplateOrDefaultClient('app/dashboard/(main)/patterns/[id]/reports/page.tsx', PatternReportsPage)
@@ -12,8 +12,9 @@ import { clientEntityRegistry, ensureClientInitialized, type ClientEntityConfig
12
12
  import { EntityFormWrapper } from '@nextsparkjs/core/components/entities/wrappers/EntityFormWrapper'
13
13
  import { BuilderEditorView } from '@nextsparkjs/core/components/dashboard/block-editor/builder-editor-view'
14
14
  import { Alert, AlertDescription } from '@nextsparkjs/core/components/ui/alert'
15
+ import { getTemplateOrDefaultClient } from '@nextsparkjs/registries/template-registry.client'
15
16
 
16
- export default function PatternsCreatePage() {
17
+ function PatternsCreatePage() {
17
18
  const router = useRouter()
18
19
  const [entityConfig, setEntityConfig] = useState<ClientEntityConfig | null>(null)
19
20
  const [loading, setLoading] = useState(true)
@@ -84,3 +85,5 @@ export default function PatternsCreatePage() {
84
85
  />
85
86
  )
86
87
  }
88
+
89
+ export default getTemplateOrDefaultClient('app/dashboard/(main)/patterns/create/page.tsx', PatternsCreatePage)
@@ -28,6 +28,7 @@ import type { Permission } from '@nextsparkjs/core/lib/permissions/types'
28
28
  import type { QuickAction, DropdownAction } from '@nextsparkjs/core/components/entities/entity-table.types'
29
29
  import { sel } from '@nextsparkjs/core/lib/test'
30
30
  import { toast } from 'sonner'
31
+ import { getTemplateOrDefaultClient } from '@nextsparkjs/registries/template-registry.client'
31
32
 
32
33
  interface PatternItem {
33
34
  id: string
@@ -37,7 +38,7 @@ interface PatternItem {
37
38
  [key: string]: unknown
38
39
  }
39
40
 
40
- export default function PatternsListPage() {
41
+ function PatternsListPage() {
41
42
  const entityType = 'patterns'
42
43
  const router = useRouter()
43
44
 
@@ -442,3 +443,5 @@ export default function PatternsListPage() {
442
443
  </div>
443
444
  )
444
445
  }
446
+
447
+ export default getTemplateOrDefaultClient('app/dashboard/(main)/patterns/page.tsx', PatternsListPage)
@@ -4,8 +4,9 @@ import { FeatureGate } from '@nextsparkjs/core/components/billing/FeatureGate'
4
4
  import { FeaturePlaceholder } from '@nextsparkjs/core/components/billing/FeaturePlaceholder'
5
5
  import { BarChart3 } from 'lucide-react'
6
6
  import { useTranslations } from 'next-intl'
7
+ import { getTemplateOrDefaultClient } from '@nextsparkjs/registries/template-registry.client'
7
8
 
8
- export default function AdvancedAnalyticsPage() {
9
+ function AdvancedAnalyticsPage() {
9
10
  const t = useTranslations('features')
10
11
 
11
12
  return (
@@ -33,3 +34,5 @@ export default function AdvancedAnalyticsPage() {
33
34
  </div>
34
35
  )
35
36
  }
37
+
38
+ export default getTemplateOrDefaultClient('app/dashboard/features/analytics/page.tsx', AdvancedAnalyticsPage)
@@ -4,8 +4,9 @@ import { FeatureGate } from '@nextsparkjs/core/components/billing/FeatureGate'
4
4
  import { FeaturePlaceholder } from '@nextsparkjs/core/components/billing/FeaturePlaceholder'
5
5
  import { Zap } from 'lucide-react'
6
6
  import { useTranslations } from 'next-intl'
7
+ import { getTemplateOrDefaultClient } from '@nextsparkjs/registries/template-registry.client'
7
8
 
8
- export default function TaskAutomationPage() {
9
+ function TaskAutomationPage() {
9
10
  const t = useTranslations('features')
10
11
 
11
12
  return (
@@ -33,3 +34,5 @@ export default function TaskAutomationPage() {
33
34
  </div>
34
35
  )
35
36
  }
37
+
38
+ export default getTemplateOrDefaultClient('app/dashboard/features/automation/page.tsx', TaskAutomationPage)
@@ -1,13 +1,16 @@
1
- import { ReactNode } from 'react'
1
+ import { type ReactNode } from 'react'
2
+ import { getTemplateOrDefault } from '@nextsparkjs/core/lib/template-resolver'
2
3
 
3
4
  interface FeaturesLayoutProps {
4
5
  children: ReactNode
5
6
  }
6
7
 
7
- export default function FeaturesLayout({ children }: FeaturesLayoutProps) {
8
+ function FeaturesLayout({ children }: FeaturesLayoutProps) {
8
9
  return (
9
10
  <div className="container py-8" data-cy="features-layout">
10
11
  {children}
11
12
  </div>
12
13
  )
13
14
  }
15
+
16
+ export default getTemplateOrDefault('app/dashboard/features/layout.tsx', FeaturesLayout)