create-nextblock 0.9.0 → 0.9.6

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 (45) hide show
  1. package/bin/create-nextblock.js +40 -60
  2. package/docker-template/.env.docker.example +5 -4
  3. package/docker-template/Dockerfile +20 -3
  4. package/docker-template/docker-compose.yml +5 -1
  5. package/docker-template/scripts/docker-setup.mjs +19 -4
  6. package/package.json +1 -1
  7. package/templates/nextblock-template/Dockerfile +20 -3
  8. package/templates/nextblock-template/app/[slug]/page.tsx +5 -0
  9. package/templates/nextblock-template/app/actions.ts +58 -8
  10. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +83 -0
  11. package/templates/nextblock-template/app/api/process-image/route.ts +13 -9
  12. package/templates/nextblock-template/app/article/[slug]/page.tsx +5 -0
  13. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +19 -13
  14. package/templates/nextblock-template/app/cms/settings/security/actions.ts +30 -0
  15. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +69 -0
  16. package/templates/nextblock-template/app/layout.tsx +49 -3
  17. package/templates/nextblock-template/app/lib/site-settings.ts +22 -7
  18. package/templates/nextblock-template/app/page.tsx +6 -0
  19. package/templates/nextblock-template/app/product/[slug]/page.tsx +5 -0
  20. package/templates/nextblock-template/app/providers.tsx +1 -1
  21. package/templates/nextblock-template/app/setup/SetupWizard.tsx +773 -0
  22. package/templates/nextblock-template/app/setup/layout.tsx +13 -0
  23. package/templates/nextblock-template/app/setup/page.tsx +103 -0
  24. package/templates/nextblock-template/components/AppShell.tsx +12 -0
  25. package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -0
  26. package/templates/nextblock-template/components/header-auth.tsx +24 -62
  27. package/templates/nextblock-template/docker-compose.yml +5 -1
  28. package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -0
  29. package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +67 -0
  30. package/templates/nextblock-template/docs/README.md +2 -0
  31. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +1 -1
  32. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +1 -1
  33. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +9 -1
  34. package/templates/nextblock-template/lib/setup/actions.ts +370 -0
  35. package/templates/nextblock-template/lib/setup/env-status.ts +86 -0
  36. package/templates/nextblock-template/lib/setup/env-write.ts +111 -0
  37. package/templates/nextblock-template/lib/setup/provisioning.ts +59 -0
  38. package/templates/nextblock-template/lib/setup/schema-apply.ts +392 -0
  39. package/templates/nextblock-template/lib/setup/system-config.ts +105 -0
  40. package/templates/nextblock-template/lib/setup/types.ts +18 -0
  41. package/templates/nextblock-template/next.config.js +13 -0
  42. package/templates/nextblock-template/package.json +1 -1
  43. package/templates/nextblock-template/proxy.ts +143 -49
  44. package/templates/nextblock-template/scripts/docker-setup.mjs +19 -4
  45. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
@@ -28,6 +28,7 @@ import {
28
28
  revokeTrustedDeviceAction,
29
29
  sendEmailEnrollmentCode,
30
30
  startTotpEnrollment,
31
+ updateAutoAcceptSignups,
31
32
  updateGlobalSecuritySettings,
32
33
  verifyEmailEnrollment,
33
34
  verifyTotpEnrollment,
@@ -369,10 +370,78 @@ export default function SecurityPanel({ data }: { data: SecurityPanelData }) {
369
370
  onSave={(formData) => run(() => updateGlobalSecuritySettings(formData))}
370
371
  />
371
372
  )}
373
+
374
+ {/* Sign-up policy (admin only) */}
375
+ {data.isAdmin && (
376
+ <SignupPolicyCard
377
+ initial={data.autoAcceptSignups}
378
+ isPending={isPending}
379
+ onSave={(formData) => run(() => updateAutoAcceptSignups(formData))}
380
+ />
381
+ )}
372
382
  </>
373
383
  );
374
384
  }
375
385
 
