create-nextblock 0.2.61 → 0.2.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextblock",
3
- "version": "0.2.61",
3
+ "version": "0.2.64",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,6 +9,8 @@ import Link from "next/link";
9
9
  import { useTranslations } from "@nextblock-cms/utils";
10
10
  import { useSearchParams } from "next/navigation";
11
11
 
12
+ import { SandboxCredentialsAlert } from "../../../components/SandboxCredentialsAlert";
13
+
12
14
  function getMessage(searchParams: URLSearchParams): Message | undefined {
13
15
  if (searchParams.has('error')) {
14
16
  const error = searchParams.get('error');
@@ -31,7 +33,8 @@ export default function Login() {
31
33
  const formMessage = getMessage(searchParams);
32
34
 
33
35
  return (
34
- <form className="flex-1 flex flex-col min-w-64 mx-auto">
36
+ <form className="flex-1 flex flex-col w-full max-w-160 mx-auto">
37
+ <SandboxCredentialsAlert />
35
38
  <h1 className="text-2xl font-medium">{t('sign_in')}</h1>
36
39
  <p className="text-sm text-foreground">
37
40
  {t('dont_have_account')}{" "}
@@ -9,6 +9,8 @@ import Link from "next/link";
9
9
  import { useTranslations } from "@nextblock-cms/utils";
10
10
  import { useSearchParams } from "next/navigation";
11
11
 
12
+ import { SandboxCredentialsAlert } from "../../../components/SandboxCredentialsAlert";
13
+
12
14
  function getMessage(searchParams: URLSearchParams): Message | undefined {
13
15
  if (searchParams.has('error')) {
14
16
  const error = searchParams.get('error');
@@ -40,7 +42,8 @@ export default function Signup() {
40
42
 
41
43
  return (
42
44
  <>
43
- <form className="flex flex-col min-w-64 max-w-64 mx-auto">
45
+ <form className="flex flex-col w-full max-w-160 mx-auto">
46
+ <SandboxCredentialsAlert />
44
47
  <h1 className="text-2xl font-medium">{t('sign_up')}</h1>
45
48
  <p className="text-sm text text-foreground">
46
49
  {t('already_have_account')}{" "}
@@ -0,0 +1,47 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { createClient } from '@supabase/supabase-js';
3
+
4
+ export const dynamic = 'force-dynamic';
5
+
6
+ export async function GET(request: NextRequest) {
7
+ // 1. Guard: Only run in Sandbox Mode
8
+ if (process.env.NEXT_PUBLIC_IS_SANDBOX !== 'true') {
9
+ return NextResponse.json({ message: 'Sandbox reset skipped: Not in Sandbox Mode' });
10
+ }
11
+
12
+ // 2. Guard: Verify Cron Secret
13
+ const authHeader = request.headers.get('authorization');
14
+ if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
15
+ return new NextResponse('Unauthorized', {
16
+ status: 401,
17
+ });
18
+ }
19
+
20
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
21
+ const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
22
+
23
+ if (!supabaseUrl || !supabaseServiceKey) {
24
+ return NextResponse.json({ error: 'Missing Supabase environment variables' }, { status: 500 });
25
+ }
26
+
27
+ const supabase = createClient(supabaseUrl, supabaseServiceKey, {
28
+ auth: {
29
+ autoRefreshToken: false,
30
+ persistSession: false,
31
+ },
32
+ });
33
+
34
+ try {
35
+ const { error } = await supabase.rpc('reset_sandbox');
36
+
37
+ if (error) {
38
+ console.error('Error resetting sandbox:', error);
39
+ return NextResponse.json({ error: error.message }, { status: 500 });
40
+ }
41
+
42
+ return NextResponse.json({ success: true, message: 'Sandbox reset successfully' });
43
+ } catch (err) {
44
+ console.error('Unexpected error resetting sandbox:', err);
45
+ return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
46
+ }
47
+ }
@@ -114,6 +114,7 @@ export function BlockEditorModal({
114
114
 
115
115
  {/* Editor Area with Contextual Background */}
116
116
  <div
117
+ onClick={(e) => e.stopPropagation()}
117
118
  className={cn(
118
119
  "flex-1 overflow-y-auto p-6",
119
120
  // Conditional Background Logic:
@@ -2,6 +2,8 @@ import '@nextblock-cms/ui/styles/globals.css';
2
2
  import '@nextblock-cms/editor/styles/editor.css';
3
3
  // app/layout.tsx
4
4
  import { EnvVarWarning } from "@/components/env-var-warning";
5
+ import { SandboxBanner } from "@/components/SandboxBanner";
6
+ import { Analytics } from "@vercel/analytics/next"
5
7
  import { ThemeSwitcher } from '@/components/theme-switcher';
6
8
  import type { Metadata } from 'next';
7
9
  import Header from "@/components/Header";
@@ -172,6 +174,7 @@ export default async function RootLayout({
172
174
  <link rel="dns-prefetch" href="https://db.ppcppwsfnrptznvbxnsz.supabase.co" />
173
175
  <link rel="dns-prefetch" href="https://realtime.supabase.com" />
174
176
  <meta name="viewport" content="width=device-width, initial-scale=1" />
177
+ <Analytics/>
175
178
  </head>
176
179
  <body className="bg-background text-foreground min-h-screen flex flex-col">
177
180
  <Providers
@@ -183,6 +186,7 @@ export default async function RootLayout({
183
186
  translations={translations}
184
187
  nonce={nonce}
185
188
  >
189
+ {process.env.NEXT_PUBLIC_IS_SANDBOX === 'true' && <SandboxBanner />}
186
190
  <ToasterProvider />
187
191
  <div className="flex-1 w-full flex flex-col items-center">
188
192
  <nav className="w-full flex justify-center border-b border-b-foreground/10 h-16">
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+
3
+ import { TriangleAlert } from 'lucide-react';
4
+ import { useTranslations } from '@nextblock-cms/utils';
5
+
6
+ export function SandboxBanner() {
7
+ const { t } = useTranslations();
8
+
9
+ return (
10
+ <div className="w-full z-[100] bg-amber-500 text-amber-950 px-4 py-2 text-center text-sm font-medium shadow-md flex items-center justify-center gap-2">
11
+ <TriangleAlert className="w-4 h-4" />
12
+ <span>
13
+ {t('sandbox_mode_banner')}
14
+ </span>
15
+ </div>
16
+ );
17
+ }
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+
3
+ import { Info } from 'lucide-react';
4
+ import { useTranslations } from '@nextblock-cms/utils';
5
+
6
+ export function SandboxCredentialsAlert() {
7
+ const { t } = useTranslations();
8
+
9
+ // Only show in sandbox mode
10
+ if (process.env.NEXT_PUBLIC_IS_SANDBOX !== 'true') {
11
+ return null;
12
+ }
13
+
14
+ return (
15
+ <div className="bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800 rounded-lg p-4 mt-6 flex items-start gap-3 text-sm">
16
+ <Info className="w-5 h-5 text-blue-600 dark:text-blue-400 shrink-0 mt-0.5" />
17
+ <div className="text-blue-900 dark:text-blue-100">
18
+ <p className="font-semibold mb-1">{t('demo_access_title')}</p>
19
+ <p className="mb-2">
20
+ {t('demo_access_desc')}
21
+ </p>
22
+ <ul className="space-y-1 font-mono text-xs bg-white/50 dark:bg-black/20 p-2 rounded border border-blue-100 dark:border-blue-900/50">
23
+ <li>{t('demo_user_label')} <span className="font-bold select-all">demo@nextblock.ca</span></li>
24
+ <li>{t('demo_password_label')} <span className="font-bold select-all">password</span></li>
25
+ </ul>
26
+ </div>
27
+ </div>
28
+ );
29
+ }
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/types/routes.d.ts";
3
+ import "./.next/dev/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@nextblock-cms/template",
3
- "version": "0.2.39",
3
+ "version": "0.2.42",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "dev": "next dev",
7
7
  "build": "next build",
8
8
  "start": "next start",
9
- "lint": "next lint"
9
+ "lint": "next lint",
10
+ "deploy:supabase": "node tools/deploy-supabase.js"
10
11
  },
11
12
  "dependencies": {
12
13
  "@aws-sdk/client-s3": "^3.920.0",
@@ -214,13 +214,13 @@ export default async function proxy(request: NextRequest) {
214
214
 
215
215
  const csp = [
216
216
  "default-src 'self'",
217
- `script-src 'self' blob: data: 'nonce-${nonceValue}'`,
218
- "style-src 'self' 'unsafe-inline'",
219
- `img-src 'self' data: blob:${r2Hostnames}`,
220
- "font-src 'self'",
217
+ `script-src 'self' blob: data: 'nonce-${nonceValue}' https://vercel.live https://vercel.com`,
218
+ "style-src 'self' 'unsafe-inline' https://vercel.live https://vercel.com",
219
+ `img-src 'self' data: blob:${r2Hostnames} https://vercel.live https://vercel.com`,
220
+ "font-src 'self' https://vercel.live https://assets.vercel.com",
221
221
  "object-src 'none'",
222
- `connect-src 'self' https://${supabaseHostname} wss://${supabaseHostname}${r2Hostnames}`,
223
- "frame-src 'self' blob: data: https://www.youtube.com",
222
+ `connect-src 'self' https://${supabaseHostname} wss://${supabaseHostname}${r2Hostnames} https://vercel.live https://vercel.com`,
223
+ "frame-src 'self' blob: data: https://www.youtube.com https://vercel.live https://vercel.com",
224
224
  "form-action 'self'",
225
225
  "base-uri 'self'",
226
226
  ].join('; ');
@@ -0,0 +1,161 @@
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ // Colors for console output
6
+ const colors = {
7
+ reset: '\x1b[0m',
8
+ green: '\x1b[32m',
9
+ yellow: '\x1b[33m',
10
+ red: '\x1b[31m',
11
+ blue: '\x1b[34m',
12
+ };
13
+
14
+ function getDbPassword() {
15
+ if (process.env.SUPABASE_DB_PASSWORD) {
16
+ return process.env.SUPABASE_DB_PASSWORD;
17
+ }
18
+ if (process.env.POSTGRES_URL) {
19
+ try {
20
+ const url = new URL(process.env.POSTGRES_URL);
21
+ return url.password;
22
+ } catch (e) {
23
+ return null;
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+
29
+ function checkEnv() {
30
+ const missing = [];
31
+
32
+ if (!process.env.SUPABASE_ACCESS_TOKEN) {
33
+ missing.push('SUPABASE_ACCESS_TOKEN');
34
+ }
35
+ if (!process.env.SUPABASE_PROJECT_ID) {
36
+ missing.push('SUPABASE_PROJECT_ID');
37
+ }
38
+ if (!process.env.NEXT_PUBLIC_URL) {
39
+ missing.push('NEXT_PUBLIC_URL');
40
+ }
41
+
42
+ const dbPassword = getDbPassword();
43
+ if (!dbPassword) {
44
+ missing.push('SUPABASE_DB_PASSWORD (or POSTGRES_URL)');
45
+ }
46
+
47
+ if (missing.length > 0) {
48
+ console.log(
49
+ `${colors.yellow}⚠️ Skipping Supabase deployment: Missing environment variables: ${missing.join(', ')}${colors.reset}`,
50
+ );
51
+ console.log(
52
+ `${colors.yellow} This is expected for Pull Requests or forks without secrets configured.${colors.reset}`,
53
+ );
54
+ return false;
55
+ }
56
+ return true;
57
+ }
58
+
59
+ function runCommand(command) {
60
+ try {
61
+ console.log(`${colors.blue}Running: ${command}${colors.reset}`);
62
+ execSync(command, { stdio: 'inherit' });
63
+ } catch (error) {
64
+ console.error(`${colors.red}❌ Command failed: ${command}${colors.reset}`);
65
+ process.exit(1);
66
+ }
67
+ }
68
+
69
+ async function deploy() {
70
+ console.log(
71
+ `${colors.green}🚀 Starting Supabase Deployment...${colors.reset}`,
72
+ );
73
+
74
+ if (!checkEnv()) {
75
+ process.exit(0);
76
+ }
77
+
78
+ const dbPassword = getDbPassword();
79
+ if (!dbPassword) {
80
+ console.error(
81
+ `${colors.red}❌ Could not determine database password.${colors.reset}`,
82
+ );
83
+ process.exit(1);
84
+ }
85
+
86
+ // Detect Supabase location
87
+ // 1. Monorepo Dev (apps/nextblock -> libs/db/src)
88
+ // 2. Standalone (root -> supabase)
89
+ let workDirFlag = '';
90
+
91
+ // Check if we are in monorepo structure relative to this script
92
+ // Script is in apps/nextblock/tools/deploy-supabase.js
93
+ // So monorepo root is ../../../
94
+ // libs/db/src is ../../../libs/db/src
95
+
96
+ // More robust check: check if libs/db/src/supabase/config.toml exists
97
+ // We assume the command is run from the project root (process.cwd())
98
+
99
+ // If running from apps/nextblock root (monorepo dev)
100
+ const monorepoDbPath = path.join(
101
+ process.cwd(),
102
+ '../../libs/db/src/supabase/config.toml',
103
+ );
104
+
105
+ // If running from workspace root (standalone)
106
+ const standaloneDbPath = path.join(process.cwd(), 'supabase/config.toml');
107
+
108
+ if (fs.existsSync(monorepoDbPath)) {
109
+ console.log(
110
+ `${colors.blue}ℹ️ Detected Monorepo environment (libs/db/src)${colors.reset}`,
111
+ );
112
+ workDirFlag = '--workdir ../../libs/db/src';
113
+ } else if (fs.existsSync(standaloneDbPath)) {
114
+ console.log(
115
+ `${colors.blue}ℹ️ Detected Standalone environment (./supabase)${colors.reset}`,
116
+ );
117
+ workDirFlag = '';
118
+ } else {
119
+ // Fallback or maybe we are running from root of monorepo?
120
+ const rootDbPath = path.join(
121
+ process.cwd(),
122
+ 'libs/db/src/supabase/config.toml',
123
+ );
124
+ if (fs.existsSync(rootDbPath)) {
125
+ workDirFlag = '--workdir libs/db/src';
126
+ }
127
+ }
128
+
129
+ console.log(
130
+ `${colors.green}🔗 Linking to Supabase project...${colors.reset}`,
131
+ );
132
+ runCommand(
133
+ `npx supabase link --project-ref ${process.env.SUPABASE_PROJECT_ID} --password ${dbPassword} ${workDirFlag}`,
134
+ );
135
+
136
+ console.log(
137
+ `${colors.green}📦 Pushing database migrations...${colors.reset}`,
138
+ );
139
+ runCommand(`npx supabase db push --include-all ${workDirFlag}`, {
140
+ stdio: 'inherit',
141
+ }); // db push input 'y' handled?
142
+ // npx supabase db push usually asks for confirmation if destructive. --include-all might implies force? using input 'y' might be safer or --force if avail.
143
+ // Actually, CI usually needs --no-interactive or equivalent if prompts exist.
144
+ // But our previous script used just db push without input 'y' and relied on process.stdin or just worked.
145
+ // Wait, in create-nextblock.js we piped 'y\n'.
146
+ // In deploy-supabase.js we are using inherit stdio, so it might prompt if strictly necessary, but usually db push to remote is fine unless destructive.
147
+ // However, I previously wrote `npx supabase db push ...` and it seemed fine.
148
+
149
+ // Re-reading previous script: runCommand used `execSync(command, { stdio: 'inherit' });`
150
+
151
+ // Let's stick to the previous working command.
152
+
153
+ console.log(
154
+ `${colors.green}⚙️ Pushing Supabase config (Site URL: ${process.env.NEXT_PUBLIC_URL})...${colors.reset}`,
155
+ );
156
+ runCommand(`npx supabase config push ${workDirFlag}`);
157
+
158
+ console.log(`${colors.green}✅ Supabase deployment complete!${colors.reset}`);
159
+ }
160
+
161
+ deploy();