create-nextblock 0.11.1 → 0.11.3

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 (59) hide show
  1. package/package.json +1 -1
  2. package/templates/nextblock-template/app/actions/interactions.test.ts +301 -0
  3. package/templates/nextblock-template/app/actions/interactions.ts +372 -0
  4. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +4 -4
  5. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +2 -2
  6. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +56 -57
  7. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +1 -1
  8. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +951 -0
  9. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +6 -0
  10. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +4 -0
  11. package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +7 -2
  12. package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +4 -0
  13. package/templates/nextblock-template/app/cms/interactions/InteractionsModerationClient.tsx +408 -0
  14. package/templates/nextblock-template/app/cms/interactions/page.tsx +51 -0
  15. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +4 -3
  16. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +1 -1
  17. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +3 -5
  18. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +1 -1
  19. package/templates/nextblock-template/app/page.tsx +2 -2
  20. package/templates/nextblock-template/app/product/[slug]/page.tsx +2 -0
  21. package/templates/nextblock-template/components/AppShell.tsx +1 -1
  22. package/templates/nextblock-template/components/PostCommentsSection.tsx +369 -0
  23. package/templates/nextblock-template/components/ProductReviewsSection.tsx +419 -0
  24. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +2 -0
  25. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +62 -19
  26. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +19 -19
  27. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +4 -4
  28. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +7 -0
  29. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +2 -0
  30. package/templates/nextblock-template/lib/setup/actions.ts +3 -1
  31. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +40 -0
  32. package/templates/nextblock-template/lib/updates/check-upstream.ts +38 -4
  33. package/templates/nextblock-template/package.json +2 -1
  34. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +2 -4
  35. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +1 -1
  36. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +1 -1
  37. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +1 -1
  38. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  39. package/templates/nextblock-template/lib/ai-block-generation.ts +0 -339
  40. package/templates/nextblock-template/lib/ai-client.ts +0 -247
  41. package/templates/nextblock-template/lib/ai-config.ts +0 -98
  42. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +0 -125
  43. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +0 -363
  44. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +0 -405
  45. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +0 -1228
  46. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +0 -5
  47. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +0 -223
  48. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +0 -2183
  49. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +0 -4807
  50. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +0 -70
  51. package/templates/nextblock-template/lib/ai-key-crypto.ts +0 -132
  52. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +0 -49
  53. package/templates/nextblock-template/lib/ai-model-catalog.ts +0 -41
  54. package/templates/nextblock-template/lib/ai-model-registry.test.ts +0 -231
  55. package/templates/nextblock-template/lib/ai-model-registry.ts +0 -522
  56. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +0 -199
  57. package/templates/nextblock-template/lib/cortex-widget-registry.ts +0 -88
  58. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +0 -237
  59. package/templates/nextblock-template/lib/cortex-widget-schema.ts +0 -393
@@ -8255,6 +8255,957 @@ grant select, update on public.system_alerts to authenticated;
8255
8255
  grant all on public.system_alerts to service_role;
8256
8256
 
8257
8257
 