386
+ function SignupPolicyCard({
387
+ initial,
388
+ isPending,
389
+ onSave,
390
+ }: {
391
+ initial: boolean;
392
+ isPending: boolean;
393
+ onSave: (formData: FormData) => void;
394
+ }) {
395
+ const [enabled, setEnabled] = useState(initial);
396
+
397
+ return (
398
+ <Card>
399
+ <CardHeader>
400
+ <CardTitle>Sign-up Policy (Admin)</CardTitle>
401
+ <CardDescription>
402
+ Controls how new public registrations are handled across the whole site.
403
+ </CardDescription>
404
+ </CardHeader>
405
+ <CardContent>
406
+ <form
407
+ onSubmit={(e) => {
408
+ e.preventDefault();
409
+ const formData = new FormData();
410
+ formData.append('auto_accept_signups', String(enabled));
411
+ onSave(formData);
412
+ }}
413
+ className="space-y-6"
414
+ >
415
+ <div className="flex items-start gap-3">
416
+ <Checkbox
417
+ id="auto_accept_signups"
418
+ checked={enabled}
419
+ onCheckedChange={(checked) => setEnabled(checked === true)}
420
+ className="mt-1"
421
+ />
422
+ <div className="space-y-1">
423
+ <Label htmlFor="auto_accept_signups">
424
+ Auto-approve registrations (skip outbound email verification)
425
+ </Label>
426
+ <p className="text-xs text-slate-500">
427
+ New accounts become active immediately, even without SMTP configured. Convenient
428
+ for local / self-hosted use; leave off for public production sites.
429
+ </p>
430
+ </div>
431
+ </div>
432
+
433
+ <Separator />
434
+
435
+ <Button type="submit" disabled={isPending}>
436
+ {isPending ? <Spinner className="mr-2 h-4 w-4 animate-spin" /> : null}
437
+ Save Policy
438
+ </Button>
439
+ </form>
440
+ </CardContent>
441
+ </Card>
442
+ );
443
+ }
444
+
376
445
  function AdminPolicyCard({
377
446
  initial,
378
447
  isPending,
@@ -8,6 +8,7 @@ import { DeferredCartDrawer } from '../components/DeferredCartDrawer';
8
8
  import { CURRENCY_COOKIE_NAME } from '@nextblock-cms/ecommerce/currency-constants';
9
9
  import { ToasterProvider } from './ToasterProvider';
10
10
  import { AppShell } from '../components/AppShell';
11
+ import { PublicEnvBootstrap } from '../components/PublicEnvBootstrap';
11
12
  import { ConsentGatedAnalytics } from '../components/privacy/ConsentGatedAnalytics';
12
13
  import { ConsentBanner } from '../components/privacy/ConsentBanner';
13
14
  import { getPrivacySettings } from '../lib/privacy/settings';
@@ -25,6 +26,7 @@ import { verifyPackageOnline } from '@nextblock-cms/db/server';
25
26
  import { unstable_cache } from 'next/cache';
26
27
  import { createStaticSupabaseClient, getSiteSettings } from './lib/site-settings';
27
28
  import { DEFAULT_OG_IMAGE } from './lib/seo';
29
+ import { isSupabaseConfigured } from '../lib/setup/env-status';
28
30
 
29
31
  const defaultUrl = process.env.NEXT_PUBLIC_URL || 'http://localhost:3000';
30
32
 
@@ -230,11 +232,40 @@ const getCachedActiveLogo = unstable_cache(
230
232
  );
231
233
 
232
234
  async function loadLayoutData() {
233
- const supabase = createSupabaseServerClient();
234
-
235
235
  const headerList = await headers();
236
- const cookieStore = await cookies();
237
236
  const nonce = headerList.get('x-nonce') || '';
237
+ const requestPath = headerList.get('x-nextblock-path') || '';
238
+
239
+ // Skip the public-chrome data loading when there's nothing to render it on: an
240
+ // unconfigured instance, OR the standalone /setup wizard. On /setup, AppShell shows no
241
+ // header/footer anyway, and the DB schema may not exist yet (configured-but-pre-migrate)
242
+ // — querying it just produces noisy "table not found" errors for data nobody displays.
243
+ if (!isSupabaseConfigured() || requestPath.startsWith('/setup')) {
244
+ return {
245
+ user: null,
246
+ profile: null,
247
+ serverDeterminedLocale: DEFAULT_LOCALE_FOR_LAYOUT,
248
+ availableCurrencies: [] as StoreCurrency[],
249
+ serverCurrencyCode: null,
250
+ availableLanguages: [] as Language[],
251
+ defaultLanguage: null,
252
+ translations: [] as Awaited<ReturnType<typeof getCachedTranslations>>,
253
+ copyrightText: '',
254
+ nonce,
255
+ hasSupabaseEnv: false,
256
+ headerNavItems: [] as NavigationItem[],
257
+ footerNavItems: [] as NavigationItem[],
258
+ logo: null as HeaderLogo | null,
259
+ canAccessCms: false,
260
+ siteTitle: 'NextBlock',
261
+ isEcommerceActive: false,
262
+ globalCss: '',
263
+ privacySettings: DEFAULT_PRIVACY_SETTINGS,
264
+ };
265
+ }
266
+
267
+ const supabase = createSupabaseServerClient();
268
+ const cookieStore = await cookies();
238
269
 
239
270
  const xUserLocaleHeader = headerList.get('x-user-locale');
240
271
  const nextUserLocaleCookie = cookieStore.get('NEXT_USER_LOCALE')?.value;
@@ -417,6 +448,14 @@ export default async function RootLayout({
417
448
  ? (await import('@vercel/toolbar/next')).VercelToolbar
418
449
  : null;
419
450
 
451
+ // Expose the PUBLIC Supabase values (url + anon key — both safe to ship to the browser)
452
+ // to the client at runtime via <PublicEnvBootstrap>. In production the client uses the
453
+ // build-time-inlined NEXT_PUBLIC_* and these just match; it only matters in local dev,
454
+ // where the wizard writes those vars at runtime and the loaded bundle would otherwise
455
+ // hold stale empties until a dev-server restart. Read from server process.env (fresh).
456
+ const publicSupabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || '';
457
+ const publicSupabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '';
458
+
420
459
  return (
421
460
  <html lang={serverDeterminedLocale} suppressHydrationWarning>
422
461
  <head>
@@ -424,6 +463,13 @@ export default async function RootLayout({
424
463
  {globalCss && <style dangerouslySetInnerHTML={{ __html: globalCss }} />}
425
464
  </head>
426
465
  <body className="min-h-screen">
466
+ {/* Sets window.__NEXTBLOCK_PUBLIC_ENV__ synchronously during render, before any
467
+ descendant calls the browser Supabase client — the local-dev runtime fallback. */}
468
+ <PublicEnvBootstrap
469
+ url={publicSupabaseUrl}
470
+ anonKey={publicSupabaseAnonKey}
471
+ r2Base={process.env.NEXT_PUBLIC_R2_BASE_URL || ''}
472
+ />
427
473
  {/* In development this loads after hydration to avoid browser-hidden nonce comparisons. */}
428
474
  <Script
429
475
  id="trusted-types-bootstrap"
@@ -23,13 +23,16 @@ export interface SiteSettings {
23
23
  * route `generateMetadata` functions.
24
24
  */
25
25
  export function createStaticSupabaseClient() {
26
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
26
+ // Fall back to a dummy host when unconfigured (fresh clone, pre-/setup) so the
27
+ // root layout can still render rather than crashing the whole app on boot. Callers
28
+ // (getSiteSettings + the getCached* helpers) already swallow failures and use
29
+ // fallbacks, and loadLayoutData short-circuits before reaching here when there is
30
+ // no Supabase env at all.
31
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://dummy.supabase.co';
27
32
  const supabaseKey =
28
- process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
29
-
30
- if (!supabaseUrl || !supabaseKey) {
31
- throw new Error('Missing Supabase environment variables for public layout data');
32
- }
33
+ process.env.SUPABASE_SERVICE_ROLE_KEY ||
34
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ||
35
+ 'dummy-anon-key';
33
36
 
34
37
  return createSupabaseJsClient<Database>(supabaseUrl, supabaseKey, {
35
38
  auth: {
@@ -53,6 +56,13 @@ export const getSiteSettings = unstable_cache(
53
56
  siteKeywords: DEFAULT_SITE_KEYWORDS,
54
57
  };
55
58
 
59
+ // Unconfigured instance (pre-/setup): the static client would point at the dummy
60
+ // host, so the fetch fails with a noisy DNS error before falling back. Skip it and
61
+ // return SEO defaults directly. (generateMetadata calls this even on /setup.)
62
+ if (!process.env.NEXT_PUBLIC_SUPABASE_URL) {
63
+ return fallback;
64
+ }
65
+
56
66
  try {
57
67
  const supabase = createStaticSupabaseClient();
58
68
  const { data, error } = await supabase
@@ -61,7 +71,12 @@ export const getSiteSettings = unstable_cache(
61
71
  .in('key', ['site_title', 'site_description', 'site_keywords']);
62
72
 
63
73
  if (error || !data) {
64
- console.error('Error fetching cached site settings:', error);
74
+ // PGRST205 = table not found: the schema isn't migrated yet (e.g. mid-setup,
75
+ // right after the connection is saved but before `npm run db:migrate`). That's an
76
+ // expected transient state — don't shout about it. Real errors still log.
77
+ if (error && error.code !== 'PGRST205') {
78
+ console.error('Error fetching cached site settings:', error);
79
+ }
65
80
  return fallback;
66
81
  }
67
82
 
@@ -63,6 +63,12 @@ async function getPreferredLocale() {
63
63
  }
64
64
 
65
65
  export async function generateMetadata(): Promise<Metadata> {
66
+ // Unconfigured instance (pre-/setup): skip all DB work so metadata generation
67
+ // can't crash the boot. The proxy redirects unconfigured traffic to /setup anyway.
68
+ if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
69
+ return { title: 'NextBlock' };
70
+ }
71
+
66
72
  const preferredLocale = await getPreferredLocale();
67
73
  const homepageSlug = await getHomepageSlugForLocale(preferredLocale);
68
74
  const pageData = await getPageDataBySlug(homepageSlug, preferredLocale);
@@ -74,6 +74,11 @@ function resolveCategoryName(category: any, languageCode?: string | null) {
74
74
  }
75
75
 
76
76
  export async function generateStaticParams() {
77
+ // Unconfigured instance (pre-/setup): no DB to read product slugs from.
78
+ if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
79
+ return [];
80
+ }
81
+
77
82
  const supabase = getSsgSupabaseClient();
78
83
  const { data: products } = await getProducts(supabase);
79
84
  const productRows = ((products || []) as any[]).filter(
@@ -65,7 +65,7 @@ export function Providers({ children, ...props }: { children: React.ReactNode;[k
65
65
  <TranslationBridge translations={translations}>
66
66
  <ThemeProvider
67
67
  attribute="class"
68
- defaultTheme="system"
68
+ defaultTheme="light"
69
69
  enableSystem
70
70
  disableTransitionOnChange
71
71
  nonce={nonce}