create-nextblock 0.11.2 → 0.12.0

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 (52) hide show
  1. package/package.json +1 -1
  2. package/templates/nextblock-template/app/(auth-pages)/sign-up/SignUpForm.tsx +141 -0
  3. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +40 -122
  4. package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +5 -0
  5. package/templates/nextblock-template/app/[slug]/page.tsx +5 -2
  6. package/templates/nextblock-template/app/[slug]/page.utils.ts +10 -9
  7. package/templates/nextblock-template/app/actions/email.ts +8 -1
  8. package/templates/nextblock-template/app/actions/feedback.ts +1 -0
  9. package/templates/nextblock-template/app/actions/formActions.ts +20 -97
  10. package/templates/nextblock-template/app/actions/interactions.ts +3 -2
  11. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +3 -2
  12. package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +1 -1
  13. package/templates/nextblock-template/app/actions/visualEditingActions.ts +2 -0
  14. package/templates/nextblock-template/app/actions.ts +14 -0
  15. package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -0
  16. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +47 -1
  17. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +4970 -8566
  18. package/templates/nextblock-template/app/article/[slug]/page.tsx +5 -2
  19. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +1 -0
  20. package/templates/nextblock-template/app/cms/pages/actions.ts +22 -18
  21. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +20 -2
  22. package/templates/nextblock-template/app/cms/posts/actions.ts +5 -0
  23. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +137 -133
  24. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +36 -0
  25. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +26 -1
  26. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +6 -2
  27. package/templates/nextblock-template/app/cms/settings/email/actions.ts +7 -3
  28. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +29 -14
  29. package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -0
  30. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +18 -5
  31. package/templates/nextblock-template/app/layout.tsx +29 -17
  32. package/templates/nextblock-template/app/lib/seo.test.ts +36 -0
  33. package/templates/nextblock-template/app/lib/seo.ts +32 -0
  34. package/templates/nextblock-template/app/product/[slug]/page.tsx +5 -2
  35. package/templates/nextblock-template/components/AppShell.tsx +16 -1
  36. package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -0
  37. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +36 -42
  38. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +7 -0
  39. package/templates/nextblock-template/lib/botProtection/verify.ts +134 -0
  40. package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -0
  41. package/templates/nextblock-template/lib/email/branding-format.ts +123 -0
  42. package/templates/nextblock-template/lib/email/branding.ts +76 -0
  43. package/templates/nextblock-template/lib/logos/active-logo.ts +53 -0
  44. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +1 -0
  45. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +8 -203
  46. package/templates/nextblock-template/lib/visual-editing/draft-content.ts +4 -2
  47. package/templates/nextblock-template/lib/visual-editing/mutations.ts +2 -2
  48. package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +1 -0
  49. package/templates/nextblock-template/package.json +1 -1
  50. package/templates/nextblock-template/proxy.ts +16 -0
  51. package/templates/nextblock-template/public/images/nextblock-logo-button-tiny.png +0 -0
  52. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
