@nextblock-cms/db 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.
@@ -104,6 +104,66 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
104
104
  };
105
105
  Relationships: [];
106
106
  };
107
+ cms_interactions: {
108
+ Row: {
109
+ content: string;
110
+ created_at: string;
111
+ id: string;
112
+ post_id: number | null;
113
+ product_id: string | null;
114
+ rating: number | null;
115
+ reactions: import('./types').Json;
116
+ status: Database["public"]["Enums"]["approval_status"];
117
+ type: Database["public"]["Enums"]["interaction_type"];
118
+ updated_at: string;
119
+ user_id: string;
120
+ };
121
+ Insert: {
122
+ content: string;
123
+ created_at?: string;
124
+ id?: string;
125
+ post_id?: number | null;
126
+ product_id?: string | null;
127
+ rating?: number | null;
128
+ reactions?: import('./types').Json;
129
+ status?: Database["public"]["Enums"]["approval_status"];
130
+ type: Database["public"]["Enums"]["interaction_type"];
131
+ updated_at?: string;
132
+ user_id: string;
133
+ };
134
+ Update: {
135
+ content?: string;
136
+ created_at?: string;
137
+ id?: string;
138
+ post_id?: number | null;
139
+ product_id?: string | null;
140
+ rating?: number | null;
141
+ reactions?: import('./types').Json;
142
+ status?: Database["public"]["Enums"]["approval_status"];
143
+ type?: Database["public"]["Enums"]["interaction_type"];
144
+ updated_at?: string;
145
+ user_id?: string;
146
+ };
147
+ Relationships: [{
148
+ foreignKeyName: "cms_interactions_post_id_fkey";
149
+ columns: ["post_id"];
150
+ isOneToOne: false;
151
+ referencedRelation: "posts";
152
+ referencedColumns: ["id"];
153
+ }, {
154
+ foreignKeyName: "cms_interactions_product_id_fkey";
155
+ columns: ["product_id"];
156
+ isOneToOne: false;
157
+ referencedRelation: "products";
158
+ referencedColumns: ["id"];
159
+ }, {
160
+ foreignKeyName: "cms_interactions_user_id_profiles_fkey";
161
+ columns: ["user_id"];
162
+ isOneToOne: false;
163
+ referencedRelation: "profiles";
164
+ referencedColumns: ["id"];
165
+ }];
166
+ };
107
167
  content_drafts: {
108
168
  Row: {
109
169
  author_id: string | null;
@@ -1531,6 +1591,7 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
1531
1591
  };
1532
1592
  products: {
1533
1593
  Row: {
1594
+ average_rating: number;
1534
1595
  created_at: string | null;
1535
1596
  description_json: import('./types').Json | null;
1536
1597
  freemius_plan_id: string | null;
@@ -1558,6 +1619,7 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
1558
1619
  status: string;
1559
1620
  stock: number | null;
1560
1621
  title: string;
1622
+ total_reviews: number;
1561
1623
  translation_group_id: string;
1562
1624
  trial_period_days: number;
1563
1625
  trial_requires_payment_method: boolean;
@@ -1565,6 +1627,7 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
1565
1627
  updated_at: string | null;
1566
1628
  };
1567
1629
  Insert: {
1630
+ average_rating?: number;
1568
1631
  created_at?: string | null;
1569
1632
  description_json?: import('./types').Json | null;
1570
1633
  freemius_plan_id?: string | null;
@@ -1592,6 +1655,7 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
1592
1655
  status?: string;
1593
1656
  stock?: number | null;
1594
1657
  title: string;
1658
+ total_reviews?: number;
1595
1659
  translation_group_id?: string;
1596
1660
  trial_period_days?: number;
1597
1661
  trial_requires_payment_method?: boolean;
@@ -1599,6 +1663,7 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
1599
1663
  updated_at?: string | null;
1600
1664
  };
1601
1665
  Update: {
1666
+ average_rating?: number;
1602
1667
  created_at?: string | null;
1603
1668
  description_json?: import('./types').Json | null;
1604
1669
  freemius_plan_id?: string | null;
@@ -1626,6 +1691,7 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
1626
1691
  status?: string;
1627
1692
  stock?: number | null;
1628
1693
  title?: string;
1694
+ total_reviews?: number;
1629
1695
  translation_group_id?: string;
1630
1696
  trial_period_days?: number;
1631
1697
  trial_requires_payment_method?: boolean;
@@ -2221,6 +2287,8 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
2221
2287
  };
2222
2288
  };
2223
2289
  Enums: {
2290
+ approval_status: "pending" | "approved" | "denied";
2291
+ interaction_type: "review" | "comment";
2224
2292
  menu_location: "HEADER" | "FOOTER" | "SIDEBAR";
2225
2293
  page_status: "draft" | "published" | "archived";
2226
2294
  revision_type: "snapshot" | "diff";
@@ -105,6 +105,70 @@ export type Database = {
105
105
  };
106
106
  Relationships: [];
107
107
  };
108
+ cms_interactions: {
109
+ Row: {
110
+ content: string;
111
+ created_at: string;
112
+ id: string;
113
+ post_id: number | null;
114
+ product_id: string | null;
115
+ rating: number | null;
116
+ reactions: Json;
117
+ status: Database["public"]["Enums"]["approval_status"];
118
+ type: Database["public"]["Enums"]["interaction_type"];
119
+ updated_at: string;
120
+ user_id: string;
121
+ };
122
+ Insert: {
123
+ content: string;
124
+ created_at?: string;
125
+ id?: string;
126
+ post_id?: number | null;
127
+ product_id?: string | null;
128
+ rating?: number | null;
129
+ reactions?: Json;
130
+ status?: Database["public"]["Enums"]["approval_status"];
131
+ type: Database["public"]["Enums"]["interaction_type"];
132
+ updated_at?: string;
133
+ user_id: string;
134
+ };
135
+ Update: {
136
+ content?: string;
137
+ created_at?: string;
138
+ id?: string;
139
+ post_id?: number | null;
140
+ product_id?: string | null;
141
+ rating?: number | null;
142
+ reactions?: Json;
143
+ status?: Database["public"]["Enums"]["approval_status"];
144
+ type?: Database["public"]["Enums"]["interaction_type"];
145
+ updated_at?: string;
146
+ user_id?: string;
147
+ };
148
+ Relationships: [
149
+ {
150
+ foreignKeyName: "cms_interactions_post_id_fkey";
151
+ columns: ["post_id"];
152
+ isOneToOne: false;
153
+ referencedRelation: "posts";
154
+ referencedColumns: ["id"];
155
+ },
156
+ {
157
+ foreignKeyName: "cms_interactions_product_id_fkey";
158
+ columns: ["product_id"];
159
+ isOneToOne: false;
160
+ referencedRelation: "products";
161
+ referencedColumns: ["id"];
162
+ },
163
+ {
164
+ foreignKeyName: "cms_interactions_user_id_profiles_fkey";
165
+ columns: ["user_id"];
166
+ isOneToOne: false;
167
+ referencedRelation: "profiles";
168
+ referencedColumns: ["id"];
169
+ }
170
+ ];
171
+ };
108
172
  content_drafts: {
109
173
  Row: {
110
174
  author_id: string | null;
@@ -1593,6 +1657,7 @@ export type Database = {
1593
1657
  };
1594
1658
  products: {
1595
1659
  Row: {
1660
+ average_rating: number;
1596
1661
  created_at: string | null;
1597
1662
  description_json: Json | null;
1598
1663
  freemius_plan_id: string | null;
@@ -1620,6 +1685,7 @@ export type Database = {
1620
1685
  status: string;
1621
1686
  stock: number | null;
1622
1687
  title: string;
1688
+ total_reviews: number;
1623
1689
  translation_group_id: string;
1624
1690
  trial_period_days: number;
1625
1691
  trial_requires_payment_method: boolean;
@@ -1627,6 +1693,7 @@ export type Database = {
1627
1693
  updated_at: string | null;
1628
1694
  };
1629
1695
  Insert: {
1696
+ average_rating?: number;
1630
1697
  created_at?: string | null;
1631
1698
  description_json?: Json | null;
1632
1699
  freemius_plan_id?: string | null;
@@ -1654,6 +1721,7 @@ export type Database = {
1654
1721
  status?: string;
1655
1722
  stock?: number | null;
1656
1723
  title: string;
1724
+ total_reviews?: number;
1657
1725
  translation_group_id?: string;
1658
1726
  trial_period_days?: number;
1659
1727
  trial_requires_payment_method?: boolean;
@@ -1661,6 +1729,7 @@ export type Database = {
1661
1729
  updated_at?: string | null;
1662
1730
  };
1663
1731
  Update: {
1732
+ average_rating?: number;
1664
1733
  created_at?: string | null;
1665
1734
  description_json?: Json | null;
1666
1735
  freemius_plan_id?: string | null;
@@ -1688,6 +1757,7 @@ export type Database = {
1688
1757
  status?: string;
1689
1758
  stock?: number | null;
1690
1759
  title?: string;
1760
+ total_reviews?: number;
1691
1761
  translation_group_id?: string;
1692
1762
  trial_period_days?: number;
1693
1763
  trial_requires_payment_method?: boolean;
@@ -2296,6 +2366,8 @@ export type Database = {
2296
2366
  };
2297
2367
  };
2298
2368
  Enums: {
2369
+ approval_status: "pending" | "approved" | "denied";
2370
+ interaction_type: "review" | "comment";
2299
2371
  menu_location: "HEADER" | "FOOTER" | "SIDEBAR";
2300
2372
  page_status: "draft" | "published" | "archived";
2301
2373
  revision_type: "snapshot" | "diff";
@@ -2358,6 +2430,8 @@ export type CompositeTypes<PublicCompositeTypeNameOrOptions extends keyof Defaul
2358
2430
  export declare const Constants: {
2359
2431
  readonly public: {
2360
2432
  readonly Enums: {
2433
+ readonly approval_status: readonly ["pending", "approved", "denied"];
2434
+ readonly interaction_type: readonly ["review", "comment"];
2361
2435
  readonly menu_location: readonly ["HEADER", "FOOTER", "SIDEBAR"];
2362
2436
  readonly page_status: readonly ["draft", "published", "archived"];
2363
2437
  readonly revision_type: readonly ["snapshot", "diff"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextblock-cms/db",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "main": "index.cjs.js",
5
5
  "module": "index.es.js",
6
6
  "types": "index.d.ts",
package/secrets.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './lib/config-resolve';
2
+ export * from './lib/secret-crypto';
@@ -0,0 +1,317 @@
1
+ -- Rewrite the seeded "How to Setup NextBlock" tutorial (EN + FR) around the four
2
+ -- current installation paths:
3
+ -- 1. One-click Deploy on Vercel (Supabase Marketplace integration, zero env vars),
4
+ -- 2. npm create nextblock (standalone app, browser /setup wizard on :3000),
5
+ -- 3. git clone -> npm install -> npx nx serve nextblock (browser /setup wizard on :4200),
6
+ -- 4. git clone -> npm install -> npm run docker:setup (fully local, non-interactive).
7
+ --
8
+ -- Also sets the posts' meta_title / meta_description (previously unset — SEO title fell
9
+ -- back to posts.title and the description to posts.subtitle) and refreshes
10
+ -- title / subtitle / excerpt for both languages. Slugs are intentionally unchanged:
11
+ -- 'how-to-setup-nextblock' and 'comment-configurer-nextblock' are public URLs and are
12
+ -- also mapped to the tutorial presentation (post-article--tutorial) by slug.
13
+ --
14
+ -- Forward-only and idempotent: it updates the two posts rows and replaces their single
15
+ -- text block (same pattern as 00000000000029). Safe to re-run; a no-op if the posts
16
+ -- do not exist.
17
+
18
+ -- EN post metadata
19
+ UPDATE public.posts
20
+ SET
21
+ title = 'How to Install NextBlock: Every Setup Option Explained',
22
+ 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.',
23
+ excerpt = 'Every way to install NextBlock — one-click cloud deploy, CLI scaffold, git clone, or self-hosted Docker — with copy-paste steps for each.',
24
+ meta_title = 'How to Install NextBlock CMS — Vercel, CLI, Git or Docker',
25
+ 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.'
26
+ WHERE slug = 'how-to-setup-nextblock';
27
+
28
+ -- FR post metadata
29
+ UPDATE public.posts
30
+ SET
31
+ title = $meta$Installer NextBlock : toutes les options expliquées$meta$,
32
+ 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$,
33
+ 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$,
34
+ meta_title = $meta$Installer NextBlock — Vercel, CLI, Git ou Docker$meta$,
35
+ 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$
36
+ WHERE slug = 'comment-configurer-nextblock';
37
+
38
+ WITH target_posts AS (
39
+ SELECT id, language_id, slug
40
+ FROM public.posts
41
+ WHERE slug IN ('how-to-setup-nextblock', 'comment-configurer-nextblock')
42
+ ),
43
+ purged AS (
44
+ DELETE FROM public.blocks
45
+ WHERE post_id IN (SELECT id FROM target_posts)
46
+ )
47
+ INSERT INTO public.blocks (post_id, language_id, block_type, content, "order")
48
+
49
+ -- EN: How to Install NextBlock
50
+ SELECT tp.id, tp.language_id, 'text', jsonb_build_object('html_content',
51
+ $$<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>
52
+
53
+ <div class='grid gap-5 md:grid-cols-2 my-10'>
54
+ <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'>
55
+ <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>
56
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>One-click deploy on Vercel</h3>
57
+ <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>
58
+ </a>
59
+ <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'>
60
+ <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>
61
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>npm create nextblock</h3>
62
+ <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>
63
+ </a>
64
+ <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'>
65
+ <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>
66
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>Clone the repository</h3>
67
+ <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>
68
+ </a>
69
+ <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'>
70
+ <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>
71
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>Self-hosted with Docker</h3>
72
+ <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>
73
+ </a>
74
+ </div>
75
+
76
+ <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>
77
+
78
+ <figure class='my-12 overflow-hidden rounded-[2rem] border border-slate-200/80 bg-slate-950 shadow-2xl dark:border-white/10'>
79
+ <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' />
80
+ <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>
81
+ </figure>
82
+
83
+ <h2 id='one-click-vercel'>Option 1: One-Click Deploy on Vercel</h2>
84
+ <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>
85
+ <ol class='space-y-2'>
86
+ <li><strong>Click Deploy to Vercel</strong> and sign in — Vercel clones NextBlock into a new repository you own.</li>
87
+ <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>
88
+ <li><strong>Open your new site</strong> once the build finishes. Every fresh instance takes you straight to the setup wizard.</li>
89
+ <li><strong>Create your administrator account.</strong> It is confirmed instantly — no verification email — and you land in the CMS dashboard.</li>
90
+ </ol>
91
+ <div class='my-8'>
92
+ <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>
93
+ </div>
94
+ <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'>
95
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-blue-700 dark:text-blue-200'>Zero configuration</p>
96
+ <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>
97
+ </div>
98
+ <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>
99
+
100
+ <h2 id='npm-create'>Option 2: Scaffold a Project with npm create nextblock</h2>
101
+ <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>
102
+ <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>
103
+ <pre><code>npm create nextblock@latest my-site
104
+ cd my-site
105
+ npm run dev</code></pre>
106
+ <p>Open <code>http://localhost:3000/setup</code> and let the wizard take over:</p>
107
+ <ol class='space-y-2'>
108
+ <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>
109
+ <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>
110
+ <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>
111
+ </ol>
112
+ <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'>
113
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-violet-700 dark:text-violet-200'>Premium modules</p>
114
+ <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>
115
+ </div>
116
+
117
+ <h2 id='git-clone'>Option 3: Clone the Repository</h2>
118
+ <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>
119
+ <pre><code>git clone https://github.com/nextblock-cms/nextblock.git
120
+ cd nextblock
121
+ npm install
122
+ npx nx serve nextblock</code></pre>
123
+ <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>
124
+ <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'>
125
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-emerald-700 dark:text-emerald-200'>No terminal setup</p>
126
+ <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>
127
+ </div>
128
+
129
+ <h2 id='docker'>Option 4: Self-Hosted with Docker</h2>
130
+ <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>
131
+ <p>With <a href='https://www.docker.com/products/docker-desktop/' target='_blank' rel='noopener'>Docker Desktop</a> installed and running:</p>
132
+ <pre><code>git clone https://github.com/nextblock-cms/nextblock.git
133
+ cd nextblock
134
+ npm install
135
+ npm run docker:setup</code></pre>
136
+ <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>
137
+ <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'>
138
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-amber-700 dark:text-amber-200'>Day-2 commands</p>
139
+ <pre class='mt-4 mb-0'><code># rebuild and restart the stack
140
+ npm run docker:up
141
+
142
+ # stop the stack (your data persists in Docker volumes)
143
+ npm run docker:down
144
+
145
+ # follow the application logs
146
+ npm run docker:logs</code></pre>
147
+ </div>
148
+
149
+ <h2 id='after-install'>After You Install: Your First 10 Minutes</h2>
150
+ <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>
151
+ <ul class='space-y-2'>
152
+ <li><strong>Add your branding</strong> — upload your logo and set the site title.</li>
153
+ <li><strong>Set your footer</strong> — copyright line and footer navigation.</li>
154
+ <li><strong>Configure email (SMTP)</strong> — under Settings, so password resets and invitations can send.</li>
155
+ <li><strong>Optional extras</strong> — connect analytics, enable bot protection, and (on Vercel) turn on automatic updates.</li>
156
+ </ul>
157
+ <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>
158
+
159
+ <h2 id='faq'>Installation FAQ</h2>
160
+ <h3>What do I need installed?</h3>
161
+ <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>
162
+ <h3>Is NextBlock free?</h3>
163
+ <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>
164
+ <h3>Do I need a Supabase account?</h3>
165
+ <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>
166
+ <h3>Do I have to run migrations or SQL by hand?</h3>
167
+ <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>
168
+ <h3>Can I switch paths later?</h3>
169
+ <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>
170
+ <h3>How do I update NextBlock?</h3>
171
+ <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>
172
+
173
+ <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'>
174
+ <p class='mt-0 text-2xl font-semibold text-slate-900 dark:text-white'>Ready to launch?</p>
175
+ <p class='text-sm text-slate-600 dark:text-slate-300'>Pick your path above, or jump straight to the fastest one.</p>
176
+ <div class='mt-5 flex flex-wrap justify-center gap-3'>
177
+ <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>
178
+ <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>
179
+ </div>
180
+ </div>$$
181
+ ), 0 FROM target_posts tp WHERE tp.slug = 'how-to-setup-nextblock'
182
+
183
+ UNION ALL
184
+
185
+ -- FR: Installer NextBlock
186
+ SELECT tp.id, tp.language_id, 'text', jsonb_build_object('html_content',
187
+ $$<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>
188
+
189
+ <div class='grid gap-5 md:grid-cols-2 my-10'>
190
+ <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'>
191
+ <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>
192
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>Déploiement Vercel en un clic</h3>
193
+ <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>
194
+ </a>
195
+ <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'>
196
+ <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>
197
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>npm create nextblock</h3>
198
+ <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>
199
+ </a>
200
+ <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'>
201
+ <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>
202
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>Cloner le dépôt</h3>
203
+ <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>
204
+ </a>
205
+ <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'>
206
+ <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>
207
+ <h3 class='mt-3 mb-2 text-xl font-semibold text-slate-900 dark:text-white'>Auto-hébergé avec Docker</h3>
208
+ <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>
209
+ </a>
210
+ </div>
211
+
212
+ <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>
213
+
214
+ <figure class='my-12 overflow-hidden rounded-[2rem] border border-slate-200/80 bg-slate-950 shadow-2xl dark:border-white/10'>
215
+ <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' />
216
+ <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>
217
+ </figure>
218
+
219
+ <h2 id='one-click-vercel'>Option 1 : Déploiement Vercel en un clic</h2>
220
+ <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>
221
+ <ol class='space-y-2'>
222
+ <li><strong>Cliquez sur Deploy to Vercel</strong> et connectez-vous — Vercel clone NextBlock dans un nouveau dépôt qui vous appartient.</li>
223
+ <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>
224
+ <li><strong>Ouvrez votre nouveau site</strong> une fois le build terminé. Toute nouvelle instance vous amène directement à l'assistant de configuration.</li>
225
+ <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>
226
+ </ol>
227
+ <div class='my-8'>
228
+ <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>
229
+ </div>
230
+ <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'>
231
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-blue-700 dark:text-blue-200'>Zéro configuration</p>
232
+ <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>
233
+ </div>
234
+ <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>
235
+
236
+ <h2 id='npm-create'>Option 2 : Créer un projet avec npm create nextblock</h2>
237
+ <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>
238
+ <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>
239
+ <pre><code>npm create nextblock@latest mon-site
240
+ cd mon-site
241
+ npm run dev</code></pre>
242
+ <p>Ouvrez <code>http://localhost:3000/setup</code> et laissez l'assistant faire le travail :</p>
243
+ <ol class='space-y-2'>
244
+ <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>
245
+ <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>
246
+ <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>
247
+ </ol>
248
+ <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'>
249
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-violet-700 dark:text-violet-200'>Modules premium</p>
250
+ <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>
251
+ </div>
252
+
253
+ <h2 id='git-clone'>Option 3 : Cloner le dépôt</h2>
254
+ <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>
255
+ <pre><code>git clone https://github.com/nextblock-cms/nextblock.git
256
+ cd nextblock
257
+ npm install
258
+ npx nx serve nextblock</code></pre>
259
+ <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>
260
+ <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'>
261
+ <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>
262
+ <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>
263
+ </div>
264
+
265
+ <h2 id='docker'>Option 4 : Auto-hébergé avec Docker</h2>
266
+ <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>
267
+ <p>Avec <a href='https://www.docker.com/products/docker-desktop/' target='_blank' rel='noopener'>Docker Desktop</a> installé et démarré :</p>
268
+ <pre><code>git clone https://github.com/nextblock-cms/nextblock.git
269
+ cd nextblock
270
+ npm install
271
+ npm run docker:setup</code></pre>
272
+ <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>
273
+ <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'>
274
+ <p class='mt-0 text-xs font-semibold uppercase tracking-[0.22em] text-amber-700 dark:text-amber-200'>Commandes du quotidien</p>
275
+ <pre class='mt-4 mb-0'><code># reconstruire et redémarrer la pile
276
+ npm run docker:up
277
+
278
+ # arrêter la pile (vos données persistent dans les volumes Docker)
279
+ npm run docker:down
280
+
281
+ # suivre les logs de l'application
282
+ npm run docker:logs</code></pre>
283
+ </div>
284
+
285
+ <h2 id='after-install'>Après l'installation : vos 10 premières minutes</h2>
286
+ <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>
287
+ <ul class='space-y-2'>
288
+ <li><strong>Ajoutez votre identité visuelle</strong> — téléversez votre logo et définissez le titre du site.</li>
289
+ <li><strong>Réglez votre pied de page</strong> — mention de copyright et navigation du pied de page.</li>
290
+ <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>
291
+ <li><strong>Extras optionnels</strong> — connectez vos outils d'analytics, activez la protection anti-bots et (sur Vercel) les mises à jour automatiques.</li>
292
+ </ul>
293
+ <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>
294
+
295
+ <h2 id='faq'>FAQ d'installation</h2>
296
+ <h3>Que dois-je installer ?</h3>
297
+ <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>
298
+ <h3>NextBlock est-il gratuit ?</h3>
299
+ <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>
300
+ <h3>Ai-je besoin d'un compte Supabase ?</h3>
301
+ <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>
302
+ <h3>Dois-je exécuter des migrations ou du SQL à la main ?</h3>
303
+ <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>
304
+ <h3>Puis-je changer de chemin plus tard ?</h3>
305
+ <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>
306
+ <h3>Comment mettre à jour NextBlock ?</h3>
307
+ <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>
308
+
309
+ <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'>
310
+ <p class='mt-0 text-2xl font-semibold text-slate-900 dark:text-white'>Prêt à vous lancer ?</p>
311
+ <p class='text-sm text-slate-600 dark:text-slate-300'>Choisissez votre chemin ci-dessus, ou passez directement au plus rapide.</p>
312
+ <div class='mt-5 flex flex-wrap justify-center gap-3'>
313
+ <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>
314
+ <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>
315
+ </div>
316
+ </div>$$
317
+ ), 0 FROM target_posts tp WHERE tp.slug = 'comment-configurer-nextblock';
@@ -0,0 +1,126 @@
1
+ -- 00000000000038_seed_consent_banner_translations.sql
2
+ -- Consent banner UI copy (EN + FR). Forward-only and idempotent.
3
+
4
+ INSERT INTO public.translations (key, translations)
5
+ VALUES
6
+ (
7
+ 'privacy.consent.aria_label',
8
+ jsonb_build_object(
9
+ 'en', 'Privacy consent',
10
+ 'fr', 'Consentement à la confidentialité'
11
+ )
12
+ ),
13
+ (
14
+ 'privacy.consent.title',
15
+ jsonb_build_object(
16
+ 'en', 'We value your privacy',
17
+ 'fr', 'Nous respectons votre vie privée'
18
+ )
19
+ ),
20
+ (
21
+ 'privacy.consent.description_before_policy_link',
22
+ jsonb_build_object(
23
+ 'en', 'We use only essential cookies by default. With your consent we also use analytics to improve the site. See our',
24
+ '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'
25
+ )
26
+ ),
27
+ (
28
+ 'privacy.consent.privacy_policy_link',
29
+ jsonb_build_object(
30
+ 'en', 'Privacy Policy',
31
+ 'fr', 'politique de confidentialité'
32
+ )
33
+ ),
34
+ (
35
+ 'privacy.consent.description_after_policy_link',
36
+ jsonb_build_object(
37
+ 'en', '.',
38
+ 'fr', '.'
39
+ )
40
+ ),
41
+ (
42
+ 'privacy.consent.privacy_policy_href',
43
+ jsonb_build_object(
44
+ 'en', '/privacy-policy',
45
+ 'fr', '/politique-de-confidentialite'
46
+ )
47
+ ),
48
+ (
49
+ 'privacy.consent.necessary_label',
50
+ jsonb_build_object(
51
+ 'en', 'Necessary',
52
+ 'fr', 'Nécessaires'
53
+ )
54
+ ),
55
+ (
56
+ 'privacy.consent.necessary_help',
57
+ jsonb_build_object(
58
+ 'en', 'Always on',
59
+ 'fr', 'Toujours actifs'
60
+ )
61
+ ),
62
+ (
63
+ 'privacy.consent.analytics_label',
64
+ jsonb_build_object(
65
+ 'en', 'Analytics',
66
+ 'fr', 'Analytique'
67
+ )
68
+ ),
69
+ (
70
+ 'privacy.consent.analytics_help',
71
+ jsonb_build_object(
72
+ 'en', 'Usage insights',
73
+ 'fr', 'Statistiques d''utilisation'
74
+ )
75
+ ),
76
+ (
77
+ 'privacy.consent.marketing_label',
78
+ jsonb_build_object(
79
+ 'en', 'Marketing',
80
+ 'fr', 'Marketing'
81
+ )
82
+ ),
83
+ (
84
+ 'privacy.consent.marketing_help',
85
+ jsonb_build_object(
86
+ 'en', 'Personalized content',
87
+ 'fr', 'Contenu personnalisé'
88
+ )
89
+ ),
90
+ (
91
+ 'privacy.consent.save_choices',
92
+ jsonb_build_object(
93
+ 'en', 'Save choices',
94
+ 'fr', 'Enregistrer mes choix'
95
+ )
96
+ ),
97
+ (
98
+ 'privacy.consent.accept_all',
99
+ jsonb_build_object(
100
+ 'en', 'Accept all',
101
+ 'fr', 'Tout accepter'
102
+ )
103
+ ),
104
+ (
105
+ 'privacy.consent.reject_all',
106
+ jsonb_build_object(
107
+ 'en', 'Reject all',
108
+ 'fr', 'Tout refuser'
109
+ )
110
+ ),
111
+ (
112
+ 'privacy.consent.hide_options',
113
+ jsonb_build_object(
114
+ 'en', 'Hide options',
115
+ 'fr', 'Masquer les options'
116
+ )
117
+ ),
118
+ (
119
+ 'privacy.consent.manage_options',
120
+ jsonb_build_object(
121
+ 'en', 'Manage options',
122
+ 'fr', 'Gérer les options'
123
+ )
124
+ )
125
+ ON CONFLICT (key) DO UPDATE
126
+ SET translations = public.translations.translations || EXCLUDED.translations;
@@ -0,0 +1,153 @@
1
+ -- 00000000000039_setup_interactions.sql
2
+ -- Migration establishing Product Reviews and Post Comments schema.
3
+
4
+ -- 1. Create Enums
5
+ CREATE TYPE public.interaction_type AS ENUM ('review', 'comment');
6
+ CREATE TYPE public.approval_status AS ENUM ('pending', 'approved', 'denied');
7
+
8
+ -- 2. Alter Products Table to support aggregations
9
+ ALTER TABLE public.products
10
+ ADD COLUMN average_rating numeric(3,2) NOT NULL DEFAULT 0.00,
11
+ ADD COLUMN total_reviews integer NOT NULL DEFAULT 0;
12
+
13
+ COMMENT ON COLUMN public.products.average_rating IS 'Aggregated average 1-5 rating of approved reviews.';
14
+ COMMENT ON COLUMN public.products.total_reviews IS 'Total count of approved reviews for this product.';
15
+
16
+ -- 3. Create Interactions Table
17
+ CREATE TABLE public.cms_interactions (
18
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
19
+ type public.interaction_type NOT NULL,
20
+ status public.approval_status NOT NULL DEFAULT 'pending',
21
+ content text NOT NULL,
22
+ rating integer,
23
+ user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
24
+ product_id uuid REFERENCES public.products(id) ON DELETE CASCADE,
25
+ post_id bigint REFERENCES public.posts(id) ON DELETE CASCADE,
26
+ reactions jsonb NOT NULL DEFAULT '{}'::jsonb,
27
+ created_at timestamptz NOT NULL DEFAULT now(),
28
+ updated_at timestamptz NOT NULL DEFAULT now(),
29
+
30
+ -- Ensure polymorphism is respected (either product or post)
31
+ CONSTRAINT check_product_or_post CHECK (
32
+ (product_id IS NOT NULL AND post_id IS NULL)
33
+ OR (post_id IS NOT NULL AND product_id IS NULL)
34
+ ),
35
+
36
+ -- Ensure rating matches business rules
37
+ CONSTRAINT check_rating_only_for_review CHECK (
38
+ (type = 'review' AND rating IS NOT NULL AND rating >= 1 AND rating <= 5)
39
+ OR (type = 'comment' AND rating IS NULL)
40
+ )
41
+ );
42
+
43
+ COMMENT ON TABLE public.cms_interactions IS 'Stores user-submitted product reviews and blog post comments.';
44
+ COMMENT ON COLUMN public.cms_interactions.rating IS 'Star rating 1-5, only populated for product reviews.';
45
+ COMMENT ON COLUMN public.cms_interactions.reactions IS 'JSONB structure tracking counts of reactions (likes, etc.).';
46
+
47
+ -- 4. Create rating aggregation trigger function
48
+ CREATE OR REPLACE FUNCTION public.update_product_ratings()
49
+ RETURNS TRIGGER AS $$
50
+ DECLARE
51
+ v_product_id uuid;
52
+ v_avg numeric(3,2);
53
+ v_count integer;
54
+ BEGIN
55
+ -- Determine which product_id we need to update
56
+ IF TG_OP = 'DELETE' THEN
57
+ v_product_id := OLD.product_id;
58
+ ELSE
59
+ v_product_id := NEW.product_id;
60
+ END IF;
61
+
62
+ IF v_product_id IS NOT NULL THEN
63
+ -- Calculate average and count of approved reviews for this product
64
+ SELECT COALESCE(avg(rating), 0.00), count(*)
65
+ INTO v_avg, v_count
66
+ FROM public.cms_interactions
67
+ WHERE product_id = v_product_id
68
+ AND type = 'review'
69
+ AND status = 'approved';
70
+
71
+ -- Update products table
72
+ UPDATE public.products
73
+ SET average_rating = ROUND(COALESCE(v_avg, 0.00), 2),
74
+ total_reviews = v_count
75
+ WHERE id = v_product_id;
76
+ END IF;
77
+
78
+ -- Handle old product_id if it changed on UPDATE (e.g. transfer of reviews)
79
+ IF TG_OP = 'UPDATE' AND OLD.product_id IS NOT NULL AND OLD.product_id <> NEW.product_id THEN
80
+ SELECT COALESCE(avg(rating), 0.00), count(*)
81
+ INTO v_avg, v_count
82
+ FROM public.cms_interactions
83
+ WHERE product_id = OLD.product_id
84
+ AND type = 'review'
85
+ AND status = 'approved';
86
+
87
+ UPDATE public.products
88
+ SET average_rating = ROUND(COALESCE(v_avg, 0.00), 2),
89
+ total_reviews = v_count
90
+ WHERE id = OLD.product_id;
91
+ END IF;
92
+
93
+ RETURN NULL;
94
+ END;
95
+ $$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = public;
96
+
97
+ -- 5. Attach trigger to interactions table
98
+ CREATE TRIGGER trigger_update_product_ratings
99
+ AFTER INSERT OR UPDATE OR DELETE
100
+ ON public.cms_interactions
101
+ FOR EACH ROW
102
+ EXECUTE FUNCTION public.update_product_ratings();
103
+
104
+ -- 6. Attach update_at timestamp trigger
105
+ CREATE TRIGGER set_updated_at
106
+ BEFORE UPDATE ON public.cms_interactions
107
+ FOR EACH ROW
108
+ EXECUTE FUNCTION public.set_current_timestamp_updated_at();
109
+
110
+ -- 7. Enable RLS and define policies
111
+ ALTER TABLE public.cms_interactions ENABLE ROW LEVEL SECURITY;
112
+
113
+ CREATE POLICY cms_interactions_read_policy
114
+ ON public.cms_interactions
115
+ FOR SELECT
116
+ TO public
117
+ USING (
118
+ status = 'approved'
119
+ OR ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
120
+ );
121
+
122
+ CREATE POLICY cms_interactions_insert_policy
123
+ ON public.cms_interactions
124
+ FOR INSERT
125
+ TO authenticated
126
+ WITH CHECK (
127
+ auth.uid() = user_id
128
+ AND (status = 'pending' OR (SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
129
+ );
130
+
131
+ CREATE POLICY cms_interactions_update_policy
132
+ ON public.cms_interactions
133
+ FOR UPDATE
134
+ TO authenticated
135
+ USING (
136
+ ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
137
+ )
138
+ WITH CHECK (
139
+ ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
140
+ );
141
+
142
+ CREATE POLICY cms_interactions_delete_policy
143
+ ON public.cms_interactions
144
+ FOR DELETE
145
+ TO authenticated
146
+ USING (
147
+ ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
148
+ );
149
+
150
+ -- 8. Grant Table Permissions
151
+ GRANT SELECT ON public.cms_interactions TO anon;
152
+ GRANT ALL ON public.cms_interactions TO authenticated;
153
+ GRANT ALL ON public.cms_interactions TO service_role;
@@ -0,0 +1,8 @@
1
+ -- 00000000000040_add_profiles_fk_to_interactions.sql
2
+ -- Alter cms_interactions.user_id to reference public.profiles directly for PostgREST joins.
3
+
4
+ ALTER TABLE public.cms_interactions
5
+ DROP CONSTRAINT IF EXISTS cms_interactions_user_id_fkey,
6
+ ADD CONSTRAINT cms_interactions_user_id_profiles_fkey FOREIGN KEY (user_id) REFERENCES public.profiles(id) ON DELETE CASCADE;
7
+
8
+ COMMENT ON COLUMN public.cms_interactions.user_id IS 'References public.profiles.id';
@@ -0,0 +1,145 @@
1
+ -- 00000000000041_seed_interactions_translations.sql
2
+ -- Adds translation strings for Product Reviews and Post Comments storefront modules.
3
+
4
+ BEGIN;
5
+
6
+ INSERT INTO public.translations (key, translations)
7
+ VALUES
8
+ (
9
+ 'reviews.customer_reviews',
10
+ '{"en": "Customer Reviews", "fr": "Avis clients"}'::jsonb
11
+ ),
12
+ (
13
+ 'reviews.share_thoughts',
14
+ '{"en": "Share your thoughts and experience with this product.", "fr": "Partagez vos impressions et votre expérience avec ce produit."}'::jsonb
15
+ ),
16
+ (
17
+ 'reviews.write_review',
18
+ '{"en": "Write a Review", "fr": "Rédiger un avis"}'::jsonb
19
+ ),
20
+ (
21
+ 'reviews.cancel_review',
22
+ '{"en": "Cancel Review", "fr": "Annuler l''avis"}'::jsonb
23
+ ),
24
+ (
25
+ 'reviews.login_to_write',
26
+ '{"en": "Please log in to write a review.", "fr": "Veuillez vous connecter pour rédiger un avis."}'::jsonb
27
+ ),
28
+ (
29
+ 'reviews.write_your_review',
30
+ '{"en": "Write your review", "fr": "Rédigez votre avis"}'::jsonb
31
+ ),
32
+ (
33
+ 'reviews.rating',
34
+ '{"en": "Rating", "fr": "Note"}'::jsonb
35
+ ),
36
+ (
37
+ 'reviews.description',
38
+ '{"en": "Review Description", "fr": "Description de l''avis"}'::jsonb
39
+ ),
40
+ (
41
+ 'reviews.description_placeholder',
42
+ '{"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
43
+ ),
44
+ (
45
+ 'reviews.submit_review',
46
+ '{"en": "Submit Review", "fr": "Soumettre l''avis"}'::jsonb
47
+ ),
48
+ (
49
+ 'reviews.submitting',
50
+ '{"en": "Submitting...", "fr": "Envoi en cours..."}'::jsonb
51
+ ),
52
+ (
53
+ 'reviews.success_pending',
54
+ '{"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
55
+ ),
56
+ (
57
+ 'reviews.cancel',
58
+ '{"en": "Cancel", "fr": "Annuler"}'::jsonb
59
+ ),
60
+ (
61
+ 'reviews.helpful',
62
+ '{"en": "Helpful", "fr": "Utile"}'::jsonb
63
+ ),
64
+ (
65
+ 'reviews.no_reviews',
66
+ '{"en": "No reviews yet", "fr": "Aucun avis pour le moment"}'::jsonb
67
+ ),
68
+ (
69
+ 'reviews.be_the_first',
70
+ '{"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
71
+ ),
72
+ (
73
+ 'reviews.load_more',
74
+ '{"en": "Load More Reviews", "fr": "Charger plus d''avis"}'::jsonb
75
+ ),
76
+ (
77
+ 'reviews.review_count_one',
78
+ '{"en": "{count} review", "fr": "{count} avis"}'::jsonb
79
+ ),
80
+ (
81
+ 'reviews.review_count_other',
82
+ '{"en": "{count} reviews", "fr": "{count} avis"}'::jsonb
83
+ ),
84
+ (
85
+ 'comments.discussion',
86
+ '{"en": "Discussion & Comments", "fr": "Discussion & Commentaires"}'::jsonb
87
+ ),
88
+ (
89
+ 'comments.join_conversation',
90
+ '{"en": "Join the conversation and express your thoughts.", "fr": "Rejoignez la conversation et exprimez vos pensées."}'::jsonb
91
+ ),
92
+ (
93
+ 'comments.write_comment',
94
+ '{"en": "Write a Comment", "fr": "Écrire un commentaire"}'::jsonb
95
+ ),
96
+ (
97
+ 'comments.cancel_comment',
98
+ '{"en": "Cancel Comment", "fr": "Annuler le commentaire"}'::jsonb
99
+ ),
100
+ (
101
+ 'comments.login_to_write',
102
+ '{"en": "Please log in to write a comment.", "fr": "Veuillez vous connecter pour écrire un commentaire."}'::jsonb
103
+ ),
104
+ (
105
+ 'comments.join_discussion',
106
+ '{"en": "Join the Discussion", "fr": "Rejoindre la discussion"}'::jsonb
107
+ ),
108
+ (
109
+ 'comments.your_message',
110
+ '{"en": "Your Message", "fr": "Votre message"}'::jsonb
111
+ ),
112
+ (
113
+ 'comments.message_placeholder',
114
+ '{"en": "What are your thoughts on this article?", "fr": "Quelles sont vos pensées sur cet article ?"}'::jsonb
115
+ ),
116
+ (
117
+ 'comments.post_comment',
118
+ '{"en": "Post Comment", "fr": "Publier le commentaire"}'::jsonb
119
+ ),
120
+ (
121
+ 'comments.success_pending',
122
+ '{"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
123
+ ),
124
+ (
125
+ 'comments.like',
126
+ '{"en": "Like", "fr": "J''aime"}'::jsonb
127
+ ),
128
+ (
129
+ 'comments.no_comments',
130
+ '{"en": "No comments yet", "fr": "Aucun commentaire pour le moment"}'::jsonb
131
+ ),
132
+ (
133
+ 'comments.be_the_first',
134
+ '{"en": "Be the first to share your thoughts!", "fr": "Soyez le premier à partager vos pensées !"}'::jsonb
135
+ ),
136
+ (
137
+ 'comments.load_more',
138
+ '{"en": "Load More Comments", "fr": "Charger plus de commentaires"}'::jsonb
139
+ )
140
+ ON CONFLICT (key) DO UPDATE
141
+ SET
142
+ translations = EXCLUDED.translations,
143
+ updated_at = now();
144
+
145
+ COMMIT;
@@ -0,0 +1,72 @@
1
+ -- Promote the one-click "Deploy to Vercel" button as a CTA in the seeded homepage.
2
+ --
3
+ -- Two placements per language, both rendered through the normal 'button' block
4
+ -- (ButtonBlockRenderer), so external URLs open in a new tab with rel=noopener:
5
+ -- 1. Home HERO — replace the secondary "View on GitHub" / "Voir sur GitHub"
6
+ -- outline button with a "Deploy on Vercel" / "Déployer sur Vercel" button
7
+ -- (same variant/size/position; the primary "Get Started" button is untouched,
8
+ -- and GitHub is still linked from the hero social row + the community section).
9
+ -- 2. Home CLOSING CTA band ("Have Questions?" / "Des questions ?") — append a
10
+ -- secondary (outline) "Deploy on Vercel" button after the contact button so
11
+ -- the page also ends on the deploy action, styled to match the hero's Deploy
12
+ -- button and keep the filled "Get in Touch" button as the band's primary.
13
+ --
14
+ -- The canonical deploy URL is the exact href from the README's Deploy button
15
+ -- (native Supabase Marketplace `stores` flow). It is stored as a raw button `url`
16
+ -- value (literal '&', not '&amp;') because the renderer uses it directly as an href.
17
+ --
18
+ -- Forward-only and idempotent:
19
+ -- * The hero swap matches the old button label, so a second run is a no-op
20
+ -- (the label no longer exists) and it silently skips any install where the
21
+ -- button was already customized or removed.
22
+ -- * The closing-band append is guarded by NOT LIKE '%Deploy on Vercel%' /
23
+ -- '%Déployer sur Vercel%', so it never appends twice.
24
+ -- Scoping is by unique block text, so exactly the home hero / closing band match.
25
+
26
+ -- ---------------------------------------------------------------------------
27
+ -- 1a. EN home hero: "View on GitHub" -> "Deploy on Vercel"
28
+ -- ---------------------------------------------------------------------------
29
+ UPDATE public.blocks
30
+ SET content = replace(
31
+ replace(content::text, '"View on GitHub"', '"Deploy on Vercel"'),
32
+ '"https://github.com/nextblock-cms/nextblock"',
33
+ '"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"'
34
+ )::jsonb
35
+ WHERE content::text LIKE '%"View on GitHub"%';
36
+
37
+ -- ---------------------------------------------------------------------------
38
+ -- 1b. FR home hero: "Voir sur GitHub" -> "Déployer sur Vercel"
39
+ -- ---------------------------------------------------------------------------
40
+ UPDATE public.blocks
41
+ SET content = replace(
42
+ replace(content::text, '"Voir sur GitHub"', '"Déployer sur Vercel"'),
43
+ '"https://github.com/nextblock-cms/nextblock"',
44
+ '"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"'
45
+ )::jsonb
46
+ WHERE content::text LIKE '%"Voir sur GitHub"%';
47
+
48
+ -- ---------------------------------------------------------------------------
49
+ -- 2a. EN home closing band ("Have Questions?"): append a Deploy on Vercel button
50
+ -- ---------------------------------------------------------------------------
51
+ UPDATE public.blocks
52
+ SET content = jsonb_set(
53
+ content,
54
+ '{column_blocks,0}',
55
+ (content #> '{column_blocks,0}') ||
56
+ '[{"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
57
+ )
58
+ WHERE content::text LIKE '%Have Questions?%'
59
+ AND content::text NOT LIKE '%Deploy on Vercel%';
60
+
61
+ -- ---------------------------------------------------------------------------
62
+ -- 2b. FR home closing band ("Des questions ?"): append a Déployer sur Vercel button
63
+ -- ---------------------------------------------------------------------------
64
+ UPDATE public.blocks
65
+ SET content = jsonb_set(
66
+ content,
67
+ '{column_blocks,0}',
68
+ (content #> '{column_blocks,0}') ||
69
+ '[{"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
70
+ )
71
+ WHERE content::text LIKE '%Des questions ?%'
72
+ AND content::text NOT LIKE '%Déployer sur Vercel%';
@@ -0,0 +1,42 @@
1
+ -- Swap the home HERO media from the static NBcover image to an embedded YouTube video.
2
+ --
3
+ -- The seeded home hero (migration 010) renders a "Why teams switch / 100% Lighthouse"
4
+ -- card in its right column whose last element is an <img src="/images/NBcover.webp" />.
5
+ -- This replaces just that <img> with a responsive 16:9 YouTube <iframe>, keeping the
6
+ -- surrounding card, its rounded/border/shadow wrapper, and every other block intact.
7
+ --
8
+ -- Rendering path: the hero right column is a 'text' block; TextBlockRenderer feeds the
9
+ -- html_content through html-react-parser, which preserves <iframe> (it only strips on*
10
+ -- handlers and swaps known CMS <img> for next/image). The proxy.ts CSP frame-src already
11
+ -- allows https://www.youtube.com, so the embed is not blocked.
12
+ --
13
+ -- Forward-only and idempotent:
14
+ -- * Each UPDATE matches the language-specific <img> alt text, so a second run finds no
15
+ -- match and is a no-op (the alt text no longer exists once swapped).
16
+ -- * String replace on content::text mirrors migration 042; the img markup is unique to
17
+ -- the home hero, so exactly the EN and FR home hero blocks are touched.
18
+ --
19
+ -- The NBcover.webp media row is left untouched — it is still the feature image for the
20
+ -- "how-nextblock-works" post; only the inline hero usage changes.
21
+
22
+ -- ---------------------------------------------------------------------------
23
+ -- 1. EN home hero: replace NBcover <img> with a YouTube <iframe>
24
+ -- ---------------------------------------------------------------------------
25
+ UPDATE public.blocks
26
+ SET content = replace(
27
+ content::text,
28
+ '<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'' />',
29
+ '<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>'
30
+ )::jsonb
31
+ WHERE content::text LIKE '%Nextblock cover showcasing dashboards and blocks%';
32
+
33
+ -- ---------------------------------------------------------------------------
34
+ -- 2. FR home hero: replace NBcover <img> with a YouTube <iframe>
35
+ -- ---------------------------------------------------------------------------
36
+ UPDATE public.blocks
37
+ SET content = replace(
38
+ content::text,
39
+ '<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'' />',
40
+ '<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>'
41
+ )::jsonb
42
+ WHERE content::text LIKE '%Couverture Nextblock%';
@@ -0,0 +1,66 @@
1
+ -- Swap the Nx-graph image in the "How NextBlock™ Works" article for an embedded
2
+ -- YouTube video, and rebalance that two-column section to an even 50/50 split.
3
+ --
4
+ -- Targets the post-body 'text' block (dollar-quoted html_content seeded in migration
5
+ -- 010) for both language variants of the same post:
6
+ -- * EN slug how-nextblock-works (img alt "Nx project graph preview ...")
7
+ -- * FR slug comment-nextblock-fonctionne (img alt "Apercu du graphe Nx ...")
8
+ --
9
+ -- Three forward-only, idempotent edits:
10
+ -- 1. Column widths md:w-3/5 (text) and md:w-2/5 (media) -> md:w-1/2 each, and the
11
+ -- flex row switches items-start -> items-center so the shorter media column sits
12
+ -- centered against the taller text column. These class fragments occur only in
13
+ -- this one section (EN + FR), so a single UPDATE rebalances both languages.
14
+ -- 2/3. Replace the <img src="/images/nx-graph.webp" ...> with a responsive 16:9
15
+ -- YouTube <iframe>, kept inside the existing white aside card above its caption.
16
+ --
17
+ -- Rendering/CSP identical to migration 043: TextBlockRenderer -> html-react-parser
18
+ -- preserves the <iframe>; proxy.ts CSP frame-src allows www.youtube.com; every utility
19
+ -- class used here lives in this .sql file, which Tailwind's content scanner
20
+ -- (libs/**/*.sql) compiles into the bundle. The nx-graph.webp media row is left intact.
21
+ --
22
+ -- Each UPDATE is guarded by a substring that only exists pre-swap, so a second run is a
23
+ -- no-op. Dollar-quoted string literals ($old$/$new$) avoid escaping the single-quoted
24
+ -- HTML attributes; the sandbox reset runs this via postgres db.unsafe() (no outer
25
+ -- dollar-quote wrapper), same as migration 010's dollar-quoted seed content.
26
+
27
+ -- ---------------------------------------------------------------------------
28
+ -- 1. Both languages: 3/5 + 2/5 columns -> 50/50, and vertical-center the row
29
+ -- ---------------------------------------------------------------------------
30
+ UPDATE public.blocks
31
+ SET content = replace(
32
+ replace(
33
+ replace(
34
+ content::text,
35
+ 'flex flex-col md:flex-row gap-8 items-start my-12',
36
+ 'flex flex-col md:flex-row gap-8 items-center my-12'
37
+ ),
38
+ 'w-full md:w-3/5 space-y-4',
39
+ 'w-full md:w-1/2 space-y-4'
40
+ ),
41
+ 'md:w-2/5 rounded-[2rem]',
42
+ 'md:w-1/2 rounded-[2rem]'
43
+ )::jsonb
44
+ WHERE content::text LIKE '%w-full md:w-3/5 space-y-4%';
45
+
46
+ -- ---------------------------------------------------------------------------
47
+ -- 2. EN post body: nx-graph <img> -> responsive YouTube <iframe>
48
+ -- ---------------------------------------------------------------------------
49
+ UPDATE public.blocks
50
+ SET content = replace(
51
+ content::text,
52
+ $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$,
53
+ $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$
54
+ )::jsonb
55
+ WHERE content::text LIKE '%Nx project graph preview showing apps and shared libraries linked together%';
56
+
57
+ -- ---------------------------------------------------------------------------
58
+ -- 3. FR post body: nx-graph <img> -> responsive YouTube <iframe>
59
+ -- ---------------------------------------------------------------------------
60
+ UPDATE public.blocks
61
+ SET content = replace(
62
+ content::text,
63
+ $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$,
64
+ $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$
65
+ )::jsonb
66
+ WHERE content::text LIKE '%Apercu du graphe Nx montrant les applications et librairies partagees%';