create-nextblock 0.0.1
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/bin/create-nextblock.js +997 -0
- package/package.json +25 -0
- package/scripts/sync-template.js +284 -0
- package/templates/nextblock-template/.env.example +37 -0
- package/templates/nextblock-template/.swcrc +30 -0
- package/templates/nextblock-template/README.md +194 -0
- package/templates/nextblock-template/app/(auth-pages)/forgot-password/page.tsx +57 -0
- package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -0
- package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +28 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +67 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +70 -0
- package/templates/nextblock-template/app/ToasterProvider.tsx +17 -0
- package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +147 -0
- package/templates/nextblock-template/app/[slug]/page.tsx +145 -0
- package/templates/nextblock-template/app/[slug]/page.utils.ts +183 -0
- package/templates/nextblock-template/app/actions/email.ts +31 -0
- package/templates/nextblock-template/app/actions/formActions.ts +65 -0
- package/templates/nextblock-template/app/actions/languageActions.ts +130 -0
- package/templates/nextblock-template/app/actions/postActions.ts +80 -0
- package/templates/nextblock-template/app/actions.ts +146 -0
- package/templates/nextblock-template/app/api/process-image/route.ts +210 -0
- package/templates/nextblock-template/app/api/revalidate/route.ts +86 -0
- package/templates/nextblock-template/app/api/revalidate-log/route.ts +23 -0
- package/templates/nextblock-template/app/api/upload/presigned-url/route.ts +106 -0
- package/templates/nextblock-template/app/api/upload/proxy/route.ts +84 -0
- package/templates/nextblock-template/app/auth/callback/route.ts +58 -0
- package/templates/nextblock-template/app/blog/[slug]/PostClientContent.tsx +169 -0
- package/templates/nextblock-template/app/blog/[slug]/page.tsx +177 -0
- package/templates/nextblock-template/app/blog/[slug]/page.utils.ts +136 -0
- package/templates/nextblock-template/app/blog/page.tsx +77 -0
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +321 -0
- package/templates/nextblock-template/app/cms/blocks/actions.ts +434 -0
- package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +348 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +567 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +98 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +58 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +62 -0
- package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +276 -0
- package/templates/nextblock-template/app/cms/blocks/components/DeleteBlockButtonClient.tsx +47 -0
- package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +182 -0
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +120 -0
- package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +133 -0
- package/templates/nextblock-template/app/cms/blocks/components/SortableBlockItem.tsx +46 -0
- package/templates/nextblock-template/app/cms/blocks/editors/ButtonBlockEditor.tsx +85 -0
- package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +182 -0
- package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +111 -0
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +150 -0
- package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +79 -0
- package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +337 -0
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +81 -0
- package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +64 -0
- package/templates/nextblock-template/app/cms/components/ConfirmationModal.tsx +51 -0
- package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +145 -0
- package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +203 -0
- package/templates/nextblock-template/app/cms/components/LanguageFilterSelect.tsx +69 -0
- package/templates/nextblock-template/app/cms/dashboard/page.tsx +247 -0
- package/templates/nextblock-template/app/cms/layout.tsx +10 -0
- package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -0
- package/templates/nextblock-template/app/cms/media/[id]/edit/page.tsx +80 -0
- package/templates/nextblock-template/app/cms/media/actions.ts +577 -0
- package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +53 -0
- package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -0
- package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -0
- package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +157 -0
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +275 -0
- package/templates/nextblock-template/app/cms/media/components/MediaImage.tsx +70 -0
- package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +195 -0
- package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +362 -0
- package/templates/nextblock-template/app/cms/media/page.tsx +120 -0
- package/templates/nextblock-template/app/cms/navigation/[id]/edit/page.tsx +101 -0
- package/templates/nextblock-template/app/cms/navigation/actions.ts +358 -0
- package/templates/nextblock-template/app/cms/navigation/components/DeleteNavItemButton.tsx +52 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +248 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationLanguageSwitcher.tsx +132 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +701 -0
- package/templates/nextblock-template/app/cms/navigation/components/SortableNavItem.tsx +98 -0
- package/templates/nextblock-template/app/cms/navigation/new/page.tsx +26 -0
- package/templates/nextblock-template/app/cms/navigation/page.tsx +102 -0
- package/templates/nextblock-template/app/cms/navigation/utils.ts +51 -0
- package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +121 -0
- package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -0
- package/templates/nextblock-template/app/cms/pages/actions.ts +241 -0
- package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +47 -0
- package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +253 -0
- package/templates/nextblock-template/app/cms/pages/new/page.tsx +52 -0
- package/templates/nextblock-template/app/cms/pages/page.tsx +232 -0
- package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +183 -0
- package/templates/nextblock-template/app/cms/posts/actions.ts +309 -0
- package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +55 -0
- package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +419 -0
- package/templates/nextblock-template/app/cms/posts/new/page.tsx +21 -0
- package/templates/nextblock-template/app/cms/posts/page.tsx +192 -0
- package/templates/nextblock-template/app/cms/revisions/JsonDiffView.tsx +86 -0
- package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +201 -0
- package/templates/nextblock-template/app/cms/revisions/actions.ts +84 -0
- package/templates/nextblock-template/app/cms/revisions/service.ts +344 -0
- package/templates/nextblock-template/app/cms/revisions/utils.ts +127 -0
- package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +68 -0
- package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +78 -0
- package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +32 -0
- package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +117 -0
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +216 -0
- package/templates/nextblock-template/app/cms/settings/languages/[id]/edit/page.tsx +77 -0
- package/templates/nextblock-template/app/cms/settings/languages/actions.ts +261 -0
- package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +76 -0
- package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +167 -0
- package/templates/nextblock-template/app/cms/settings/languages/new/page.tsx +34 -0
- package/templates/nextblock-template/app/cms/settings/languages/page.tsx +156 -0
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +19 -0
- package/templates/nextblock-template/app/cms/settings/logos/actions.ts +114 -0
- package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +177 -0
- package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +11 -0
- package/templates/nextblock-template/app/cms/settings/logos/page.tsx +118 -0
- package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -0
- package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +91 -0
- package/templates/nextblock-template/app/cms/users/actions.ts +156 -0
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +71 -0
- package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +138 -0
- package/templates/nextblock-template/app/cms/users/page.tsx +183 -0
- package/templates/nextblock-template/app/favicon.ico +0 -0
- package/templates/nextblock-template/app/globals.css +401 -0
- package/templates/nextblock-template/app/layout.tsx +191 -0
- package/templates/nextblock-template/app/lib/sitemap-utils.ts +68 -0
- package/templates/nextblock-template/app/page.tsx +109 -0
- package/templates/nextblock-template/app/providers.tsx +43 -0
- package/templates/nextblock-template/app/robots.txt/route.ts +19 -0
- package/templates/nextblock-template/app/sitemap.xml/route.ts +63 -0
- package/templates/nextblock-template/app/unauthorized/page.tsx +27 -0
- package/templates/nextblock-template/backup/backup_2025-06-19.sql +8057 -0
- package/templates/nextblock-template/backup/backup_2025-06-20.sql +8159 -0
- package/templates/nextblock-template/backup/backup_2025-07-08.sql +8411 -0
- package/templates/nextblock-template/backup/backup_2025-07-09.sql +8442 -0
- package/templates/nextblock-template/backup/backup_2025-07-10.sql +8442 -0
- package/templates/nextblock-template/backup/backup_2025-10-01.sql +8803 -0
- package/templates/nextblock-template/backup/backup_2025-10-02.sql +9749 -0
- package/templates/nextblock-template/components/BlockRenderer.tsx +119 -0
- package/templates/nextblock-template/components/FooterNavigation.tsx +33 -0
- package/templates/nextblock-template/components/Header.tsx +42 -0
- package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -0
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +103 -0
- package/templates/nextblock-template/components/ResponsiveNav.tsx +372 -0
- package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +17 -0
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +93 -0
- package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +180 -0
- package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -0
- package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +69 -0
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +98 -0
- package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +41 -0
- package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +240 -0
- package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +79 -0
- package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +33 -0
- package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +189 -0
- package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +31 -0
- package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +59 -0
- package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +51 -0
- package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +40 -0
- package/templates/nextblock-template/components/blocks/types.ts +8 -0
- package/templates/nextblock-template/components/env-var-warning.tsx +33 -0
- package/templates/nextblock-template/components/form-message.tsx +26 -0
- package/templates/nextblock-template/components/header-auth.tsx +71 -0
- package/templates/nextblock-template/components/submit-button.tsx +23 -0
- package/templates/nextblock-template/components/theme-switcher.tsx +78 -0
- package/templates/nextblock-template/context/AuthContext.tsx +138 -0
- package/templates/nextblock-template/context/CurrentContentContext.tsx +42 -0
- package/templates/nextblock-template/context/LanguageContext.tsx +206 -0
- package/templates/nextblock-template/docs/cms-application-overview.md +56 -0
- package/templates/nextblock-template/docs/cms-architecture-overview.md +73 -0
- package/templates/nextblock-template/docs/files-structure.md +426 -0
- package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +174 -0
- package/templates/nextblock-template/eslint.config.mjs +28 -0
- package/templates/nextblock-template/index.d.ts +5 -0
- package/templates/nextblock-template/lib/blocks/README.md +670 -0
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +1001 -0
- package/templates/nextblock-template/lib/ui/ColorPicker.ts +1 -0
- package/templates/nextblock-template/lib/ui/ConfirmationDialog.ts +1 -0
- package/templates/nextblock-template/lib/ui/CustomSelectWithInput.ts +1 -0
- package/templates/nextblock-template/lib/ui/Skeleton.ts +1 -0
- package/templates/nextblock-template/lib/ui/avatar.ts +1 -0
- package/templates/nextblock-template/lib/ui/badge.ts +1 -0
- package/templates/nextblock-template/lib/ui/button.ts +1 -0
- package/templates/nextblock-template/lib/ui/card.ts +1 -0
- package/templates/nextblock-template/lib/ui/checkbox.ts +1 -0
- package/templates/nextblock-template/lib/ui/dialog.ts +1 -0
- package/templates/nextblock-template/lib/ui/dropdown-menu.ts +1 -0
- package/templates/nextblock-template/lib/ui/input.ts +1 -0
- package/templates/nextblock-template/lib/ui/label.ts +1 -0
- package/templates/nextblock-template/lib/ui/popover.ts +1 -0
- package/templates/nextblock-template/lib/ui/progress.ts +1 -0
- package/templates/nextblock-template/lib/ui/select.ts +1 -0
- package/templates/nextblock-template/lib/ui/separator.ts +1 -0
- package/templates/nextblock-template/lib/ui/table.ts +1 -0
- package/templates/nextblock-template/lib/ui/textarea.ts +1 -0
- package/templates/nextblock-template/lib/ui/tooltip.ts +1 -0
- package/templates/nextblock-template/lib/ui/ui.ts +1 -0
- package/templates/nextblock-template/middleware.ts +206 -0
- package/templates/nextblock-template/next-env.d.ts +6 -0
- package/templates/nextblock-template/next.config.js +99 -0
- package/templates/nextblock-template/package.json +52 -0
- package/templates/nextblock-template/postcss.config.js +6 -0
- package/templates/nextblock-template/project.json +7 -0
- package/templates/nextblock-template/public/.gitkeep +0 -0
- package/templates/nextblock-template/scripts/backfill-image-meta.ts +149 -0
- package/templates/nextblock-template/scripts/backup.js +53 -0
- package/templates/nextblock-template/scripts/test-bundle-optimization.js +114 -0
- package/templates/nextblock-template/tailwind.config.ts +19 -0
- package/templates/nextblock-template/tsconfig.json +62 -0
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-nextblock",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-nextblock": "./bin/create-nextblock.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"sync-template": "node ./scripts/sync-template.js",
|
|
12
|
+
"prepack": "npm run sync-template"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [],
|
|
15
|
+
"author": "",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"chalk": "^5.6.2",
|
|
20
|
+
"commander": "^14.0.1",
|
|
21
|
+
"fs-extra": "^11.3.2",
|
|
22
|
+
"inquirer": "^12.10.0",
|
|
23
|
+
"ora": "^8.0.1"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { resolve, relative, sep, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const PROJECT_ROOT = resolve(__dirname, '..');
|
|
11
|
+
const SOURCE_DIR = resolve(PROJECT_ROOT, '../nextblock');
|
|
12
|
+
const TARGET_DIR = resolve(PROJECT_ROOT, 'templates/nextblock-template');
|
|
13
|
+
const REPO_ROOT = resolve(PROJECT_ROOT, '..', '..');
|
|
14
|
+
const UI_GLOBALS_SOURCE = resolve(PROJECT_ROOT, '../../libs/ui/src/styles/globals.css');
|
|
15
|
+
const BACKUP_SOURCE_DIR = resolve(SOURCE_DIR, 'backup');
|
|
16
|
+
const BACKUP_TARGET_DIR = resolve(TARGET_DIR, 'backup');
|
|
17
|
+
const UI_PROXY_MODULES = [
|
|
18
|
+
'avatar',
|
|
19
|
+
'badge',
|
|
20
|
+
'button',
|
|
21
|
+
'card',
|
|
22
|
+
'checkbox',
|
|
23
|
+
'ColorPicker',
|
|
24
|
+
'ConfirmationDialog',
|
|
25
|
+
'CustomSelectWithInput',
|
|
26
|
+
'dialog',
|
|
27
|
+
'dropdown-menu',
|
|
28
|
+
'input',
|
|
29
|
+
'label',
|
|
30
|
+
'popover',
|
|
31
|
+
'progress',
|
|
32
|
+
'select',
|
|
33
|
+
'separator',
|
|
34
|
+
'Skeleton',
|
|
35
|
+
'table',
|
|
36
|
+
'textarea',
|
|
37
|
+
'tooltip',
|
|
38
|
+
'ui',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const IGNORED_SEGMENTS = new Set([
|
|
42
|
+
'node_modules',
|
|
43
|
+
'.git',
|
|
44
|
+
'.next',
|
|
45
|
+
'dist',
|
|
46
|
+
'tmp',
|
|
47
|
+
'coverage',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
async function ensureTemplateSync() {
|
|
51
|
+
const sourceExists = await fs.pathExists(SOURCE_DIR);
|
|
52
|
+
if (!sourceExists) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Source project not found at ${SOURCE_DIR}. Please ensure apps/nextblock exists before syncing.`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(
|
|
59
|
+
chalk.blue(
|
|
60
|
+
`Syncing template from ${chalk.bold(relative(PROJECT_ROOT, SOURCE_DIR))} to ${chalk.bold(
|
|
61
|
+
relative(PROJECT_ROOT, TARGET_DIR),
|
|
62
|
+
)}`,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await fs.ensureDir(TARGET_DIR);
|
|
67
|
+
await fs.emptyDir(TARGET_DIR);
|
|
68
|
+
|
|
69
|
+
await fs.copy(SOURCE_DIR, TARGET_DIR, {
|
|
70
|
+
dereference: true,
|
|
71
|
+
filter: (src) => {
|
|
72
|
+
const rel = relative(SOURCE_DIR, src);
|
|
73
|
+
if (!rel) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const segments = rel.split(sep);
|
|
78
|
+
return segments.every((segment) => !IGNORED_SEGMENTS.has(segment));
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await ensureEnvExample();
|
|
83
|
+
await ensureGlobalStyles();
|
|
84
|
+
await ensureClientTranslations();
|
|
85
|
+
await sanitizeBlockEditorImports();
|
|
86
|
+
await sanitizeUiImports();
|
|
87
|
+
await ensureUiProxies();
|
|
88
|
+
await ensureBackups();
|
|
89
|
+
await ensureTemplateProjectJson();
|
|
90
|
+
|
|
91
|
+
console.log(chalk.green('Template sync complete.'));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function ensureEnvExample() {
|
|
95
|
+
const envTargets = [
|
|
96
|
+
resolve(REPO_ROOT, '.env.example'),
|
|
97
|
+
resolve(REPO_ROOT, '.env.exemple'),
|
|
98
|
+
resolve(SOURCE_DIR, '.env.example'),
|
|
99
|
+
resolve(SOURCE_DIR, '.env.exemple'),
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const destination = resolve(TARGET_DIR, '.env.example');
|
|
103
|
+
|
|
104
|
+
for (const envPath of envTargets) {
|
|
105
|
+
if (await fs.pathExists(envPath)) {
|
|
106
|
+
await fs.copy(envPath, destination);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const placeholder = `# Environment variables for NextBlock CMS
|
|
112
|
+
NEXT_PUBLIC_SUPABASE_URL=
|
|
113
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
|
114
|
+
SUPABASE_SERVICE_ROLE_KEY=
|
|
115
|
+
SUPABASE_JWT_SECRET=
|
|
116
|
+
NEXT_PUBLIC_URL=http://localhost:3000
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
await fs.writeFile(destination, placeholder);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function ensureGlobalStyles() {
|
|
123
|
+
const destination = resolve(TARGET_DIR, 'app/globals.css');
|
|
124
|
+
|
|
125
|
+
if (await fs.pathExists(destination)) {
|
|
126
|
+
await fs.remove(destination);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (await fs.pathExists(UI_GLOBALS_SOURCE)) {
|
|
130
|
+
await fs.copy(UI_GLOBALS_SOURCE, destination);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const fallback = `@tailwind base;
|
|
135
|
+
@tailwind components;
|
|
136
|
+
@tailwind utilities;
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
await fs.outputFile(destination, fallback);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function ensureClientTranslations() {
|
|
143
|
+
const providersPath = resolve(TARGET_DIR, 'app/providers.tsx');
|
|
144
|
+
if (!(await fs.pathExists(providersPath))) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let content = await fs.readFile(providersPath, 'utf8');
|
|
149
|
+
const wrapperImportPath = '@nextblock-cms/utils';
|
|
150
|
+
const wrapperImportStatement = `import { TranslationsProvider } from '${wrapperImportPath}';`;
|
|
151
|
+
const existingImportRegex =
|
|
152
|
+
/import\s+\{\s*TranslationsProvider\s*\}\s*from\s*['"]@nextblock-cms\/utils['"];?/;
|
|
153
|
+
const legacyImportRegex =
|
|
154
|
+
/import\s+\{\s*TranslationsProvider\s*\}\s*from\s*['"]@\/lib\/client-translations['"];?/;
|
|
155
|
+
|
|
156
|
+
if (existingImportRegex.test(content) || legacyImportRegex.test(content)) {
|
|
157
|
+
content = content
|
|
158
|
+
.replace(existingImportRegex, wrapperImportStatement)
|
|
159
|
+
.replace(legacyImportRegex, wrapperImportStatement);
|
|
160
|
+
} else if (!content.includes(wrapperImportStatement)) {
|
|
161
|
+
const lines = content.split(/\r?\n/);
|
|
162
|
+
const insertIndex = lines.findIndex((line) => line.startsWith('import')) + 1;
|
|
163
|
+
if (insertIndex > 0) {
|
|
164
|
+
lines.splice(insertIndex, 0, wrapperImportStatement);
|
|
165
|
+
content = lines.join('\n');
|
|
166
|
+
} else {
|
|
167
|
+
content = `${wrapperImportStatement}\n${content}`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await fs.writeFile(providersPath, content);
|
|
172
|
+
|
|
173
|
+
const wrapperPath = resolve(TARGET_DIR, 'lib/client-translations.tsx');
|
|
174
|
+
if (await fs.pathExists(wrapperPath)) {
|
|
175
|
+
await fs.remove(wrapperPath);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function sanitizeBlockEditorImports() {
|
|
180
|
+
const blockEditorPath = resolve(TARGET_DIR, 'app/cms/blocks/components/BlockEditorArea.tsx');
|
|
181
|
+
if (!(await fs.pathExists(blockEditorPath))) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const replacements = [
|
|
186
|
+
{ pattern: /(\.\.\/editors\/[A-Za-z0-9_-]+)\.js/g, replacement: '$1.tsx' },
|
|
187
|
+
{ pattern: /(\.\.\/actions)\.js/g, replacement: '$1.ts' },
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
const content = await fs.readFile(blockEditorPath, 'utf8');
|
|
191
|
+
let updated = content;
|
|
192
|
+
|
|
193
|
+
for (const { pattern, replacement } of replacements) {
|
|
194
|
+
updated = updated.replace(pattern, replacement);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (updated !== content) {
|
|
198
|
+
await fs.writeFile(blockEditorPath, updated);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function sanitizeUiImports() {
|
|
203
|
+
const searchDirs = ['app', 'components', 'context', 'lib'];
|
|
204
|
+
const validExtensions = new Set(['.ts', '.tsx', '.js', '.jsx']);
|
|
205
|
+
const filesToProcess = [];
|
|
206
|
+
|
|
207
|
+
for (const relativeDir of searchDirs) {
|
|
208
|
+
const absoluteDir = resolve(TARGET_DIR, relativeDir);
|
|
209
|
+
if (await fs.pathExists(absoluteDir)) {
|
|
210
|
+
await collectFiles(absoluteDir, filesToProcess, validExtensions);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const filePath of filesToProcess) {
|
|
215
|
+
const original = await fs.readFile(filePath, 'utf8');
|
|
216
|
+
const replaced = original.replace(/@nextblock-cms\/ui\/(?!styles\/)[A-Za-z0-9/_-]+/g, '@nextblock-cms/ui');
|
|
217
|
+
|
|
218
|
+
if (replaced !== original) {
|
|
219
|
+
await fs.writeFile(filePath, replaced);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function collectFiles(directory, accumulator, extensions) {
|
|
225
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
226
|
+
for (const entry of entries) {
|
|
227
|
+
const fullPath = resolve(directory, entry.name);
|
|
228
|
+
if (entry.isDirectory()) {
|
|
229
|
+
await collectFiles(fullPath, accumulator, extensions);
|
|
230
|
+
} else {
|
|
231
|
+
const dotIndex = entry.name.lastIndexOf('.');
|
|
232
|
+
if (dotIndex !== -1) {
|
|
233
|
+
const ext = entry.name.slice(dotIndex);
|
|
234
|
+
if (extensions.has(ext)) {
|
|
235
|
+
accumulator.push(fullPath);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function ensureUiProxies() {
|
|
243
|
+
const proxiesDir = resolve(TARGET_DIR, 'lib/ui');
|
|
244
|
+
await fs.ensureDir(proxiesDir);
|
|
245
|
+
|
|
246
|
+
const proxyContent = "export * from '@nextblock-cms/ui';\n";
|
|
247
|
+
|
|
248
|
+
for (const moduleName of UI_PROXY_MODULES) {
|
|
249
|
+
const proxyPath = resolve(proxiesDir, `${moduleName}.ts`);
|
|
250
|
+
if (!(await fs.pathExists(proxyPath))) {
|
|
251
|
+
await fs.outputFile(proxyPath, proxyContent);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function ensureBackups() {
|
|
257
|
+
if (!(await fs.pathExists(BACKUP_SOURCE_DIR))) {
|
|
258
|
+
await fs.remove(BACKUP_TARGET_DIR).catch(() => undefined);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
await fs.ensureDir(BACKUP_TARGET_DIR);
|
|
263
|
+
await fs.copy(BACKUP_SOURCE_DIR, BACKUP_TARGET_DIR, {
|
|
264
|
+
dereference: true,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function ensureTemplateProjectJson() {
|
|
269
|
+
const projectJsonPath = resolve(TARGET_DIR, 'project.json');
|
|
270
|
+
const minimalConfig = {
|
|
271
|
+
name: 'nextblock-template',
|
|
272
|
+
projectType: 'application',
|
|
273
|
+
root: 'apps/create-nextblock/templates/nextblock-template',
|
|
274
|
+
sourceRoot: 'apps/create-nextblock/templates/nextblock-template',
|
|
275
|
+
targets: {},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
await fs.writeJSON(projectJsonPath, minimalConfig, { spaces: 2 });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
ensureTemplateSync().catch((error) => {
|
|
282
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
283
|
+
process.exit(1);
|
|
284
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
NEXT_PUBLIC_URL=
|
|
2
|
+
# Vercel / Supabase
|
|
3
|
+
SUPABASE_PROJECT_ID=
|
|
4
|
+
POSTGRES_URL=
|
|
5
|
+
POSTGRES_PRISMA_URL=
|
|
6
|
+
SUPABASE_URL=
|
|
7
|
+
NEXT_PUBLIC_SUPABASE_URL=
|
|
8
|
+
POSTGRES_URL_NON_POOLING=
|
|
9
|
+
SUPABASE_JWT_SECRET=
|
|
10
|
+
POSTGRES_USER=
|
|
11
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
|
12
|
+
POSTGRES_PASSWORD=
|
|
13
|
+
POSTGRES_DATABASE=
|
|
14
|
+
SUPABASE_SERVICE_ROLE_KEY=
|
|
15
|
+
POSTGRES_HOST=
|
|
16
|
+
SUPABASE_ANON_KEY=
|
|
17
|
+
|
|
18
|
+
# Cloudflare
|
|
19
|
+
NEXT_PUBLIC_R2_BASE_URL=
|
|
20
|
+
R2_ACCESS_KEY_ID=
|
|
21
|
+
R2_SECRET_ACCESS_KEY=
|
|
22
|
+
R2_BUCKET_NAME=
|
|
23
|
+
R2_ACCOUNT_ID=
|
|
24
|
+
NEXT_PUBLIC_R2_PUBLIC_URL=
|
|
25
|
+
R2_TOKEN_VALUE=
|
|
26
|
+
|
|
27
|
+
NEXTJS_REVALIDATE_URL=
|
|
28
|
+
REVALIDATE_SECRET_TOKEN=
|
|
29
|
+
LHCI_GITHUB_APP_TOKEN=
|
|
30
|
+
|
|
31
|
+
# Email SMTP Configuration
|
|
32
|
+
SMTP_HOST=
|
|
33
|
+
SMTP_PORT=
|
|
34
|
+
SMTP_USER=
|
|
35
|
+
SMTP_PASS=
|
|
36
|
+
SMTP_FROM_EMAIL=
|
|
37
|
+
SMTP_FROM_NAME=
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jsc": {
|
|
3
|
+
"target": "es2017",
|
|
4
|
+
"parser": {
|
|
5
|
+
"syntax": "typescript",
|
|
6
|
+
"decorators": true,
|
|
7
|
+
"dynamicImport": true
|
|
8
|
+
},
|
|
9
|
+
"transform": {
|
|
10
|
+
"decoratorMetadata": true,
|
|
11
|
+
"legacyDecorator": true
|
|
12
|
+
},
|
|
13
|
+
"keepClassNames": true,
|
|
14
|
+
"externalHelpers": true,
|
|
15
|
+
"loose": true
|
|
16
|
+
},
|
|
17
|
+
"module": {
|
|
18
|
+
"type": "commonjs"
|
|
19
|
+
},
|
|
20
|
+
"sourceMaps": true,
|
|
21
|
+
"exclude": [
|
|
22
|
+
"jest.config.ts",
|
|
23
|
+
".*\\.spec.tsx?$",
|
|
24
|
+
".*\\.test.tsx?$",
|
|
25
|
+
"./src/jest-setup.ts$",
|
|
26
|
+
"./**/jest-setup.ts$",
|
|
27
|
+
".*.js$",
|
|
28
|
+
".*.d.ts$"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Next.js 15 & Supabase - Ultra-Fast CMS Template
|
|
2
|
+
|
|
3
|
+
This project is a starter template for building an ultra-fast, localized, block-based Content Management System (CMS) using Next.js 15 (App Router), Supabase for the backend (PostgreSQL, Auth, Storage via R2), Tailwind CSS for styling, and shadcn/ui for components.
|
|
4
|
+
|
|
5
|
+
It features:
|
|
6
|
+
- Role-Based Access Control (Admin, Writer, User)
|
|
7
|
+
- Internationalization (i18n) with client-side language switching on single URLs
|
|
8
|
+
- Block-based content editor for Pages and Posts
|
|
9
|
+
- Media uploads to Cloudflare R2 with a Media Library
|
|
10
|
+
- Static Site Generation (SSG) with Incremental Static Regeneration (ISR) for public-facing content
|
|
11
|
+
- On-demand revalidation via Supabase Database Webhooks
|
|
12
|
+
|
|
13
|
+
## Features Implemented (Phases 1-6)
|
|
14
|
+
|
|
15
|
+
* **Authentication & Authorization (Phase 1):** User roles (ADMIN, WRITER, USER), profiles table linked to `auth.users`, Row Level Security (RLS) on tables, Next.js middleware for route protection, and client-side auth context.
|
|
16
|
+
* **Internationalization (Phase 2):** `languages` table in Supabase, client-side language switching using `LanguageContext` without URL path changes (e.g., `/about-us` serves content based on selected language), and auto-creation of localized placeholder content.
|
|
17
|
+
* **CMS Schema & Core CRUD (Phase 3):** Database tables for `pages`, `posts`, `media`, `blocks`, `navigation_items`. CRUD UIs and server actions for managing Pages, Posts, Navigation Items, Users (role changes), and Languages.
|
|
18
|
+
* **Block-Based Content Builder (Phase 4):** Dynamic block system for Pages (and Posts), UI for adding, editing (basic forms), deleting, and drag-and-drop reordering of content blocks.
|
|
19
|
+
* **Rich Text & Media (Phase 5):** Tiptap rich text editor integrated into "Text" blocks, image insertion from Media Library into Tiptap, and media uploads to Cloudflare R2 with a Media Library UI (upload, view, delete, edit metadata).
|
|
20
|
+
* **SSG & Revalidation (Phase 6):** Static generation of public pages/posts (default language), client-side content fetching for language changes, `generateStaticParams`, `generateMetadata`, and on-demand revalidation via Supabase Database Webhooks calling a Next.js API route.
|
|
21
|
+
|
|
22
|
+
## Clone and Run Locally
|
|
23
|
+
|
|
24
|
+
1. **Create a Supabase Project:**
|
|
25
|
+
* Go to the [Supabase dashboard](https://database.new) and create a new project.
|
|
26
|
+
|
|
27
|
+
2. **Clone This Repository:**
|
|
28
|
+
```bash
|
|
29
|
+
git clone <your-repository-url> your-cms-app
|
|
30
|
+
cd your-cms-app
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
3. **Install Dependencies:**
|
|
34
|
+
```bash
|
|
35
|
+
npm install
|
|
36
|
+
# or
|
|
37
|
+
yarn install
|
|
38
|
+
# or
|
|
39
|
+
pnpm install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
4. **Set Up Environment Variables:**
|
|
43
|
+
* Rename `.env.example` to `.env.local`.
|
|
44
|
+
* Update the following variables in `.env.local` with your Supabase project details and other configurations:
|
|
45
|
+
|
|
46
|
+
```env
|
|
47
|
+
# Supabase Project Connection (from your Supabase project's API settings)
|
|
48
|
+
NEXT_PUBLIC_SUPABASE_URL=[https://your-project-ref.supabase.co](https://your-project-ref.supabase.co)
|
|
49
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-public-anon-key
|
|
50
|
+
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key # Found in API settings, needed for admin actions like deleting users
|
|
51
|
+
SUPABASE_PROJECT_ID=your-supabase-project-id # Used by the Supabase CLI (e.g., in supabase/config.toml)
|
|
52
|
+
|
|
53
|
+
# Cloudflare R2 Storage (from your Cloudflare R2 bucket settings & API token)
|
|
54
|
+
NEXT_PUBLIC_R2_BASE_URL=[https://your-r2-public-url.r2.dev/your-bucket-name](https://your-r2-public-url.r2.dev/your-bucket-name) # Or your custom domain for R2
|
|
55
|
+
R2_ACCOUNT_ID=your_cloudflare_account_id
|
|
56
|
+
R2_ACCESS_KEY_ID=your_r2_access_key_id
|
|
57
|
+
R2_SECRET_ACCESS_KEY=your_r2_secret_access_key
|
|
58
|
+
R2_BUCKET_NAME=your_r2_bucket_name
|
|
59
|
+
R2_S3_ENDPOINT=https://<R2_ACCOUNT_ID>.r2.cloudflarestorage.com # e.g., [https://abcdef12345.r2.cloudflarestorage.com](https://abcdef12345.r2.cloudflarestorage.com)
|
|
60
|
+
R2_REGION=auto # Typically 'auto' for R2
|
|
61
|
+
|
|
62
|
+
# Next.js Site Configuration
|
|
63
|
+
NEXT_PUBLIC_SITE_URL=http://localhost:3000 # For local dev; update for production (e.g., [https://www.yourdomain.com](https://www.yourdomain.com))
|
|
64
|
+
REVALIDATE_SECRET_TOKEN=generate_a_strong_random_string_here # Used to secure the on-demand revalidation API endpoint
|
|
65
|
+
```
|
|
66
|
+
* `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` can be found in your Supabase project's API settings.
|
|
67
|
+
* `SUPABASE_SERVICE_ROLE_KEY` is also in API settings (typically hidden by default, click "Reveal").
|
|
68
|
+
* Generate a strong, unique string for `REVALIDATE_SECRET_TOKEN`.
|
|
69
|
+
|
|
70
|
+
5. **Apply Supabase Migrations:**
|
|
71
|
+
* Ensure you have the Supabase CLI installed and are logged in (`supabase login`).
|
|
72
|
+
* Link your local project to your Supabase project:
|
|
73
|
+
```bash
|
|
74
|
+
supabase link --project-ref your-project-ref
|
|
75
|
+
```
|
|
76
|
+
* Apply all database migrations:
|
|
77
|
+
```bash
|
|
78
|
+
supabase db push
|
|
79
|
+
```
|
|
80
|
+
Alternatively, if you prefer to run migrations individually (e.g., for a fresh setup or to ensure order):
|
|
81
|
+
```bash
|
|
82
|
+
supabase migration up
|
|
83
|
+
```
|
|
84
|
+
* This will create all necessary tables (`profiles`, `languages`, `pages`, `posts`, `media`, `blocks`, `navigation_items`), roles, RLS policies, and helper functions.
|
|
85
|
+
|
|
86
|
+
6. **Configure Supabase Database Webhooks for On-Demand Revalidation:**
|
|
87
|
+
Since this project avoids using Supabase Edge Functions (and their Docker dependency) for revalidation, you need to manually set up Database Webhooks to call your Next.js API endpoint directly.
|
|
88
|
+
|
|
89
|
+
* Go to your Supabase Project Dashboard -> Database -> Webhooks.
|
|
90
|
+
* Click "Create a new webhook".
|
|
91
|
+
|
|
92
|
+
* **For the `pages` Table:**
|
|
93
|
+
* **Name:** `Next.js Revalidate Pages` (or similar)
|
|
94
|
+
* **Table:** Select `pages` (from the `public` schema).
|
|
95
|
+
* **Events:** Check `INSERT`, `UPDATE`, `DELETE`.
|
|
96
|
+
* **Webhook Type:** `HTTP Request`
|
|
97
|
+
* **HTTP URL:** Your Next.js application's revalidation API endpoint.
|
|
98
|
+
* For local development (if using a tunneling service like ngrok to expose localhost): `http://your-ngrok-url.ngrok.io/api/revalidate`
|
|
99
|
+
* For production (e.g., Vercel): `https://your-app-name.vercel.app/api/revalidate`
|
|
100
|
+
* **HTTP Method:** `POST`
|
|
101
|
+
* **HTTP Headers:**
|
|
102
|
+
* Click "Add header".
|
|
103
|
+
* Header name: `x-revalidate-secret`
|
|
104
|
+
* Header value: The same `REVALIDATE_SECRET_TOKEN` you set in your `.env.local`.
|
|
105
|
+
* Click "Add header" again.
|
|
106
|
+
* Header name: `Content-Type`
|
|
107
|
+
* Header value: `application/json`
|
|
108
|
+
* Click "Create webhook".
|
|
109
|
+
|
|
110
|
+
* **For the `posts` Table:**
|
|
111
|
+
* Create another webhook with similar settings:
|
|
112
|
+
* **Name:** `Next.js Revalidate Posts`
|
|
113
|
+
* **Table:** `posts`
|
|
114
|
+
* **Events:** `INSERT`, `UPDATE`, `DELETE`.
|
|
115
|
+
* **HTTP URL:** Same as above.
|
|
116
|
+
* **HTTP Method:** `POST`
|
|
117
|
+
* **HTTP Headers:**
|
|
118
|
+
* `x-revalidate-secret`: Your `REVALIDATE_SECRET_TOKEN`
|
|
119
|
+
* `Content-Type`: `application/json`
|
|
120
|
+
* Click "Create webhook".
|
|
121
|
+
|
|
122
|
+
7. **Run the Next.js Development Server:**
|
|
123
|
+
```bash
|
|
124
|
+
npm run dev
|
|
125
|
+
# or
|
|
126
|
+
yarn dev
|
|
127
|
+
# or
|
|
128
|
+
pnpm dev
|
|
129
|
+
```
|
|
130
|
+
The application should now be running on [http://localhost:3000](http://localhost:3000/).
|
|
131
|
+
|
|
132
|
+
8. **Initial Admin User Setup:**
|
|
133
|
+
* Sign up for a new user account through the application's sign-up page.
|
|
134
|
+
* After signing up and verifying the email, you'll need to manually update this user's role to `ADMIN` in the Supabase `profiles` table. You can do this via the Supabase Studio (Table Editor -> `profiles` table).
|
|
135
|
+
* Find your user's row (by their ID, which matches `auth.users.id`).
|
|
136
|
+
* Change the `role` column value from `USER` to `ADMIN`.
|
|
137
|
+
|
|
138
|
+
9. **Shadcn/UI Styling (Optional):**
|
|
139
|
+
* This template comes with the default shadcn/ui style initialized. If you want to customize the theme or use a different base color, you can delete `components.json` and re-initialize shadcn/ui following their [official documentation](https://ui.shadcn.com/docs/installation/next).
|
|
140
|
+
|
|
141
|
+
## Project Structure Highlights
|
|
142
|
+
|
|
143
|
+
* `app/`: Next.js App Router.
|
|
144
|
+
* `app/(auth-pages)/`: Routes for sign-in, sign-up, etc.
|
|
145
|
+
* `app/cms/`: CMS admin panel routes and layouts.
|
|
146
|
+
* `app/cms/[entity]/`: CRUD pages for different content types (pages, posts, media, users, navigation, languages).
|
|
147
|
+
* `app/cms/blocks/`: Components and actions related to the block editor.
|
|
148
|
+
* `app/[slug]/`: Dynamic route for public "Pages".
|
|
149
|
+
* `app/blog/[slug]/`: Dynamic route for public "Posts".
|
|
150
|
+
* `app/api/`: API routes (e.g., for revalidation, R2 pre-signed URLs).
|
|
151
|
+
* `components/`: Shared UI components (shadcn/ui based).
|
|
152
|
+
* `components/ui/`: shadcn/ui components.
|
|
153
|
+
* `context/`: React Context providers (e.g., `AuthContext`, `LanguageContext`).
|
|
154
|
+
* `lib/`: Utility functions and configurations.
|
|
155
|
+
* `lib/cloudflare/`: Client for Cloudflare R2.
|
|
156
|
+
* `utils/supabase/`: Supabase client setup, types, and middleware helpers.
|
|
157
|
+
* `supabase/migrations/`: SQL database migrations.
|
|
158
|
+
|
|
159
|
+
## Documentation
|
|
160
|
+
|
|
161
|
+
For a deeper understanding of the CMS's internal workings, please refer to the detailed documentation:
|
|
162
|
+
|
|
163
|
+
* **[CMS Application Overview](./docs/cms-application-overview.md):** A high-level guide to the Next.js application structure, core modules, and key functionalities.
|
|
164
|
+
* **[Block Editor Architecture](./docs/cms-architecture-overview.md):** A technical deep-dive into the architecture of the block-based content editor.
|
|
165
|
+
|
|
166
|
+
## Deployment
|
|
167
|
+
|
|
168
|
+
This project is optimized for deployment on [Vercel](https://vercel.com/).
|
|
169
|
+
|
|
170
|
+
1. Push your code to a GitHub/GitLab/Bitbucket repository.
|
|
171
|
+
2. Import the project into Vercel.
|
|
172
|
+
3. **Configure Environment Variables in Vercel:**
|
|
173
|
+
* Add all the environment variables from your `.env.local` file to your Vercel project settings (Project Settings -> Environment Variables). This includes Supabase keys, R2 keys, `NEXT_PUBLIC_SITE_URL` (set to your production domain), and `REVALIDATE_SECRET_TOKEN`.
|
|
174
|
+
4. Vercel will automatically build and deploy your Next.js application.
|
|
175
|
+
5. Ensure your Supabase Database Webhooks are pointing to your production Next.js API endpoint for revalidation.
|
|
176
|
+
|
|
177
|
+
## Database Backup
|
|
178
|
+
|
|
179
|
+
This project includes a simple script to backup your Supabase PostgreSQL database.
|
|
180
|
+
|
|
181
|
+
**Requirements:**
|
|
182
|
+
* You must have the PostgreSQL command-line tools (`pg_dump`) installed and available in your system's PATH.
|
|
183
|
+
|
|
184
|
+
**Usage:**
|
|
185
|
+
To create a backup, run the following command from your project root:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm run db:backup
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This command will generate a timestamped SQL dump file and save it to the `backup/` directory.
|
|
192
|
+
## Feedback and Issues
|
|
193
|
+
|
|
194
|
+
Please file feedback and issues on the GitHub repository for this project.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { forgotPasswordAction } from "../../actions";
|
|
5
|
+
import { FormMessage, Message } from "../../../components/form-message";
|
|
6
|
+
import { SubmitButton } from "../../../components/submit-button";
|
|
7
|
+
import { Input } from "@nextblock-cms/ui";
|
|
8
|
+
import { Label } from "@nextblock-cms/ui";
|
|
9
|
+
import Link from "next/link";
|
|
10
|
+
import { useTranslations } from "@nextblock-cms/utils";
|
|
11
|
+
import { useSearchParams } from "next/navigation";
|
|
12
|
+
|
|
13
|
+
function getMessage(searchParams: URLSearchParams): Message | undefined {
|
|
14
|
+
if (searchParams.has('error')) {
|
|
15
|
+
const error = searchParams.get('error');
|
|
16
|
+
if (error) return { error };
|
|
17
|
+
}
|
|
18
|
+
if (searchParams.has('success')) {
|
|
19
|
+
const success = searchParams.get('success');
|
|
20
|
+
if (success) return { success };
|
|
21
|
+
}
|
|
22
|
+
if (searchParams.has('message')) {
|
|
23
|
+
const message = searchParams.get('message');
|
|
24
|
+
if (message) return { message };
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function ForgotPassword() {
|
|
30
|
+
const { t } = useTranslations();
|
|
31
|
+
const searchParams = useSearchParams();
|
|
32
|
+
const formMessage = getMessage(searchParams);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
<form className="flex-1 flex flex-col w-full gap-2 text-foreground [&>input]:mb-6 min-w-64 max-w-64 mx-auto">
|
|
37
|
+
<div>
|
|
38
|
+
<h1 className="text-2xl font-medium">{t('reset_password')}</h1>
|
|
39
|
+
<p className="text-sm text-secondary-foreground">
|
|
40
|
+
{t('already_have_account')}{" "}
|
|
41
|
+
<Link className="text-primary underline" href="/sign-in">
|
|
42
|
+
{t('sign_in')}
|
|
43
|
+
</Link>
|
|
44
|
+
</p>
|
|
45
|
+
</div>
|
|
46
|
+
<div className="flex flex-col gap-2 [&>input]:mb-3 mt-8">
|
|
47
|
+
<Label htmlFor="email">{t('email')}</Label>
|
|
48
|
+
<Input name="email" placeholder={t('you_at_example_com')} required />
|
|
49
|
+
<SubmitButton formAction={forgotPasswordAction}>
|
|
50
|
+
{t('reset_password')}
|
|
51
|
+
</SubmitButton>
|
|
52
|
+
<FormMessage message={formMessage} />
|
|
53
|
+
</div>
|
|
54
|
+
</form>
|
|
55
|
+
</>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { useSearchParams } from 'next/navigation';
|
|
5
|
+
|
|
6
|
+
export default function PostSignIn() {
|
|
7
|
+
const searchParams = useSearchParams();
|
|
8
|
+
const redirectTo = searchParams.get('redirect_to');
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (redirectTo) {
|
|
12
|
+
window.location.href = redirectTo;
|
|
13
|
+
} else {
|
|
14
|
+
window.location.href = '/';
|
|
15
|
+
}
|
|
16
|
+
}, [redirectTo]);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex justify-center items-center h-full w-full py-20">
|
|
20
|
+
<div className="relative">
|
|
21
|
+
<div className="h-16 w-16 rounded-full border-t-4 border-b-4 border-primary animate-spin"></div>
|
|
22
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
23
|
+
<div className="h-10 w-10 rounded-full bg-background"></div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|