@@ -175,6 +175,7 @@ async function publishPageDraft(draft: ContentDraftRow, authorId: string) {
175
175
  addStringMeta(pageUpdate, draft, "status");
176
176
  addNullableStringMeta(pageUpdate, draft, "meta_title");
177
177
  addNullableStringMeta(pageUpdate, draft, "meta_description");
178
+ addNullableStringMeta(pageUpdate, draft, "custom_canonical");
178
179
  addNullableStringMeta(pageUpdate, draft, "feature_image_id");
179
180
 
180
181
  const { error: pageError } = await supabase
@@ -219,6 +220,7 @@ async function publishPostDraft(draft: ContentDraftRow, authorId: string) {
219
220
  addStringMeta(postUpdate, draft, "status");
220
221
  addNullableStringMeta(postUpdate, draft, "meta_title");
221
222
  addNullableStringMeta(postUpdate, draft, "meta_description");
223
+ addNullableStringMeta(postUpdate, draft, "custom_canonical");
222
224
  addNullableStringMeta(postUpdate, draft, "label");
223
225
  addNullableStringMeta(postUpdate, draft, "excerpt");
224
226
  addNullableStringMeta(postUpdate, draft, "subtitle");
@@ -9,6 +9,7 @@ import { createEmailChallenge, evaluateTwoFactor } from "../lib/auth/twoFactor";
9
9
  import { REMEMBER_INTENT_COOKIE, setSecureCookie } from "../lib/auth/cookies";
10
10
  import { sendTwoFactorCodeEmail } from "./actions/twoFactorEmail";
11
11
  import { getSystemConfiguration } from "../lib/setup/system-config";
12
+ import { verifyBotProtection } from "../lib/botProtection/verify";
12
13
 
13
14
  export const signUpAction = async (formData: FormData) => {
14
15
  const email = formData.get("email")?.toString();
@@ -30,6 +31,19 @@ export const signUpAction = async (formData: FormData) => {
30
31
  );
31
32
  }
32
33
 
34
+ // Bot protection: the same honeypot + Turnstile/reCAPTCHA check the public
35
+ // contact forms use, run BEFORE any account is created. On a honeypot hit we
36
+ // mimic a normal "check your email" success so the bot learns nothing; a failed
37
+ // captcha surfaces the reason. (The site-wide provider from CMS → Settings →
38
+ // Bot Protection applies; when it's "none", only the honeypot gates signup.)
39
+ const botCheck = await verifyBotProtection(formData);
40
+ if (!botCheck.ok) {
41
+ if (botCheck.reason === "honeypot") {
42
+ return encodedRedirect("success", "/sign-up", "auth.signup_check_email_profile");
43
+ }
44
+ return encodedRedirect("error", "/sign-up", botCheck.message);
45
+ }
46
+
33
47
  // Auto-accept mode (system_configuration.auto_accept_signups): create an already-
34
48
  // confirmed account via the service role so the user is active immediately, with no
35
49
  // outbound verification email — regardless of SMTP / project email-confirmation
@@ -0,0 +1,48 @@
1
+ import { NextResponse } from 'next/server';
2
+
3
+ import { resolveEmailBranding } from '../../../../lib/email/branding';
4
+
5
+ // Public, unauthenticated logo endpoint for the Supabase auth emails (magic link, password
6
+ // reset, confirm email, invite, ...). Those templates are rendered and sent by Supabase's
7
+ // own SMTP from static HTML, so they cannot read the CMS logo per-send. Instead their
8
+ // `<img>` points here (`{{ .SiteURL }}/api/brand/email-logo`); when the recipient opens the
9
+ // email their client fetches this route, which 302-redirects to the operator's CURRENT
10
+ // uploaded logo. That keeps the statically-pushed auth templates in sync with the live CMS
11
+ // logo with no re-deploy — "change at will".
12
+ //
13
+ // Email image proxies (e.g. Gmail) hit this unauthenticated and follow the redirect, so it
14
+ // must never require auth and must always return an image (never a 4xx/5xx that would show
15
+ // a broken-image icon). When no logo is configured it returns a 1x1 transparent PNG — a
16
+ // static <img> can't fall back to a text banner the way the app-rendered emails do.
17
+ export const dynamic = 'force-dynamic';
18
+
19
+ // 1x1 transparent PNG.
20
+ const TRANSPARENT_PNG = Buffer.from(
21
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
22
+ 'base64',
23
+ );
24
+
25
+ function transparentPixel(): NextResponse {
26
+ return new NextResponse(TRANSPARENT_PNG, {
27
+ status: 200,
28
+ headers: {
29
+ 'Content-Type': 'image/png',
30
+ // Short cache so a newly-set logo starts showing quickly on freshly-sent emails.
31
+ 'Cache-Control': 'public, max-age=60',
32
+ },
33
+ });
34
+ }
35
+
36
+ export async function GET() {
37
+ try {
38
+ const { logoUrl } = await resolveEmailBranding();
39
+ if (logoUrl) {
40
+ const response = NextResponse.redirect(logoUrl, 302);
41
+ response.headers.set('Cache-Control', 'public, max-age=300');
42
+ return response;
43
+ }
44
+ } catch {
45
+ // Fall through to the transparent pixel — never surface an error to an email client.
46
+ }
47
+ return transparentPixel();
48
+ }
@@ -14,7 +14,7 @@ import {
14
14
  } from '@nextblock-cms/ecommerce/server';
15
15
  import postgres from 'postgres';
16
16
 
17
- import { CORTEX_AI_PACKAGE_ID } from '@nextblock/cortex';
17
+ import { CORTEX_AI_PACKAGE_ID } from '@nextblock/cortex';
18
18
  import { SANDBOX_RESET_SQL } from './sandboxResetSql';
19
19
 
20
20
  export const dynamic = 'force-dynamic';
@@ -2712,6 +2712,48 @@ async function seedFakeStoreData(sql: SqlClient, supabaseAdmin: any) {
2712
2712
  }
2713
2713
  }
2714
2714
 
2715
+ // The home-page "Live Demo" promo (migration 00000000000006) sends visitors to this
2716
+ // very sandbox. That CTA is valuable on production (nextblock.dev) but pointless *inside*
2717
+ // the demo it points at, so we strip it after the reset replays migrations. The blocks are
2718
+ // tagged with a `nb-sandbox-promo` sentinel comment; after removing them we renumber the
2719
+ // affected home pages so their block ordering stays contiguous.
2720
+ async function removeSandboxPromoSections(sql: SqlClient) {
2721
+ const deleted = await sql`
2722
+ DELETE FROM public.blocks
2723
+ WHERE block_type = 'section'
2724
+ AND content::text LIKE '%nb-sandbox-promo%'
2725
+ RETURNING id
2726
+ `;
2727
+
2728
+ if (deleted.length === 0) {
2729
+ return;
2730
+ }
2731
+
2732
+ await sql`
2733
+ WITH home_pages AS (
2734
+ SELECT p.id
2735
+ FROM public.pages p
2736
+ JOIN public.languages l ON l.id = p.language_id
2737
+ WHERE (l.code = 'en' AND p.slug = 'home')
2738
+ OR (l.code = 'fr' AND p.slug = 'accueil')
2739
+ ),
2740
+ ranked AS (
2741
+ SELECT b.id,
2742
+ (ROW_NUMBER() OVER (PARTITION BY b.page_id ORDER BY b."order", b.id) - 1) AS new_order
2743
+ FROM public.blocks b
2744
+ WHERE b.page_id IN (SELECT id FROM home_pages)
2745
+ )
2746
+ UPDATE public.blocks b
2747
+ SET "order" = r.new_order
2748
+ FROM ranked r
2749
+ WHERE b.id = r.id AND b."order" <> r.new_order
2750
+ `;
2751
+
2752
+ console.log(
2753
+ `[Sandbox Reset] Removed ${deleted.length} sandbox-promo section(s) from the home pages.`
2754
+ );
2755
+ }
2756
+
2715
2757
  export async function GET(request: NextRequest) {
2716
2758
  // 1. Guard: fail closed anywhere that is not explicitly the sandbox.
2717
2759
  const isSandboxResetEnabled = process.env.NEXT_PUBLIC_IS_SANDBOX === 'true';
@@ -2818,6 +2860,10 @@ export async function GET(request: NextRequest) {
2818
2860
  throw dbError;
2819
2861
  }
2820
2862
 
2863
+ // The replayed migrations re-add the "Live Demo" home-page promo that points at this
2864
+ // sandbox — strip it here so the demo does not advertise itself to itself.
2865
+ await removeSandboxPromoSections(db);
2866
+
2821
2867
  const normalizedMediaCount = await normalizeMediaStorageKeys(db);
2822
2868
  if (normalizedMediaCount > 0) {
2823
2869
  console.log(