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.
- package/package.json +1 -1
- package/templates/nextblock-template/app/(auth-pages)/sign-up/SignUpForm.tsx +141 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +40 -122
- package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +5 -0
- package/templates/nextblock-template/app/[slug]/page.tsx +5 -2
- package/templates/nextblock-template/app/[slug]/page.utils.ts +10 -9
- package/templates/nextblock-template/app/actions/email.ts +8 -1
- package/templates/nextblock-template/app/actions/feedback.ts +1 -0
- package/templates/nextblock-template/app/actions/formActions.ts +20 -97
- package/templates/nextblock-template/app/actions/interactions.ts +3 -2
- package/templates/nextblock-template/app/actions/twoFactorEmail.ts +3 -2
- package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +1 -1
- package/templates/nextblock-template/app/actions/visualEditingActions.ts +2 -0
- package/templates/nextblock-template/app/actions.ts +14 -0
- package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -0
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +47 -1
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +4970 -8566
- package/templates/nextblock-template/app/article/[slug]/page.tsx +5 -2
- package/templates/nextblock-template/app/article/[slug]/page.utils.ts +1 -0
- package/templates/nextblock-template/app/cms/pages/actions.ts +22 -18
- package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +20 -2
- package/templates/nextblock-template/app/cms/posts/actions.ts +5 -0
- package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +137 -133
- package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +36 -0
- package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +26 -1
- package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +6 -2
- package/templates/nextblock-template/app/cms/settings/email/actions.ts +7 -3
- package/templates/nextblock-template/app/cms/settings/logos/actions.ts +29 -14
- package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -0
- package/templates/nextblock-template/app/cms/settings/logos/page.tsx +18 -5
- package/templates/nextblock-template/app/layout.tsx +29 -17
- package/templates/nextblock-template/app/lib/seo.test.ts +36 -0
- package/templates/nextblock-template/app/lib/seo.ts +32 -0
- package/templates/nextblock-template/app/product/[slug]/page.tsx +5 -2
- package/templates/nextblock-template/components/AppShell.tsx +16 -1
- package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -0
- package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +36 -42
- package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +7 -0
- package/templates/nextblock-template/lib/botProtection/verify.ts +134 -0
- package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -0
- package/templates/nextblock-template/lib/email/branding-format.ts +123 -0
- package/templates/nextblock-template/lib/email/branding.ts +76 -0
- package/templates/nextblock-template/lib/logos/active-logo.ts +53 -0
- package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +1 -0
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +8 -203
- package/templates/nextblock-template/lib/visual-editing/draft-content.ts +4 -2
- package/templates/nextblock-template/lib/visual-editing/mutations.ts +2 -2
- package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +1 -0
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/proxy.ts +16 -0
- package/templates/nextblock-template/public/images/nextblock-logo-button-tiny.png +0 -0
- 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(
|