8258
+ -- >>> FROM: 00000000000037_refresh_setup_article_install_paths.sql <<<
8259
+ -- Rewrite the seeded "How to Setup NextBlock" tutorial (EN + FR) around the four
8260
+ -- current installation paths:
8261
+ -- 1. One-click Deploy on Vercel (Supabase Marketplace integration, zero env vars),
8262
+ -- 2. npm create nextblock (standalone app, browser /setup wizard on :3000),
8263
+ -- 3. git clone -> npm install -> npx nx serve nextblock (browser /setup wizard on :4200),
8264
+ -- 4. git clone -> npm install -> npm run docker:setup (fully local, non-interactive).
8265
+ --
8266
+ -- Also sets the posts' meta_title / meta_description (previously unset — SEO title fell
8267
+ -- back to posts.title and the description to posts.subtitle) and refreshes
8268
+ -- title / subtitle / excerpt for both languages. Slugs are intentionally unchanged:
8269
+ -- 'how-to-setup-nextblock' and 'comment-configurer-nextblock' are public URLs and are
8270
+ -- also mapped to the tutorial presentation (post-article--tutorial) by slug.
8271
+ --
8272
+ -- Forward-only and idempotent: it updates the two posts rows and replaces their single
8273
+ -- text block (same pattern as 00000000000029). Safe to re-run; a no-op if the posts
8274
+ -- do not exist.
8275
+
8276
+ -- EN post metadata
8277
+ UPDATE public.posts
8278
+ SET
8279
+ title = 'How to Install NextBlock: Every Setup Option Explained',
8280
+ subtitle = 'Four ways to launch NextBlock: a one-click Vercel deploy, npm create nextblock, git clone with the browser setup wizard, or a fully local Docker stack.',
8281
+ excerpt = 'Every way to install NextBlock — one-click cloud deploy, CLI scaffold, git clone, or self-hosted Docker — with copy-paste steps for each.',
8282
+ meta_title = 'How to Install NextBlock CMS — Vercel, CLI, Git or Docker',
8283
+ meta_description = 'Install NextBlock in minutes — one-click Vercel deploy, npm create nextblock, git clone, or self-hosted Docker. No config files, no manual SQL.'
8284
+ WHERE slug = 'how-to-setup-nextblock';
8285
+
8286
+ -- FR post metadata
8287
+ UPDATE public.posts
8288
+ SET
8289
+ title = $meta$Installer NextBlock : toutes les options expliquées$meta$,
8290
+ subtitle = $meta$Quatre façons de lancer NextBlock : déploiement Vercel en un clic, npm create nextblock, git clone avec l'assistant dans le navigateur, ou une pile Docker 100 % locale.$meta$,
8291
+ excerpt = $meta$Toutes les façons d'installer NextBlock — cloud en un clic, CLI, git clone ou Docker auto-hébergé — avec les étapes à copier-coller.$meta$,
8292
+ meta_title = $meta$Installer NextBlock — Vercel, CLI, Git ou Docker$meta$,
8293
+ meta_description = $meta$Installez NextBlock en quelques minutes : déploiement Vercel en un clic, npm create nextblock, git clone ou Docker auto-hébergé. Sans config ni SQL.$meta$
8294
+ WHERE slug = 'comment-configurer-nextblock';
8295
+
8296
+ WITH target_posts AS (
8297
+ SELECT id, language_id, slug
8298
+ FROM public.posts
8299
+ WHERE slug IN ('how-to-setup-nextblock', 'comment-configurer-nextblock')
8300
+ ),
8301
+ purged AS (
8302
+ DELETE FROM public.blocks
8303
+ WHERE post_id IN (SELECT id FROM target_posts)
8304
+ )
8305
+ INSERT INTO public.blocks (post_id, language_id, block_type, content, "order")
8306
+
8307
+ -- EN: How to Install NextBlock
8308
+ SELECT tp.id, tp.language_id, 'text', jsonb_build_object('html_content',
8309
+ $$<p class='text-lg leading-8 text-slate-700 dark:text-slate-300'>NextBlock is an open-source, AI-native Next.js CMS built on Supabase — and installing it no longer involves config files, terminal wizards, or manual SQL. There are four ways to get running, and they all end in the same place: a browser <strong>setup wizard</strong> that connects your database, configures media storage, and creates your admin account for you. Pick the path that fits, follow the steps, and you will be publishing in minutes.</p>
8310
+
8311
+ <div class='grid gap-5 md:grid-cols-2 my-10'>
8312
+ <a href='#one-click-vercel' class='block rounded-[1.75rem] border border-blue-200 bg-blue-50/70 p-6 no-underline transition-shadow hover:shadow-lg dark:border-blue-500/20 dark:bg-blue-500/10'>
8313
+ <p class='mt-0 mb-0 text-xs font-semibold uppercase tracking-[0.22em] text-blue-700 dark:text-blue-200'>Fastest &middot; about 3 minutes</p>
8314
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>One-click deploy on Vercel</h3>
8315
+ <p class='mb-0 text-sm leading-6 text-slate-600 dark:text-slate-300'>A live production site with a managed database. No terminal, no environment variables, nothing to copy.</p>
8316
+ </a>
8317
+ <a href='#npm-create' class='block rounded-[1.75rem] border border-violet-200 bg-violet-50/70 p-6 no-underline transition-shadow hover:shadow-lg dark:border-violet-500/20 dark:bg-violet-500/10'>
8318
+ <p class='mt-0 mb-0 text-xs font-semibold uppercase tracking-[0.22em] text-violet-700 dark:text-violet-200'>Recommended for new projects</p>
8319
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>npm create nextblock</h3>
8320
+ <p class='mb-0 text-sm leading-6 text-slate-600 dark:text-slate-300'>Scaffold a standalone Next.js app with NextBlock built in, then finish setup in your browser.</p>
8321
+ </a>
8322
+ <a href='#git-clone' class='block rounded-[1.75rem] border border-emerald-200 bg-emerald-50/70 p-6 no-underline transition-shadow hover:shadow-lg dark:border-emerald-500/20 dark:bg-emerald-500/10'>
8323
+ <p class='mt-0 mb-0 text-xs font-semibold uppercase tracking-[0.22em] text-emerald-700 dark:text-emerald-200'>Full source code</p>
8324
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>Clone the repository</h3>
8325
+ <p class='mb-0 text-sm leading-6 text-slate-600 dark:text-slate-300'>Run the complete monorepo — the CMS, every package, and the docs. Made for contributors and platform teams.</p>
8326
+ </a>
8327
+ <a href='#docker' class='block rounded-[1.75rem] border border-amber-200 bg-amber-50/70 p-6 no-underline transition-shadow hover:shadow-lg dark:border-amber-500/20 dark:bg-amber-500/10'>
8328
+ <p class='mt-0 mb-0 text-xs font-semibold uppercase tracking-[0.22em] text-amber-700 dark:text-amber-200'>100% local &middot; no accounts</p>
8329
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>Self-hosted with Docker</h3>
8330
+ <p class='mb-0 text-sm leading-6 text-slate-600 dark:text-slate-300'>One command boots the entire stack on your machine — database, auth, storage, and the CMS. No cloud services at all.</p>
8331
+ </a>
8332
+ </div>
8333
+
8334
+ <p class='text-sm text-slate-500 dark:text-slate-400'>Not sure which to pick? If you want a live website with the least effort, choose <a href='#one-click-vercel'>Vercel</a>. If you want a local project to build on, choose <a href='#npm-create'>npm create nextblock</a>.</p>
8335
+
8336
+ <figure class='my-12 overflow-hidden rounded-[2rem] border border-slate-200/80 bg-slate-950 shadow-2xl dark:border-white/10'>
8337
+ <img src='/images/included.webp' alt='NextBlock CMS platform overview showing the block editor, CMS dashboard, and integrations included with every installation' class='w-full h-auto object-cover' />
8338
+ <figcaption class='border-t border-white/10 px-6 py-4 text-sm text-slate-300'>Whichever path you choose, you get the same block editor, the same CMS, and the same database schema.</figcaption>
8339
+ </figure>
8340
+
8341
+ <h2 id='one-click-vercel'>Option 1: One-Click Deploy on Vercel</h2>
8342
+ <p>The fastest way to get a production NextBlock site. One button creates your own copy of NextBlock on GitHub, provisions a managed Supabase database, and deploys the site — you never open a terminal or copy a single key.</p>
8343
+ <ol class='space-y-2'>
8344
+ <li><strong>Click Deploy to Vercel</strong> and sign in — Vercel clones NextBlock into a new repository you own.</li>
8345
+ <li><strong>Create the Supabase database</strong> when prompted: pick a name and a region. Vercel connects it to the project and injects the keys before the first build.</li>
8346
+ <li><strong>Open your new site</strong> once the build finishes. Every fresh instance takes you straight to the setup wizard.</li>
8347
+ <li><strong>Create your administrator account.</strong> It is confirmed instantly — no verification email — and you land in the CMS dashboard.</li>
8348
+ </ol>
8349
+ <div class='my-8'>
8350
+ <a href='https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnextblock-cms%2Fnextblock&amp;project-name=nextblock&amp;repository-name=nextblock&amp;stores=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22supabase%22%2C%22productSlug%22%3A%22supabase%22%7D%5D' target='_blank' rel='noopener' class='inline-flex items-center rounded-full bg-slate-900 px-6 py-3 text-sm font-semibold text-white no-underline shadow-lg hover:bg-slate-700 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200'>Deploy to Vercel &rarr;</a>
8351
+ </div>
8352
+ <div class='rounded-3xl border border-blue-200 bg-blue-50/80 p-6 my-8 dark:border-blue-500/20 dark:bg-blue-500/10'>
8353
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-blue-700 dark:text-blue-200'>Zero configuration</p>
8354
+ <p class='mt-3 mb-0 text-sm text-slate-700 dark:text-slate-200'>There are no environment variables to fill in. Media storage automatically uses your connected Supabase project, security secrets are derived for you, and database migrations run automatically on every production build. Adding a custom domain later? Set <code>NEXT_PUBLIC_URL</code> in your Vercel project and redeploy.</p>
8355
+ </div>
8356
+ <p>Your site also keeps itself current: the dashboard checklist includes a one-click <strong>Connect GitHub</strong> step that installs a daily workflow syncing your copy with the latest NextBlock release.</p>
8357
+
8358
+ <h2 id='npm-create'>Option 2: Scaffold a Project with npm create nextblock</h2>
8359
+ <p>The best starting point for building your own site. The CLI scaffolds a standalone Next.js application with NextBlock already wired in — no monorepo, no workspace tooling — and hands everything else to the browser wizard.</p>
8360
+ <p>Before you start, install <a href='https://nodejs.org' target='_blank' rel='noopener'>Node.js 20 or newer</a> (it includes npm), then create a free project at <a href='https://supabase.com' target='_blank' rel='noopener'>supabase.com</a> (or pick the CLI's Docker mode during creation and skip cloud accounts entirely).</p>
8361
+ <pre><code>npm create nextblock@latest my-site
8362
+ cd my-site
8363
+ npm run dev</code></pre>
8364
+ <p>Open <code>http://localhost:3000/setup</code> and let the wizard take over:</p>
8365
+ <ol class='space-y-2'>
8366
+ <li><strong>Connect Supabase</strong> — paste your project URL, publishable (anon) key, secret (service role) key, and a personal access token so the wizard can apply the database schema for you.</li>
8367
+ <li><strong>Choose media storage</strong> — plug in a Cloudflare R2 bucket for images and files, or leave it blank to use your connected Supabase project's storage.</li>
8368
+ <li><strong>Create your administrator</strong> — the wizard applies every migration, generates the app secrets, writes <code>.env.local</code>, creates your confirmed admin account, and signs you in. Restart <code>npm run dev</code> once afterwards so the fresh environment is baked into the app.</li>
8369
+ </ol>
8370
+ <div class='rounded-3xl border border-violet-200 bg-violet-50/80 p-6 my-8 dark:border-violet-500/20 dark:bg-violet-500/10'>
8371
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-violet-700 dark:text-violet-200'>Premium modules</p>
8372
+ <p class='mt-3 mb-0 text-sm text-slate-700 dark:text-slate-200'>Need a store? One command adds products, checkout, orders, and coupons — license-gated and ready when you are: <code>npx create-nextblock activate ecommerce</code></p>
8373
+ </div>
8374
+
8375
+ <h2 id='git-clone'>Option 3: Clone the Repository</h2>
8376
+ <p>Run the full Nx monorepo: the CMS application, every shared package, the CLI source, and the documentation. This is the path for contributors, plugin authors, and teams that customize the platform itself.</p>
8377
+ <pre><code>git clone https://github.com/nextblock-cms/nextblock.git
8378
+ cd nextblock
8379
+ npm install
8380
+ npx nx serve nextblock</code></pre>
8381
+ <p>Open <code>http://localhost:4200</code> — a fresh install redirects every page to <code>/setup</code>, where the same three-step wizard connects Supabase, configures storage, and creates your admin. It validates your keys, writes <code>.env.local</code> with generated secrets, and applies all migrations over the Supabase Management API — no Supabase CLI required.</p>
8382
+ <div class='rounded-3xl border border-emerald-200 bg-emerald-50/80 p-6 my-8 dark:border-emerald-500/20 dark:bg-emerald-500/10'>
8383
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-emerald-700 dark:text-emerald-200'>No terminal setup</p>
8384
+ <p class='mt-3 mb-0 text-sm text-slate-700 dark:text-slate-200'>Configuration moved entirely to the browser — there is no interactive terminal step anymore. When the wizard finishes you are signed in as the administrator; restart the dev server once afterwards so the fresh environment is baked into the app bundle.</p>
8385
+ </div>
8386
+
8387
+ <h2 id='docker'>Option 4: Self-Hosted with Docker</h2>
8388
+ <p>Everything runs on your machine: Supabase's Postgres and auth engines, a PostgREST API behind a Kong gateway, S3-compatible MinIO storage, and the CMS itself. No Supabase account, no Vercel, no email service — ideal for evaluations, air-gapped environments, and anyone who wants a fully self-hosted CMS with complete data ownership.</p>
8389
+ <p>With <a href='https://www.docker.com/products/docker-desktop/' target='_blank' rel='noopener'>Docker Desktop</a> installed and running:</p>
8390
+ <pre><code>git clone https://github.com/nextblock-cms/nextblock.git
8391
+ cd nextblock
8392
+ npm install
8393
+ npm run docker:setup</code></pre>
8394
+ <p>The command asks nothing: it generates secure keys, builds the stack, applies every migration, and starts the services. When it finishes, open <code>http://localhost:3000</code> — the setup wizard already has the database and MinIO storage wired up, so the only step left is creating your administrator (confirmed instantly, no email required).</p>
8395
+ <div class='rounded-3xl border border-amber-200 bg-amber-50/80 p-6 my-8 dark:border-amber-500/20 dark:bg-amber-500/10'>
8396
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-amber-700 dark:text-amber-200'>Day-2 commands</p>
8397
+ <pre class='mt-4 mb-0'><code># rebuild and restart the stack
8398
+ npm run docker:up
8399
+
8400
+ # stop the stack (your data persists in Docker volumes)
8401
+ npm run docker:down
8402
+
8403
+ # follow the application logs
8404
+ npm run docker:logs</code></pre>
8405
+ </div>
8406
+
8407
+ <h2 id='after-install'>After You Install: Your First 10 Minutes</h2>
8408
+ <p>Every path drops you at <code>/cms/dashboard</code>, signed in as the first administrator. A built-in onboarding checklist walks you through the rest:</p>
8409
+ <ul class='space-y-2'>
8410
+ <li><strong>Add your branding</strong> — upload your logo and set the site title.</li>
8411
+ <li><strong>Set your footer</strong> — copyright line and footer navigation.</li>
8412
+ <li><strong>Configure email (SMTP)</strong> — under Settings, so password resets and invitations can send.</li>
8413
+ <li><strong>Optional extras</strong> — connect analytics, enable bot protection, and (on Vercel) turn on automatic updates.</li>
8414
+ </ul>
8415
+ <p>From there, see how the platform fits together in <a href='/article/how-nextblock-works'>How NextBlock Works</a>, add a storefront with the <a href='/article/nextblock-commerce-guide'>Commerce guide</a>, or meet your AI copilot in the <a href='/article/nextblock-cortex-ai-guide'>Cortex AI guide</a>.</p>
8416
+
8417
+ <h2 id='faq'>Installation FAQ</h2>
8418
+ <h3>What do I need installed?</h3>
8419
+ <p>Nothing for the Vercel path — it runs entirely in the browser. For <code>npm create nextblock</code>: <a href='https://nodejs.org' target='_blank' rel='noopener'>Node.js 20+</a> (which includes npm). For the cloned repository: Node.js 20+ and git. For Docker: those plus Docker Desktop.</p>
8420
+ <h3>Is NextBlock free?</h3>
8421
+ <p>Yes — the core of NextBlock is a 100% free, open-source CMS (AGPL). Premium packages such as e-commerce and Cortex AI are optional and activate with a license key. Both Vercel and Supabase offer free tiers, so a starter site can run at no cost.</p>
8422
+ <h3>Do I need a Supabase account?</h3>
8423
+ <p>On Vercel, the database is created for you during the deploy. For <code>npm create nextblock</code> and the cloned repository you bring a free Supabase project. With Docker you need no cloud accounts at all.</p>
8424
+ <h3>Do I have to run migrations or SQL by hand?</h3>
8425
+ <p>No. The setup wizard, the Vercel build, and the Docker stack all apply the database schema automatically — and re-running is always safe.</p>
8426
+ <h3>Can I switch paths later?</h3>
8427
+ <p>Yes. Every path runs the same application and the same database schema, so you can prototype locally with Docker today and deploy to Vercel tomorrow. NextBlock deploys like any standard Next.js app.</p>
8428
+ <h3>How do I update NextBlock?</h3>
8429
+ <p>On Vercel, the Connect GitHub onboarding step enables a daily automatic sync with upstream. On a cloned repository, <code>git pull</code>, run <code>npm run db:migrate</code>, then restart (on production builds, pending migrations apply automatically). With Docker, pull the latest code and run <code>npm run docker:up</code>.</p>
8430
+
8431
+ <div class='rounded-[2rem] border border-slate-200/80 bg-slate-50 p-8 my-12 text-center dark:border-white/10 dark:bg-white/5'>
8432
+ <p class='mt-0 text-2xl font-semibold text-slate-900 dark:text-white'>Ready to launch?</p>
8433
+ <p class='text-sm text-slate-600 dark:text-slate-300'>Pick your path above, or jump straight to the fastest one.</p>
8434
+ <div class='mt-5 flex flex-wrap justify-center gap-3'>
8435
+ <a href='https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnextblock-cms%2Fnextblock&amp;project-name=nextblock&amp;repository-name=nextblock&amp;stores=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22supabase%22%2C%22productSlug%22%3A%22supabase%22%7D%5D' target='_blank' rel='noopener' class='inline-flex items-center rounded-full bg-slate-900 px-6 py-3 text-sm font-semibold text-white no-underline shadow-lg hover:bg-slate-700 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200'>Deploy on Vercel</a>
8436
+ <a href='https://github.com/nextblock-cms/nextblock' target='_blank' rel='noopener' class='inline-flex items-center rounded-full border border-slate-300 px-6 py-3 text-sm font-semibold text-slate-700 no-underline hover:border-slate-500 dark:border-white/20 dark:text-slate-200 dark:hover:border-white/50'>View on GitHub</a>
8437
+ </div>
8438
+ </div>$$
8439
+ ), 0 FROM target_posts tp WHERE tp.slug = 'how-to-setup-nextblock'
8440
+
8441
+ UNION ALL
8442
+
8443
+ -- FR: Installer NextBlock
8444
+ SELECT tp.id, tp.language_id, 'text', jsonb_build_object('html_content',
8445
+ $$<p class='text-lg leading-8 text-slate-700 dark:text-slate-300'>NextBlock est un CMS open source et natif IA, construit sur Next.js et Supabase — et son installation ne passe plus par des fichiers de configuration, des assistants en ligne de commande ou du SQL manuel. Il existe quatre façons de démarrer, et elles aboutissent toutes au même endroit : un <strong>assistant de configuration</strong> dans le navigateur qui connecte votre base de données, configure le stockage des médias et crée votre compte administrateur. Choisissez le chemin qui vous convient, suivez les étapes, et vous publierez en quelques minutes.</p>
8446
+
8447
+ <div class='grid gap-5 md:grid-cols-2 my-10'>
8448
+ <a href='#one-click-vercel' class='block rounded-[1.75rem] border border-blue-200 bg-blue-50/70 p-6 no-underline transition-shadow hover:shadow-lg dark:border-blue-500/20 dark:bg-blue-500/10'>
8449
+ <p class='mt-0 mb-0 text-xs font-semibold uppercase tracking-[0.22em] text-blue-700 dark:text-blue-200'>Le plus rapide &middot; environ 3 minutes</p>
8450
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>Déploiement Vercel en un clic</h3>
8451
+ <p class='mb-0 text-sm leading-6 text-slate-600 dark:text-slate-300'>Un site de production en ligne avec une base de données gérée. Pas de terminal, pas de variables d'environnement, rien à copier.</p>
8452
+ </a>
8453
+ <a href='#npm-create' class='block rounded-[1.75rem] border border-violet-200 bg-violet-50/70 p-6 no-underline transition-shadow hover:shadow-lg dark:border-violet-500/20 dark:bg-violet-500/10'>
8454
+ <p class='mt-0 mb-0 text-xs font-semibold uppercase tracking-[0.22em] text-violet-700 dark:text-violet-200'>Recommandé pour les nouveaux projets</p>
8455
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>npm create nextblock</h3>
8456
+ <p class='mb-0 text-sm leading-6 text-slate-600 dark:text-slate-300'>Générez une app Next.js autonome avec NextBlock intégré, puis terminez la configuration dans votre navigateur.</p>
8457
+ </a>
8458
+ <a href='#git-clone' class='block rounded-[1.75rem] border border-emerald-200 bg-emerald-50/70 p-6 no-underline transition-shadow hover:shadow-lg dark:border-emerald-500/20 dark:bg-emerald-500/10'>
8459
+ <p class='mt-0 mb-0 text-xs font-semibold uppercase tracking-[0.22em] text-emerald-700 dark:text-emerald-200'>Code source complet</p>
8460
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>Cloner le dépôt</h3>
8461
+ <p class='mb-0 text-sm leading-6 text-slate-600 dark:text-slate-300'>Faites tourner le monorepo complet — le CMS, tous les packages et la documentation. Conçu pour les contributeurs et les équipes plateforme.</p>
8462
+ </a>
8463
+ <a href='#docker' class='block rounded-[1.75rem] border border-amber-200 bg-amber-50/70 p-6 no-underline transition-shadow hover:shadow-lg dark:border-amber-500/20 dark:bg-amber-500/10'>
8464
+ <p class='mt-0 mb-0 text-xs font-semibold uppercase tracking-[0.22em] text-amber-700 dark:text-amber-200'>100 % local &middot; aucun compte</p>
8465
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>Auto-hébergé avec Docker</h3>
8466
+ <p class='mb-0 text-sm leading-6 text-slate-600 dark:text-slate-300'>Une seule commande démarre toute la pile sur votre machine — base de données, auth, stockage et le CMS. Aucun service cloud.</p>
8467
+ </a>
8468
+ </div>
8469
+
8470
+ <p class='text-sm text-slate-500 dark:text-slate-400'>Vous hésitez ? Pour un site en ligne avec le minimum d'effort, choisissez <a href='#one-click-vercel'>Vercel</a>. Pour un projet local à personnaliser, choisissez <a href='#npm-create'>npm create nextblock</a>.</p>
8471
+
8472
+ <figure class='my-12 overflow-hidden rounded-[2rem] border border-slate-200/80 bg-slate-950 shadow-2xl dark:border-white/10'>
8473
+ <img src='/images/included.webp' alt='Aperçu de la plateforme NextBlock : éditeur de blocs, tableau de bord CMS et intégrations incluses dans chaque installation' class='w-full h-auto object-cover' />
8474
+ <figcaption class='border-t border-white/10 px-6 py-4 text-sm text-slate-300'>Quel que soit le chemin choisi, vous obtenez le même éditeur de blocs, le même CMS et le même schéma de base de données.</figcaption>
8475
+ </figure>
8476
+
8477
+ <h2 id='one-click-vercel'>Option 1 : Déploiement Vercel en un clic</h2>
8478
+ <p>Le moyen le plus rapide d'obtenir un site NextBlock en production. Un seul bouton crée votre propre copie de NextBlock sur GitHub, provisionne une base de données Supabase gérée et déploie le site — sans jamais ouvrir un terminal ni copier la moindre clé.</p>
8479
+ <ol class='space-y-2'>
8480
+ <li><strong>Cliquez sur Deploy to Vercel</strong> et connectez-vous — Vercel clone NextBlock dans un nouveau dépôt qui vous appartient.</li>
8481
+ <li><strong>Créez la base de données Supabase</strong> quand on vous le demande : choisissez un nom et une région. Vercel la connecte au projet et injecte les clés avant le premier build.</li>
8482
+ <li><strong>Ouvrez votre nouveau site</strong> une fois le build terminé. Toute nouvelle instance vous amène directement à l'assistant de configuration.</li>
8483
+ <li><strong>Créez votre compte administrateur.</strong> Il est confirmé instantanément — aucun email de vérification — et vous arrivez dans le tableau de bord du CMS.</li>
8484
+ </ol>
8485
+ <div class='my-8'>
8486
+ <a href='https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnextblock-cms%2Fnextblock&amp;project-name=nextblock&amp;repository-name=nextblock&amp;stores=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22supabase%22%2C%22productSlug%22%3A%22supabase%22%7D%5D' target='_blank' rel='noopener' class='inline-flex items-center rounded-full bg-slate-900 px-6 py-3 text-sm font-semibold text-white no-underline shadow-lg hover:bg-slate-700 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200'>Déployer sur Vercel &rarr;</a>
8487
+ </div>
8488
+ <div class='rounded-3xl border border-blue-200 bg-blue-50/80 p-6 my-8 dark:border-blue-500/20 dark:bg-blue-500/10'>
8489
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-blue-700 dark:text-blue-200'>Zéro configuration</p>
8490
+ <p class='mt-3 mb-0 text-sm text-slate-700 dark:text-slate-200'>Aucune variable d'environnement à remplir. Le stockage des médias utilise automatiquement votre projet Supabase connecté, les secrets de sécurité sont dérivés pour vous, et les migrations de base de données s'exécutent automatiquement à chaque build de production. Un domaine personnalisé plus tard ? Définissez <code>NEXT_PUBLIC_URL</code> dans votre projet Vercel et redéployez.</p>
8491
+ </div>
8492
+ <p>Par ailleurs, votre site reste à jour tout seul : la checklist du tableau de bord inclut une étape <strong>Connect GitHub</strong> en un clic qui installe un workflow quotidien synchronisant votre copie avec la dernière version de NextBlock.</p>
8493
+
8494
+ <h2 id='npm-create'>Option 2 : Créer un projet avec npm create nextblock</h2>
8495
+ <p>Le meilleur point de départ pour construire votre propre site. Le CLI génère une application Next.js autonome avec NextBlock déjà intégré — sans monorepo ni outillage de workspace — et confie tout le reste à l'assistant dans le navigateur.</p>
8496
+ <p>Avant de commencer, installez <a href='https://nodejs.org' target='_blank' rel='noopener'>Node.js 20 ou plus récent</a> (npm inclus), puis créez un projet gratuit sur <a href='https://supabase.com' target='_blank' rel='noopener'>supabase.com</a> (ou choisissez le mode Docker du CLI pendant la création et passez-vous entièrement de comptes cloud).</p>
8497
+ <pre><code>npm create nextblock@latest mon-site
8498
+ cd mon-site
8499
+ npm run dev</code></pre>
8500
+ <p>Ouvrez <code>http://localhost:3000/setup</code> et laissez l'assistant faire le travail :</p>
8501
+ <ol class='space-y-2'>
8502
+ <li><strong>Connectez Supabase</strong> — collez l'URL du projet, la clé publiable (anon), la clé secrète (service role) et un jeton d'accès personnel pour que l'assistant applique le schéma de base de données à votre place.</li>
8503
+ <li><strong>Choisissez le stockage des médias</strong> — branchez un bucket Cloudflare R2 pour les images et les fichiers, ou laissez les champs vides pour utiliser le stockage de votre projet Supabase.</li>
8504
+ <li><strong>Créez votre administrateur</strong> — l'assistant applique toutes les migrations, génère les secrets de l'application, écrit <code>.env.local</code>, crée votre compte admin confirmé et vous connecte. Redémarrez ensuite <code>npm run dev</code> une fois pour que le nouvel environnement soit intégré à l'application.</li>
8505
+ </ol>
8506
+ <div class='rounded-3xl border border-violet-200 bg-violet-50/80 p-6 my-8 dark:border-violet-500/20 dark:bg-violet-500/10'>
8507
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-violet-700 dark:text-violet-200'>Modules premium</p>
8508
+ <p class='mt-3 mb-0 text-sm text-slate-700 dark:text-slate-200'>Besoin d'une boutique ? Une seule commande ajoute produits, paiement, commandes et coupons — activés par clé de licence, prêts quand vous l'êtes : <code>npx create-nextblock activate ecommerce</code></p>
8509
+ </div>
8510
+
8511
+ <h2 id='git-clone'>Option 3 : Cloner le dépôt</h2>
8512
+ <p>Faites tourner le monorepo Nx complet : l'application CMS, tous les packages partagés, le code du CLI et la documentation. C'est le chemin des contributeurs, des auteurs de plugins et des équipes qui personnalisent la plateforme elle-même.</p>
8513
+ <pre><code>git clone https://github.com/nextblock-cms/nextblock.git
8514
+ cd nextblock
8515
+ npm install
8516
+ npx nx serve nextblock</code></pre>
8517
+ <p>Ouvrez <code>http://localhost:4200</code> — une nouvelle installation redirige chaque page vers <code>/setup</code>, où le même assistant en trois étapes connecte Supabase, configure le stockage et crée votre admin. Il valide vos clés, écrit <code>.env.local</code> avec des secrets générés, et applique toutes les migrations via l'API de management Supabase — sans CLI Supabase.</p>
8518
+ <div class='rounded-3xl border border-emerald-200 bg-emerald-50/80 p-6 my-8 dark:border-emerald-500/20 dark:bg-emerald-500/10'>
8519
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-emerald-700 dark:text-emerald-200'>Zéro configuration dans le terminal</p>
8520
+ <p class='mt-3 mb-0 text-sm text-slate-700 dark:text-slate-200'>La configuration se fait entièrement dans le navigateur — il n'y a plus d'étape interactive en ligne de commande. Quand l'assistant termine, vous êtes connecté en administrateur ; redémarrez ensuite le serveur de développement une fois pour que le nouvel environnement soit intégré au bundle de l'application.</p>
8521
+ </div>
8522
+
8523
+ <h2 id='docker'>Option 4 : Auto-hébergé avec Docker</h2>
8524
+ <p>Tout tourne sur votre machine : les moteurs Postgres et auth de Supabase, une API PostgREST derrière une passerelle Kong, un stockage MinIO compatible S3 et le CMS lui-même. Pas de compte Supabase, pas de Vercel, pas de service d'email — idéal pour les évaluations, les environnements isolés et la pleine propriété de vos données.</p>
8525
+ <p>Avec <a href='https://www.docker.com/products/docker-desktop/' target='_blank' rel='noopener'>Docker Desktop</a> installé et démarré :</p>
8526
+ <pre><code>git clone https://github.com/nextblock-cms/nextblock.git
8527
+ cd nextblock
8528
+ npm install
8529
+ npm run docker:setup</code></pre>
8530
+ <p>La commande ne pose aucune question : elle génère des clés sécurisées, construit la pile, applique toutes les migrations et démarre les services. Quand elle se termine, ouvrez <code>http://localhost:3000</code> — la base de données et le stockage MinIO sont déjà connectés dans l'assistant de configuration, il ne reste qu'à créer votre administrateur (confirmé instantanément, sans email).</p>
8531
+ <div class='rounded-3xl border border-amber-200 bg-amber-50/80 p-6 my-8 dark:border-amber-500/20 dark:bg-amber-500/10'>
8532
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-amber-700 dark:text-amber-200'>Commandes du quotidien</p>
8533
+ <pre class='mt-4 mb-0'><code># reconstruire et redémarrer la pile
8534
+ npm run docker:up
8535
+
8536
+ # arrêter la pile (vos données persistent dans les volumes Docker)
8537
+ npm run docker:down
8538
+
8539
+ # suivre les logs de l'application
8540
+ npm run docker:logs</code></pre>
8541
+ </div>
8542
+
8543
+ <h2 id='after-install'>Après l'installation : vos 10 premières minutes</h2>
8544
+ <p>Chaque chemin vous dépose sur <code>/cms/dashboard</code>, connecté en tant que premier administrateur. Une checklist de démarrage intégrée vous guide pour la suite :</p>
8545
+ <ul class='space-y-2'>
8546
+ <li><strong>Ajoutez votre identité visuelle</strong> — téléversez votre logo et définissez le titre du site.</li>
8547
+ <li><strong>Réglez votre pied de page</strong> — mention de copyright et navigation du pied de page.</li>
8548
+ <li><strong>Configurez l'email (SMTP)</strong> — dans les réglages, pour que les réinitialisations de mot de passe et les invitations partent bien.</li>
8549
+ <li><strong>Extras optionnels</strong> — connectez vos outils d'analytics, activez la protection anti-bots et (sur Vercel) les mises à jour automatiques.</li>
8550
+ </ul>
8551
+ <p>Ensuite, découvrez comment la plateforme s'articule dans <a href='/article/comment-nextblock-fonctionne'>Comment NextBlock fonctionne</a>, ou ajoutez une boutique avec le <a href='/article/guide-commerce-nextblock'>guide Commerce</a>.</p>
8552
+
8553
+ <h2 id='faq'>FAQ d'installation</h2>
8554
+ <h3>Que dois-je installer ?</h3>
8555
+ <p>Rien pour le chemin Vercel — tout se passe dans le navigateur. Pour <code>npm create nextblock</code> : <a href='https://nodejs.org' target='_blank' rel='noopener'>Node.js 20+</a> (npm inclus). Pour le dépôt cloné : Node.js 20+ et git. Pour Docker : ajoutez Docker Desktop.</p>
8556
+ <h3>NextBlock est-il gratuit ?</h3>
8557
+ <p>Oui — le cœur du CMS est 100 % gratuit et open source (AGPL). Les packages premium comme l'e-commerce et Cortex AI sont optionnels et s'activent avec une clé de licence. Vercel et Supabase proposent chacun une offre gratuite : un site de départ peut donc tourner sans frais.</p>
8558
+ <h3>Ai-je besoin d'un compte Supabase ?</h3>
8559
+ <p>Sur Vercel, la base de données est créée pour vous pendant le déploiement. Pour <code>npm create nextblock</code> et le dépôt cloné, il vous faut un projet Supabase gratuit. Avec Docker, aucun compte cloud n'est nécessaire.</p>
8560
+ <h3>Dois-je exécuter des migrations ou du SQL à la main ?</h3>
8561
+ <p>Non. L'assistant de configuration, le build Vercel et la pile Docker appliquent tous le schéma de base de données automatiquement — et relancer l'opération est toujours sans risque.</p>
8562
+ <h3>Puis-je changer de chemin plus tard ?</h3>
8563
+ <p>Oui. Chaque chemin exécute la même application et le même schéma de base de données : vous pouvez prototyper en local avec Docker aujourd'hui et déployer sur Vercel demain. NextBlock se déploie comme n'importe quelle app Next.js.</p>
8564
+ <h3>Comment mettre à jour NextBlock ?</h3>
8565
+ <p>Sur Vercel, l'étape Connect GitHub de la checklist active une synchronisation quotidienne automatique. Sur un dépôt cloné, <code>git pull</code>, lancez <code>npm run db:migrate</code>, puis redémarrez (lors des builds de production, les migrations en attente s'appliquent automatiquement). Avec Docker, récupérez le dernier code et lancez <code>npm run docker:up</code>.</p>
8566
+
8567
+ <div class='rounded-[2rem] border border-slate-200/80 bg-slate-50 p-8 my-12 text-center dark:border-white/10 dark:bg-white/5'>
8568
+ <p class='mt-0 text-2xl font-semibold text-slate-900 dark:text-white'>Prêt à vous lancer ?</p>
8569
+ <p class='text-sm text-slate-600 dark:text-slate-300'>Choisissez votre chemin ci-dessus, ou passez directement au plus rapide.</p>
8570
+ <div class='mt-5 flex flex-wrap justify-center gap-3'>
8571
+ <a href='https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnextblock-cms%2Fnextblock&amp;project-name=nextblock&amp;repository-name=nextblock&amp;stores=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22supabase%22%2C%22productSlug%22%3A%22supabase%22%7D%5D' target='_blank' rel='noopener' class='inline-flex items-center rounded-full bg-slate-900 px-6 py-3 text-sm font-semibold text-white no-underline shadow-lg hover:bg-slate-700 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200'>Déployer sur Vercel</a>
8572
+ <a href='https://github.com/nextblock-cms/nextblock' target='_blank' rel='noopener' class='inline-flex items-center rounded-full border border-slate-300 px-6 py-3 text-sm font-semibold text-slate-700 no-underline hover:border-slate-500 dark:border-white/20 dark:text-slate-200 dark:hover:border-white/50'>Voir sur GitHub</a>
8573
+ </div>
8574
+ </div>$$
8575
+ ), 0 FROM target_posts tp WHERE tp.slug = 'comment-configurer-nextblock';
8576
+
8577
+
8578
+ -- >>> FROM: 00000000000038_seed_consent_banner_translations.sql <<<
8579
+ -- 00000000000038_seed_consent_banner_translations.sql
8580
+ -- Consent banner UI copy (EN + FR). Forward-only and idempotent.
8581
+
8582
+ INSERT INTO public.translations (key, translations)
8583
+ VALUES
8584
+ (
8585
+ 'privacy.consent.aria_label',
8586
+ jsonb_build_object(
8587
+ 'en', 'Privacy consent',
8588
+ 'fr', 'Consentement à la confidentialité'
8589
+ )
8590
+ ),
8591
+ (
8592
+ 'privacy.consent.title',
8593
+ jsonb_build_object(
8594
+ 'en', 'We value your privacy',
8595
+ 'fr', 'Nous respectons votre vie privée'
8596
+ )
8597
+ ),
8598
+ (
8599
+ 'privacy.consent.description_before_policy_link',
8600
+ jsonb_build_object(
8601
+ 'en', 'We use only essential cookies by default. With your consent we also use analytics to improve the site. See our',
8602
+ 'fr', 'Nous utilisons seulement les témoins essentiels par défaut. Avec votre consentement, nous utilisons aussi l''analytique pour améliorer le site. Consultez notre'
8603
+ )
8604
+ ),
8605
+ (
8606
+ 'privacy.consent.privacy_policy_link',
8607
+ jsonb_build_object(
8608
+ 'en', 'Privacy Policy',
8609
+ 'fr', 'politique de confidentialité'
8610
+ )
8611
+ ),
8612
+ (
8613
+ 'privacy.consent.description_after_policy_link',
8614
+ jsonb_build_object(
8615
+ 'en', '.',
8616
+ 'fr', '.'
8617
+ )
8618
+ ),
8619
+ (
8620
+ 'privacy.consent.privacy_policy_href',
8621
+ jsonb_build_object(
8622
+ 'en', '/privacy-policy',
8623
+ 'fr', '/politique-de-confidentialite'
8624
+ )
8625
+ ),
8626
+ (
8627
+ 'privacy.consent.necessary_label',
8628
+ jsonb_build_object(
8629
+ 'en', 'Necessary',
8630
+ 'fr', 'Nécessaires'
8631
+ )
8632
+ ),
8633
+ (
8634
+ 'privacy.consent.necessary_help',
8635
+ jsonb_build_object(
8636
+ 'en', 'Always on',
8637
+ 'fr', 'Toujours actifs'
8638
+ )
8639
+ ),
8640
+ (
8641
+ 'privacy.consent.analytics_label',
8642
+ jsonb_build_object(
8643
+ 'en', 'Analytics',
8644
+ 'fr', 'Analytique'
8645
+ )
8646
+ ),
8647
+ (
8648
+ 'privacy.consent.analytics_help',
8649
+ jsonb_build_object(
8650
+ 'en', 'Usage insights',
8651
+ 'fr', 'Statistiques d''utilisation'
8652
+ )
8653
+ ),
8654
+ (
8655
+ 'privacy.consent.marketing_label',
8656
+ jsonb_build_object(
8657
+ 'en', 'Marketing',
8658
+ 'fr', 'Marketing'
8659
+ )
8660
+ ),
8661
+ (
8662
+ 'privacy.consent.marketing_help',
8663
+ jsonb_build_object(
8664
+ 'en', 'Personalized content',
8665
+ 'fr', 'Contenu personnalisé'
8666
+ )
8667
+ ),
8668
+ (
8669
+ 'privacy.consent.save_choices',
8670
+ jsonb_build_object(
8671
+ 'en', 'Save choices',
8672
+ 'fr', 'Enregistrer mes choix'
8673
+ )
8674
+ ),
8675
+ (
8676
+ 'privacy.consent.accept_all',
8677
+ jsonb_build_object(
8678
+ 'en', 'Accept all',
8679
+ 'fr', 'Tout accepter'
8680
+ )
8681
+ ),
8682
+ (
8683
+ 'privacy.consent.reject_all',
8684
+ jsonb_build_object(
8685
+ 'en', 'Reject all',
8686
+ 'fr', 'Tout refuser'
8687
+ )
8688
+ ),
8689
+ (
8690
+ 'privacy.consent.hide_options',
8691
+ jsonb_build_object(
8692
+ 'en', 'Hide options',
8693
+ 'fr', 'Masquer les options'
8694
+ )
8695
+ ),
8696
+ (
8697
+ 'privacy.consent.manage_options',
8698
+ jsonb_build_object(
8699
+ 'en', 'Manage options',
8700
+ 'fr', 'Gérer les options'
8701
+ )
8702
+ )
8703
+ ON CONFLICT (key) DO UPDATE
8704
+ SET translations = public.translations.translations || EXCLUDED.translations;
8705
+
8706
+
8707
+ -- >>> FROM: 00000000000039_setup_interactions.sql <<<
8708
+ -- 00000000000039_setup_interactions.sql
8709
+ -- Migration establishing Product Reviews and Post Comments schema.
8710
+
8711
+ -- 1. Create Enums
8712
+ CREATE TYPE public.interaction_type AS ENUM ('review', 'comment');
8713
+ CREATE TYPE public.approval_status AS ENUM ('pending', 'approved', 'denied');
8714
+
8715
+ -- 2. Alter Products Table to support aggregations
8716
+ ALTER TABLE public.products
8717
+ ADD COLUMN average_rating numeric(3,2) NOT NULL DEFAULT 0.00,
8718
+ ADD COLUMN total_reviews integer NOT NULL DEFAULT 0;
8719
+
8720
+ COMMENT ON COLUMN public.products.average_rating IS 'Aggregated average 1-5 rating of approved reviews.';
8721
+ COMMENT ON COLUMN public.products.total_reviews IS 'Total count of approved reviews for this product.';
8722
+
8723
+ -- 3. Create Interactions Table
8724
+ CREATE TABLE public.cms_interactions (
8725
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
8726
+ type public.interaction_type NOT NULL,
8727
+ status public.approval_status NOT NULL DEFAULT 'pending',
8728
+ content text NOT NULL,
8729
+ rating integer,
8730
+ user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
8731
+ product_id uuid REFERENCES public.products(id) ON DELETE CASCADE,
8732
+ post_id bigint REFERENCES public.posts(id) ON DELETE CASCADE,
8733
+ reactions jsonb NOT NULL DEFAULT '{}'::jsonb,
8734
+ created_at timestamptz NOT NULL DEFAULT now(),
8735
+ updated_at timestamptz NOT NULL DEFAULT now(),
8736
+
8737
+ -- Ensure polymorphism is respected (either product or post)
8738
+ CONSTRAINT check_product_or_post CHECK (
8739
+ (product_id IS NOT NULL AND post_id IS NULL)
8740
+ OR (post_id IS NOT NULL AND product_id IS NULL)
8741
+ ),
8742
+
8743
+ -- Ensure rating matches business rules
8744
+ CONSTRAINT check_rating_only_for_review CHECK (
8745
+ (type = 'review' AND rating IS NOT NULL AND rating >= 1 AND rating <= 5)
8746
+ OR (type = 'comment' AND rating IS NULL)
8747
+ )
8748
+ );
8749
+
8750
+ COMMENT ON TABLE public.cms_interactions IS 'Stores user-submitted product reviews and blog post comments.';
8751
+ COMMENT ON COLUMN public.cms_interactions.rating IS 'Star rating 1-5, only populated for product reviews.';
8752
+ COMMENT ON COLUMN public.cms_interactions.reactions IS 'JSONB structure tracking counts of reactions (likes, etc.).';
8753
+
8754
+ -- 4. Create rating aggregation trigger function
8755
+ CREATE OR REPLACE FUNCTION public.update_product_ratings()
8756
+ RETURNS TRIGGER AS $$
8757
+ DECLARE
8758
+ v_product_id uuid;
8759
+ v_avg numeric(3,2);
8760
+ v_count integer;
8761
+ BEGIN
8762
+ -- Determine which product_id we need to update
8763
+ IF TG_OP = 'DELETE' THEN
8764
+ v_product_id := OLD.product_id;
8765
+ ELSE
8766
+ v_product_id := NEW.product_id;
8767
+ END IF;
8768
+
8769
+ IF v_product_id IS NOT NULL THEN
8770
+ -- Calculate average and count of approved reviews for this product
8771
+ SELECT COALESCE(avg(rating), 0.00), count(*)
8772
+ INTO v_avg, v_count
8773
+ FROM public.cms_interactions
8774
+ WHERE product_id = v_product_id
8775
+ AND type = 'review'
8776
+ AND status = 'approved';
8777
+
8778
+ -- Update products table
8779
+ UPDATE public.products
8780
+ SET average_rating = ROUND(COALESCE(v_avg, 0.00), 2),
8781
+ total_reviews = v_count
8782
+ WHERE id = v_product_id;
8783
+ END IF;
8784
+
8785
+ -- Handle old product_id if it changed on UPDATE (e.g. transfer of reviews)
8786
+ IF TG_OP = 'UPDATE' AND OLD.product_id IS NOT NULL AND OLD.product_id <> NEW.product_id THEN
8787
+ SELECT COALESCE(avg(rating), 0.00), count(*)
8788
+ INTO v_avg, v_count
8789
+ FROM public.cms_interactions
8790
+ WHERE product_id = OLD.product_id
8791
+ AND type = 'review'
8792
+ AND status = 'approved';
8793
+
8794
+ UPDATE public.products
8795
+ SET average_rating = ROUND(COALESCE(v_avg, 0.00), 2),
8796
+ total_reviews = v_count
8797
+ WHERE id = OLD.product_id;
8798
+ END IF;
8799
+
8800
+ RETURN NULL;
8801
+ END;
8802
+ $$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = public;
8803
+
8804
+ -- 5. Attach trigger to interactions table
8805
+ CREATE TRIGGER trigger_update_product_ratings
8806
+ AFTER INSERT OR UPDATE OR DELETE
8807
+ ON public.cms_interactions
8808
+ FOR EACH ROW
8809
+ EXECUTE FUNCTION public.update_product_ratings();
8810
+
8811
+ -- 6. Attach update_at timestamp trigger
8812
+ CREATE TRIGGER set_updated_at
8813
+ BEFORE UPDATE ON public.cms_interactions
8814
+ FOR EACH ROW
8815
+ EXECUTE FUNCTION public.set_current_timestamp_updated_at();
8816
+
8817
+ -- 7. Enable RLS and define policies
8818
+ ALTER TABLE public.cms_interactions ENABLE ROW LEVEL SECURITY;
8819
+
8820
+ CREATE POLICY cms_interactions_read_policy
8821
+ ON public.cms_interactions
8822
+ FOR SELECT
8823
+ TO public
8824
+ USING (
8825
+ status = 'approved'
8826
+ OR ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
8827
+ );
8828
+
8829
+ CREATE POLICY cms_interactions_insert_policy
8830
+ ON public.cms_interactions
8831
+ FOR INSERT
8832
+ TO authenticated
8833
+ WITH CHECK (
8834
+ auth.uid() = user_id
8835
+ AND (status = 'pending' OR (SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
8836
+ );
8837
+
8838
+ CREATE POLICY cms_interactions_update_policy
8839
+ ON public.cms_interactions
8840
+ FOR UPDATE
8841
+ TO authenticated
8842
+ USING (
8843
+ ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
8844
+ )
8845
+ WITH CHECK (
8846
+ ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
8847
+ );
8848
+
8849
+ CREATE POLICY cms_interactions_delete_policy
8850
+ ON public.cms_interactions
8851
+ FOR DELETE
8852
+ TO authenticated
8853
+ USING (
8854
+ ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
8855
+ );
8856
+
8857
+ -- 8. Grant Table Permissions
8858
+ GRANT SELECT ON public.cms_interactions TO anon;
8859
+ GRANT ALL ON public.cms_interactions TO authenticated;
8860
+ GRANT ALL ON public.cms_interactions TO service_role;
8861
+
8862
+
8863
+ -- >>> FROM: 00000000000040_add_profiles_fk_to_interactions.sql <<<
8864
+ -- 00000000000040_add_profiles_fk_to_interactions.sql
8865
+ -- Alter cms_interactions.user_id to reference public.profiles directly for PostgREST joins.
8866
+
8867
+ ALTER TABLE public.cms_interactions
8868
+ DROP CONSTRAINT IF EXISTS cms_interactions_user_id_fkey,
8869
+ ADD CONSTRAINT cms_interactions_user_id_profiles_fkey FOREIGN KEY (user_id) REFERENCES public.profiles(id) ON DELETE CASCADE;
8870
+
8871
+ COMMENT ON COLUMN public.cms_interactions.user_id IS 'References public.profiles.id';
8872
+
8873
+
8874
+ -- >>> FROM: 00000000000041_seed_interactions_translations.sql <<<
8875
+ -- 00000000000041_seed_interactions_translations.sql
8876
+ -- Adds translation strings for Product Reviews and Post Comments storefront modules.
8877
+
8878
+
8879
+ INSERT INTO public.translations (key, translations)
8880
+ VALUES
8881
+ (
8882
+ 'reviews.customer_reviews',
8883
+ '{"en": "Customer Reviews", "fr": "Avis clients"}'::jsonb
8884
+ ),
8885
+ (
8886
+ 'reviews.share_thoughts',
8887
+ '{"en": "Share your thoughts and experience with this product.", "fr": "Partagez vos impressions et votre expérience avec ce produit."}'::jsonb
8888
+ ),
8889
+ (
8890
+ 'reviews.write_review',
8891
+ '{"en": "Write a Review", "fr": "Rédiger un avis"}'::jsonb
8892
+ ),
8893
+ (
8894
+ 'reviews.cancel_review',
8895
+ '{"en": "Cancel Review", "fr": "Annuler l''avis"}'::jsonb
8896
+ ),
8897
+ (
8898
+ 'reviews.login_to_write',
8899
+ '{"en": "Please log in to write a review.", "fr": "Veuillez vous connecter pour rédiger un avis."}'::jsonb
8900
+ ),
8901
+ (
8902
+ 'reviews.write_your_review',
8903
+ '{"en": "Write your review", "fr": "Rédigez votre avis"}'::jsonb
8904
+ ),
8905
+ (
8906
+ 'reviews.rating',
8907
+ '{"en": "Rating", "fr": "Note"}'::jsonb
8908
+ ),
8909
+ (
8910
+ 'reviews.description',
8911
+ '{"en": "Review Description", "fr": "Description de l''avis"}'::jsonb
8912
+ ),
8913
+ (
8914
+ 'reviews.description_placeholder',
8915
+ '{"en": "What did you like or dislike? How does it perform?", "fr": "Qu''avez-vous aimé ou déploré ? Comment se comporte-t-il ?"}'::jsonb
8916
+ ),
8917
+ (
8918
+ 'reviews.submit_review',
8919
+ '{"en": "Submit Review", "fr": "Soumettre l''avis"}'::jsonb
8920
+ ),
8921
+ (
8922
+ 'reviews.submitting',
8923
+ '{"en": "Submitting...", "fr": "Envoi en cours..."}'::jsonb
8924
+ ),
8925
+ (
8926
+ 'reviews.success_pending',
8927
+ '{"en": "Your review has been submitted successfully and is pending moderation.", "fr": "Votre avis a été soumis avec succès et est en attente de modération."}'::jsonb
8928
+ ),
8929
+ (
8930
+ 'reviews.cancel',
8931
+ '{"en": "Cancel", "fr": "Annuler"}'::jsonb
8932
+ ),
8933
+ (
8934
+ 'reviews.helpful',
8935
+ '{"en": "Helpful", "fr": "Utile"}'::jsonb
8936
+ ),
8937
+ (
8938
+ 'reviews.no_reviews',
8939
+ '{"en": "No reviews yet", "fr": "Aucun avis pour le moment"}'::jsonb
8940
+ ),
8941
+ (
8942
+ 'reviews.be_the_first',
8943
+ '{"en": "There are no reviews for this product yet. Be the first to write one!", "fr": "Il n''y a pas encore d''avis sur ce produit. Soyez le premier à en rédiger un !"}'::jsonb
8944
+ ),
8945
+ (
8946
+ 'reviews.load_more',
8947
+ '{"en": "Load More Reviews", "fr": "Charger plus d''avis"}'::jsonb
8948
+ ),
8949
+ (
8950
+ 'reviews.review_count_one',
8951
+ '{"en": "{count} review", "fr": "{count} avis"}'::jsonb
8952
+ ),
8953
+ (
8954
+ 'reviews.review_count_other',
8955
+ '{"en": "{count} reviews", "fr": "{count} avis"}'::jsonb
8956
+ ),
8957
+ (
8958
+ 'comments.discussion',
8959
+ '{"en": "Discussion & Comments", "fr": "Discussion & Commentaires"}'::jsonb
8960
+ ),
8961
+ (
8962
+ 'comments.join_conversation',
8963
+ '{"en": "Join the conversation and express your thoughts.", "fr": "Rejoignez la conversation et exprimez vos pensées."}'::jsonb
8964
+ ),
8965
+ (
8966
+ 'comments.write_comment',
8967
+ '{"en": "Write a Comment", "fr": "Écrire un commentaire"}'::jsonb
8968
+ ),
8969
+ (
8970
+ 'comments.cancel_comment',
8971
+ '{"en": "Cancel Comment", "fr": "Annuler le commentaire"}'::jsonb
8972
+ ),
8973
+ (
8974
+ 'comments.login_to_write',
8975
+ '{"en": "Please log in to write a comment.", "fr": "Veuillez vous connecter pour écrire un commentaire."}'::jsonb
8976
+ ),
8977
+ (
8978
+ 'comments.join_discussion',
8979
+ '{"en": "Join the Discussion", "fr": "Rejoindre la discussion"}'::jsonb
8980
+ ),
8981
+ (
8982
+ 'comments.your_message',
8983
+ '{"en": "Your Message", "fr": "Votre message"}'::jsonb
8984
+ ),
8985
+ (
8986
+ 'comments.message_placeholder',
8987
+ '{"en": "What are your thoughts on this article?", "fr": "Quelles sont vos pensées sur cet article ?"}'::jsonb
8988
+ ),
8989
+ (
8990
+ 'comments.post_comment',
8991
+ '{"en": "Post Comment", "fr": "Publier le commentaire"}'::jsonb
8992
+ ),
8993
+ (
8994
+ 'comments.success_pending',
8995
+ '{"en": "Your comment has been submitted successfully and is pending moderation.", "fr": "Votre commentaire a été soumis avec succès et est en attente de modération."}'::jsonb
8996
+ ),
8997
+ (
8998
+ 'comments.like',
8999
+ '{"en": "Like", "fr": "J''aime"}'::jsonb
9000
+ ),
9001
+ (
9002
+ 'comments.no_comments',
9003
+ '{"en": "No comments yet", "fr": "Aucun commentaire pour le moment"}'::jsonb
9004
+ ),
9005
+ (
9006
+ 'comments.be_the_first',
9007
+ '{"en": "Be the first to share your thoughts!", "fr": "Soyez le premier à partager vos pensées !"}'::jsonb
9008
+ ),
9009
+ (
9010
+ 'comments.load_more',
9011
+ '{"en": "Load More Comments", "fr": "Charger plus de commentaires"}'::jsonb
9012
+ )
9013
+ ON CONFLICT (key) DO UPDATE
9014
+ SET
9015
+ translations = EXCLUDED.translations,
9016
+ updated_at = now();
9017
+
9018
+
9019
+
9020
+ -- >>> FROM: 00000000000042_seed_deploy_to_vercel_ctas.sql <<<
9021
+ -- Promote the one-click "Deploy to Vercel" button as a CTA in the seeded homepage.
9022
+ --
9023
+ -- Two placements per language, both rendered through the normal 'button' block
9024
+ -- (ButtonBlockRenderer), so external URLs open in a new tab with rel=noopener:
9025
+ -- 1. Home HERO — replace the secondary "View on GitHub" / "Voir sur GitHub"
9026
+ -- outline button with a "Deploy on Vercel" / "Déployer sur Vercel" button
9027
+ -- (same variant/size/position; the primary "Get Started" button is untouched,
9028
+ -- and GitHub is still linked from the hero social row + the community section).
9029
+ -- 2. Home CLOSING CTA band ("Have Questions?" / "Des questions ?") — append a
9030
+ -- secondary (outline) "Deploy on Vercel" button after the contact button so
9031
+ -- the page also ends on the deploy action, styled to match the hero's Deploy
9032
+ -- button and keep the filled "Get in Touch" button as the band's primary.
9033
+ --
9034
+ -- The canonical deploy URL is the exact href from the README's Deploy button
9035
+ -- (native Supabase Marketplace \`stores\` flow). It is stored as a raw button \`url\`
9036
+ -- value (literal '&', not '&amp;') because the renderer uses it directly as an href.
9037
+ --
9038
+ -- Forward-only and idempotent:
9039
+ -- * The hero swap matches the old button label, so a second run is a no-op
9040
+ -- (the label no longer exists) and it silently skips any install where the
9041
+ -- button was already customized or removed.
9042
+ -- * The closing-band append is guarded by NOT LIKE '%Deploy on Vercel%' /
9043
+ -- '%Déployer sur Vercel%', so it never appends twice.
9044
+ -- Scoping is by unique block text, so exactly the home hero / closing band match.
9045
+
9046
+ -- ---------------------------------------------------------------------------
9047
+ -- 1a. EN home hero: "View on GitHub" -> "Deploy on Vercel"
9048
+ -- ---------------------------------------------------------------------------
9049
+ UPDATE public.blocks
9050
+ SET content = replace(
9051
+ replace(content::text, '"View on GitHub"', '"Deploy on Vercel"'),
9052
+ '"https://github.com/nextblock-cms/nextblock"',
9053
+ '"https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnextblock-cms%2Fnextblock&project-name=nextblock&repository-name=nextblock&stores=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22supabase%22%2C%22productSlug%22%3A%22supabase%22%7D%5D"'
9054
+ )::jsonb
9055
+ WHERE content::text LIKE '%"View on GitHub"%';
9056
+
9057
+ -- ---------------------------------------------------------------------------
9058
+ -- 1b. FR home hero: "Voir sur GitHub" -> "Déployer sur Vercel"
9059
+ -- ---------------------------------------------------------------------------
9060
+ UPDATE public.blocks
9061
+ SET content = replace(
9062
+ replace(content::text, '"Voir sur GitHub"', '"Déployer sur Vercel"'),
9063
+ '"https://github.com/nextblock-cms/nextblock"',
9064
+ '"https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnextblock-cms%2Fnextblock&project-name=nextblock&repository-name=nextblock&stores=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22supabase%22%2C%22productSlug%22%3A%22supabase%22%7D%5D"'
9065
+ )::jsonb
9066
+ WHERE content::text LIKE '%"Voir sur GitHub"%';
9067
+
9068
+ -- ---------------------------------------------------------------------------
9069
+ -- 2a. EN home closing band ("Have Questions?"): append a Deploy on Vercel button
9070
+ -- ---------------------------------------------------------------------------
9071
+ UPDATE public.blocks
9072
+ SET content = jsonb_set(
9073
+ content,
9074
+ '{column_blocks,0}',
9075
+ (content #> '{column_blocks,0}') ||
9076
+ '[{"block_type":"button","content":{"text":"Deploy on Vercel","url":"https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnextblock-cms%2Fnextblock&project-name=nextblock&repository-name=nextblock&stores=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22supabase%22%2C%22productSlug%22%3A%22supabase%22%7D%5D","variant":"outline","size":"lg","position":"center"}}]'::jsonb
9077
+ )
9078
+ WHERE content::text LIKE '%Have Questions?%'
9079
+ AND content::text NOT LIKE '%Deploy on Vercel%';
9080
+
9081
+ -- ---------------------------------------------------------------------------
9082
+ -- 2b. FR home closing band ("Des questions ?"): append a Déployer sur Vercel button
9083
+ -- ---------------------------------------------------------------------------
9084
+ UPDATE public.blocks
9085
+ SET content = jsonb_set(
9086
+ content,
9087
+ '{column_blocks,0}',
9088
+ (content #> '{column_blocks,0}') ||
9089
+ '[{"block_type":"button","content":{"text":"Déployer sur Vercel","url":"https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnextblock-cms%2Fnextblock&project-name=nextblock&repository-name=nextblock&stores=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22supabase%22%2C%22productSlug%22%3A%22supabase%22%7D%5D","variant":"outline","size":"lg","position":"center"}}]'::jsonb
9090
+ )
9091
+ WHERE content::text LIKE '%Des questions ?%'
9092
+ AND content::text NOT LIKE '%Déployer sur Vercel%';
9093
+
9094
+
9095
+ -- >>> FROM: 00000000000043_swap_home_hero_media_to_youtube.sql <<<
9096
+ -- Swap the home HERO media from the static NBcover image to an embedded YouTube video.
9097
+ --
9098
+ -- The seeded home hero (migration 010) renders a "Why teams switch / 100% Lighthouse"
9099
+ -- card in its right column whose last element is an <img src="/images/NBcover.webp" />.
9100
+ -- This replaces just that <img> with a responsive 16:9 YouTube <iframe>, keeping the
9101
+ -- surrounding card, its rounded/border/shadow wrapper, and every other block intact.
9102
+ --
9103
+ -- Rendering path: the hero right column is a 'text' block; TextBlockRenderer feeds the
9104
+ -- html_content through html-react-parser, which preserves <iframe> (it only strips on*
9105
+ -- handlers and swaps known CMS <img> for next/image). The proxy.ts CSP frame-src already
9106
+ -- allows https://www.youtube.com, so the embed is not blocked.
9107
+ --
9108
+ -- Forward-only and idempotent:
9109
+ -- * Each UPDATE matches the language-specific <img> alt text, so a second run finds no
9110
+ -- match and is a no-op (the alt text no longer exists once swapped).
9111
+ -- * String replace on content::text mirrors migration 042; the img markup is unique to
9112
+ -- the home hero, so exactly the EN and FR home hero blocks are touched.
9113
+ --
9114
+ -- The NBcover.webp media row is left untouched — it is still the feature image for the
9115
+ -- "how-nextblock-works" post; only the inline hero usage changes.
9116
+
9117
+ -- ---------------------------------------------------------------------------
9118
+ -- 1. EN home hero: replace NBcover <img> with a YouTube <iframe>
9119
+ -- ---------------------------------------------------------------------------
9120
+ UPDATE public.blocks
9121
+ SET content = replace(
9122
+ content::text,
9123
+ '<img src=''/images/NBcover.webp'' alt=''Nextblock cover showcasing dashboards and blocks'' class=''w-full h-auto object-cover transform group-hover:scale-105 transition-transform duration-700'' fetchpriority=''high'' />',
9124
+ '<div class=''relative w-full aspect-video''><iframe class=''absolute inset-0 h-full w-full border-0'' src=''https://www.youtube.com/embed/71MyfoL4YVM?si=jtlDXV6cSC8rDgz0'' title=''NextBlock demo video'' allow=''accelerated-motion; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'' referrerpolicy=''strict-origin-when-cross-origin'' loading=''lazy'' allowfullscreen></iframe></div>'
9125
+ )::jsonb
9126
+ WHERE content::text LIKE '%Nextblock cover showcasing dashboards and blocks%';
9127
+
9128
+ -- ---------------------------------------------------------------------------
9129
+ -- 2. FR home hero: replace NBcover <img> with a YouTube <iframe>
9130
+ -- ---------------------------------------------------------------------------
9131
+ UPDATE public.blocks
9132
+ SET content = replace(
9133
+ content::text,
9134
+ '<img src=''/images/NBcover.webp'' alt=''Couverture Nextblock'' class=''w-full h-auto object-cover transform group-hover:scale-105 transition-transform duration-700'' fetchpriority=''high'' />',
9135
+ '<div class=''relative w-full aspect-video''><iframe class=''absolute inset-0 h-full w-full border-0'' src=''https://www.youtube.com/embed/71MyfoL4YVM?si=jtlDXV6cSC8rDgz0'' title=''Vidéo de démonstration NextBlock'' allow=''accelerated-motion; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'' referrerpolicy=''strict-origin-when-cross-origin'' loading=''lazy'' allowfullscreen></iframe></div>'
9136
+ )::jsonb
9137
+ WHERE content::text LIKE '%Couverture Nextblock%';
9138
+
9139
+
9140
+ -- >>> FROM: 00000000000044_swap_article_nx_graph_for_youtube.sql <<<
9141
+ -- Swap the Nx-graph image in the "How NextBlock™ Works" article for an embedded
9142
+ -- YouTube video, and rebalance that two-column section to an even 50/50 split.
9143
+ --
9144
+ -- Targets the post-body 'text' block (dollar-quoted html_content seeded in migration
9145
+ -- 010) for both language variants of the same post:
9146
+ -- * EN slug how-nextblock-works (img alt "Nx project graph preview ...")
9147
+ -- * FR slug comment-nextblock-fonctionne (img alt "Apercu du graphe Nx ...")
9148
+ --
9149
+ -- Three forward-only, idempotent edits:
9150
+ -- 1. Column widths md:w-3/5 (text) and md:w-2/5 (media) -> md:w-1/2 each, and the
9151
+ -- flex row switches items-start -> items-center so the shorter media column sits
9152
+ -- centered against the taller text column. These class fragments occur only in
9153
+ -- this one section (EN + FR), so a single UPDATE rebalances both languages.
9154
+ -- 2/3. Replace the <img src="/images/nx-graph.webp" ...> with a responsive 16:9
9155
+ -- YouTube <iframe>, kept inside the existing white aside card above its caption.
9156
+ --
9157
+ -- Rendering/CSP identical to migration 043: TextBlockRenderer -> html-react-parser
9158
+ -- preserves the <iframe>; proxy.ts CSP frame-src allows www.youtube.com; every utility
9159
+ -- class used here lives in this .sql file, which Tailwind's content scanner
9160
+ -- (libs/**/*.sql) compiles into the bundle. The nx-graph.webp media row is left intact.
9161
+ --
9162
+ -- Each UPDATE is guarded by a substring that only exists pre-swap, so a second run is a
9163
+ -- no-op. Dollar-quoted string literals ($old$/$new$) avoid escaping the single-quoted
9164
+ -- HTML attributes; the sandbox reset runs this via postgres db.unsafe() (no outer
9165
+ -- dollar-quote wrapper), same as migration 010's dollar-quoted seed content.
9166
+
9167
+ -- ---------------------------------------------------------------------------
9168
+ -- 1. Both languages: 3/5 + 2/5 columns -> 50/50, and vertical-center the row
9169
+ -- ---------------------------------------------------------------------------
9170
+ UPDATE public.blocks
9171
+ SET content = replace(
9172
+ replace(
9173
+ replace(
9174
+ content::text,
9175
+ 'flex flex-col md:flex-row gap-8 items-start my-12',
9176
+ 'flex flex-col md:flex-row gap-8 items-center my-12'
9177
+ ),
9178
+ 'w-full md:w-3/5 space-y-4',
9179
+ 'w-full md:w-1/2 space-y-4'
9180
+ ),
9181
+ 'md:w-2/5 rounded-[2rem]',
9182
+ 'md:w-1/2 rounded-[2rem]'
9183
+ )::jsonb
9184
+ WHERE content::text LIKE '%w-full md:w-3/5 space-y-4%';
9185
+
9186
+ -- ---------------------------------------------------------------------------
9187
+ -- 2. EN post body: nx-graph <img> -> responsive YouTube <iframe>
9188
+ -- ---------------------------------------------------------------------------
9189
+ UPDATE public.blocks
9190
+ SET content = replace(
9191
+ content::text,
9192
+ $old$<img src='/images/nx-graph.webp' alt='Nx project graph preview showing apps and shared libraries linked together' class='w-full h-auto rounded-2xl object-cover' />$old$,
9193
+ $new$<div class='relative aspect-video overflow-hidden rounded-2xl'><iframe class='absolute inset-0 h-full w-full border-0' src='https://www.youtube.com/embed/DNqU8ez9qjs?si=p2oIy0f-n7wiaBmO' title='How NextBlock™ Works' allow='accelerated-motion; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share' referrerpolicy='strict-origin-when-cross-origin' loading='lazy' allowfullscreen></iframe></div>$new$
9194
+ )::jsonb
9195
+ WHERE content::text LIKE '%Nx project graph preview showing apps and shared libraries linked together%';
9196
+
9197
+ -- ---------------------------------------------------------------------------
9198
+ -- 3. FR post body: nx-graph <img> -> responsive YouTube <iframe>
9199
+ -- ---------------------------------------------------------------------------
9200
+ UPDATE public.blocks
9201
+ SET content = replace(
9202
+ content::text,
9203
+ $old$<img src='/images/nx-graph.webp' alt='Apercu du graphe Nx montrant les applications et librairies partagees' class='w-full h-auto rounded-2xl object-cover' />$old$,
9204
+ $new$<div class='relative aspect-video overflow-hidden rounded-2xl'><iframe class='absolute inset-0 h-full w-full border-0' src='https://www.youtube.com/embed/DNqU8ez9qjs?si=p2oIy0f-n7wiaBmO' title='Comment NextBlock™ fonctionne' allow='accelerated-motion; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share' referrerpolicy='strict-origin-when-cross-origin' loading='lazy' allowfullscreen></iframe></div>$new$
9205
+ )::jsonb
9206
+ WHERE content::text LIKE '%Apercu du graphe Nx montrant les applications et librairies partagees%';
9207
+
9208
+
8258
9209
  -- Step D: Anchor preserved profiles
8259
9210
  INSERT INTO public.profiles (id, updated_at, full_name, avatar_url, website, role)
8260
9211
  SELECT preserved_user.id, NULL, NULL, NULL, NULL, 'ADMIN'