create-nextly-app 0.0.2-alpha.1 → 0.0.2-alpha.2
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/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/cli.mjs.map +1 -1
- package/package.json +4 -4
- package/templates/base/.env.example +16 -0
- package/templates/base/README.md +30 -0
- package/templates/base/eslint.config.mjs +18 -0
- package/templates/base/next.config.ts +21 -0
- package/templates/base/postcss.config.mjs +7 -0
- package/templates/base/public/file.svg +1 -0
- package/templates/base/public/globe.svg +1 -0
- package/templates/base/public/next.svg +1 -0
- package/templates/base/public/vercel.svg +1 -0
- package/templates/base/public/window.svg +1 -0
- package/templates/base/src/app/admin/[[...params]]/layout.tsx +20 -0
- package/templates/base/src/app/admin/[[...params]]/page.tsx +27 -0
- package/templates/base/src/app/admin/api/[[...params]]/route.ts +12 -0
- package/templates/base/src/app/api/health/route.ts +7 -0
- package/templates/base/src/app/api/media/[[...path]]/route.ts +34 -0
- package/templates/base/src/app/favicon.ico +0 -0
- package/templates/base/src/app/globals.css +55 -0
- package/templates/base/src/app/layout.tsx +43 -0
- package/templates/base/src/app/page.tsx +65 -0
- package/templates/base/src/types/generated/.gitkeep +0 -0
- package/templates/base/src/types/generated/nextly-types.ts +12 -0
- package/templates/base/tsconfig.json +35 -0
- package/templates/blank/.env.example +8 -0
- package/templates/blank/README.md +63 -0
- package/templates/blank/nextly.config.ts +14 -0
- package/templates/blank/src/access/README.md +22 -0
- package/templates/blank/src/app/(frontend)/page.tsx +176 -0
- package/templates/blank/src/app/globals.css +79 -0
- package/templates/blank/src/app/layout.tsx +46 -0
- package/templates/blank/src/collections/README.md +22 -0
- package/templates/blank/src/components/README.md +10 -0
- package/templates/blank/src/components/ThemeToggle.tsx +113 -0
- package/templates/blank/src/lib/README.md +15 -0
- package/templates/blank/src/singles/README.md +19 -0
- package/templates/blank/template.json +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-nextly-app",
|
|
3
|
-
"version": "0.0.2-alpha.
|
|
3
|
+
"version": "0.0.2-alpha.2",
|
|
4
4
|
"description": "CLI to scaffold Nextly in your Next.js project",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
43
|
"vite-tsconfig-paths": "^5.1.4",
|
|
44
44
|
"vitest": "^4.0.8",
|
|
45
|
-
"@nextlyhq/eslint-config": "0.0.2-alpha.
|
|
46
|
-
"@nextlyhq/telemetry": "0.0.2-alpha.
|
|
47
|
-
"@nextlyhq/tsconfig": "0.0.2-alpha.
|
|
45
|
+
"@nextlyhq/eslint-config": "0.0.2-alpha.1",
|
|
46
|
+
"@nextlyhq/telemetry": "0.0.2-alpha.1",
|
|
47
|
+
"@nextlyhq/tsconfig": "0.0.2-alpha.1"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=20.0.0"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Nextly Configuration
|
|
2
|
+
# Generated by create-nextly-app
|
|
3
|
+
|
|
4
|
+
# Database Configuration
|
|
5
|
+
DB_DIALECT={{databaseDialect}}
|
|
6
|
+
DATABASE_URL={{databaseUrl}}
|
|
7
|
+
|
|
8
|
+
# Authentication (REQUIRED)
|
|
9
|
+
# Generate with: openssl rand -base64 32
|
|
10
|
+
NEXTLY_SECRET=change-me-generate-a-secure-secret
|
|
11
|
+
|
|
12
|
+
# Application URL
|
|
13
|
+
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
14
|
+
|
|
15
|
+
# Storage Configuration
|
|
16
|
+
{{storageEnvVars}}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Base template
|
|
2
|
+
|
|
3
|
+
Shared scaffolding for all `create-nextly-app` templates. The CLI applies this template first, then overlays the user-selected template (`blank`, `blog`, or future) on top.
|
|
4
|
+
|
|
5
|
+
> Not user-selectable. You will not see "base" in the CLI menu. It is always applied beneath your chosen template.
|
|
6
|
+
|
|
7
|
+
## What's in here
|
|
8
|
+
|
|
9
|
+
- Admin route handler stub (`app/admin/[[...params]]/page.tsx`)
|
|
10
|
+
- Default API handler stubs
|
|
11
|
+
- Shared styles and Tailwind preset wiring
|
|
12
|
+
- Default `tsconfig.json`, `next.config.ts`, `eslint.config.mjs`, `postcss.config.mjs`
|
|
13
|
+
|
|
14
|
+
## Maintainer notes
|
|
15
|
+
|
|
16
|
+
When editing files in `base/`, run the CLI against `--template blank` and `--template blog` locally to confirm both still scaffold cleanly:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm --filter create-nextly-app dev -- my-test \
|
|
20
|
+
--template blank \
|
|
21
|
+
--local-template ./templates \
|
|
22
|
+
--skip-install
|
|
23
|
+
|
|
24
|
+
pnpm --filter create-nextly-app dev -- my-test \
|
|
25
|
+
--template blog \
|
|
26
|
+
--local-template ./templates \
|
|
27
|
+
--skip-install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
See [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"next-env.d.ts",
|
|
15
|
+
]),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
|
|
3
|
+
const nextConfig: NextConfig = {
|
|
4
|
+
serverExternalPackages: [
|
|
5
|
+
"nextly",
|
|
6
|
+
"@nextlyhq/adapter-drizzle",
|
|
7
|
+
"@nextlyhq/adapter-postgres",
|
|
8
|
+
"@nextlyhq/adapter-mysql",
|
|
9
|
+
"@nextlyhq/adapter-sqlite",
|
|
10
|
+
"drizzle-orm",
|
|
11
|
+
"drizzle-kit",
|
|
12
|
+
"pg",
|
|
13
|
+
"mysql2",
|
|
14
|
+
"better-sqlite3",
|
|
15
|
+
"bcryptjs",
|
|
16
|
+
"sharp",
|
|
17
|
+
"esbuild",
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default nextConfig;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getBrandingCss } from "nextly/config";
|
|
2
|
+
|
|
3
|
+
import config from "../../../../nextly.config";
|
|
4
|
+
|
|
5
|
+
const brandingCss = getBrandingCss(config.admin?.branding);
|
|
6
|
+
|
|
7
|
+
export default function AdminLayout({
|
|
8
|
+
children,
|
|
9
|
+
}: {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
{brandingCss && (
|
|
15
|
+
<style dangerouslySetInnerHTML={{ __html: brandingCss }} />
|
|
16
|
+
)}
|
|
17
|
+
{children}
|
|
18
|
+
</>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Plugin-side admin imports. Every scaffold ships @nextlyhq/plugin-form-builder
|
|
4
|
+
// as a dependency (see packages/create-nextly-app utils/template.ts). These
|
|
5
|
+
// three lines register the plugin's custom admin field components and load its
|
|
6
|
+
// CSS so the Forms collection's drag-and-drop field builder and Submissions
|
|
7
|
+
// filter UI render correctly. Without them, Forms still appears in the sidebar
|
|
8
|
+
// but the builder falls back to plain JSON/text inputs.
|
|
9
|
+
import "@nextlyhq/admin/style.css";
|
|
10
|
+
import "@nextlyhq/plugin-form-builder/admin";
|
|
11
|
+
import "@nextlyhq/plugin-form-builder/styles/builder.css";
|
|
12
|
+
import "@nextlyhq/plugin-form-builder/styles/submissions-filter.css";
|
|
13
|
+
import { RootLayout, QueryProvider, ErrorBoundary } from "@nextlyhq/admin";
|
|
14
|
+
|
|
15
|
+
export default function AdminPage() {
|
|
16
|
+
return (
|
|
17
|
+
<ErrorBoundary
|
|
18
|
+
onError={(error, errorInfo) => {
|
|
19
|
+
console.error("Admin error:", error, errorInfo);
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
<QueryProvider>
|
|
23
|
+
<RootLayout />
|
|
24
|
+
</QueryProvider>
|
|
25
|
+
</ErrorBoundary>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createDynamicHandlers } from "nextly/runtime";
|
|
2
|
+
|
|
3
|
+
import nextlyConfig from "../../../../../nextly.config";
|
|
4
|
+
|
|
5
|
+
const handlers = createDynamicHandlers({ config: nextlyConfig });
|
|
6
|
+
|
|
7
|
+
export const GET = handlers.GET;
|
|
8
|
+
export const POST = handlers.POST;
|
|
9
|
+
export const PUT = handlers.PUT;
|
|
10
|
+
export const PATCH = handlers.PATCH;
|
|
11
|
+
export const DELETE = handlers.DELETE;
|
|
12
|
+
export const OPTIONS = handlers.OPTIONS;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media API Routes (Catch-All Handler)
|
|
3
|
+
*
|
|
4
|
+
* This single route handles all media operations:
|
|
5
|
+
*
|
|
6
|
+
* Media Files:
|
|
7
|
+
* - GET /api/media - List media with pagination
|
|
8
|
+
* - POST /api/media - Upload new media file
|
|
9
|
+
* - GET /api/media/:id - Get media by ID
|
|
10
|
+
* - PATCH /api/media/:id - Update media metadata
|
|
11
|
+
* - DELETE /api/media/:id - Delete media file
|
|
12
|
+
* - PATCH /api/media/:id/move - Move media to folder
|
|
13
|
+
*
|
|
14
|
+
* Folders:
|
|
15
|
+
* - GET /api/media/folders - List folders
|
|
16
|
+
* - POST /api/media/folders - Create folder
|
|
17
|
+
* - GET /api/media/folders/:id - Get folder by ID
|
|
18
|
+
* - PATCH /api/media/folders/:id - Update folder
|
|
19
|
+
* - DELETE /api/media/folders/:id - Delete folder
|
|
20
|
+
* - GET /api/media/folders/:id/contents - Get folder contents
|
|
21
|
+
* - GET /api/media/folders/root/contents - Get root folder contents
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { createMediaHandlers } from "nextly/api/media-handlers";
|
|
25
|
+
|
|
26
|
+
import nextlyConfig from "../../../../../nextly.config";
|
|
27
|
+
|
|
28
|
+
// Pass config to ensure storage plugins work across all worker processes
|
|
29
|
+
const handlers = createMediaHandlers({ config: nextlyConfig });
|
|
30
|
+
|
|
31
|
+
export const GET = handlers.GET;
|
|
32
|
+
export const POST = handlers.POST;
|
|
33
|
+
export const PATCH = handlers.PATCH;
|
|
34
|
+
export const DELETE = handlers.DELETE;
|
|
Binary file
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--background: #ffffff;
|
|
5
|
+
--foreground: #171717;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@theme inline {
|
|
9
|
+
--color-background: var(--background);
|
|
10
|
+
--color-foreground: var(--foreground);
|
|
11
|
+
--font-sans: var(--font-geist-sans);
|
|
12
|
+
--font-mono: var(--font-geist-mono);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
:root {
|
|
17
|
+
--background: #0a0a0a;
|
|
18
|
+
--foreground: #ededed;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
body {
|
|
23
|
+
background: var(--background);
|
|
24
|
+
color: var(--foreground);
|
|
25
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ===============================================================
|
|
29
|
+
Accessibility baseline
|
|
30
|
+
---------------------------------------------------------------
|
|
31
|
+
Applied globally so the template passes a WCAG AA keyboard audit
|
|
32
|
+
out of the box. Adjust to match your brand.
|
|
33
|
+
=============================================================== */
|
|
34
|
+
|
|
35
|
+
/* Visible focus ring for keyboard users. `:focus-visible` only fires
|
|
36
|
+
on keyboard / programmatic focus, so clicking a button doesn't
|
|
37
|
+
show the ring but tabbing to it does. */
|
|
38
|
+
*:focus-visible {
|
|
39
|
+
outline: 2px solid currentColor;
|
|
40
|
+
outline-offset: 2px;
|
|
41
|
+
border-radius: 2px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Honor OS-level reduced-motion preference — disables hover scale,
|
|
45
|
+
transitions, and animations for users who've opted out. */
|
|
46
|
+
@media (prefers-reduced-motion: reduce) {
|
|
47
|
+
*,
|
|
48
|
+
*::before,
|
|
49
|
+
*::after {
|
|
50
|
+
animation-duration: 0.01ms !important;
|
|
51
|
+
animation-iteration-count: 1 !important;
|
|
52
|
+
transition-duration: 0.01ms !important;
|
|
53
|
+
scroll-behavior: auto !important;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Geist, Geist_Mono } from "next/font/google";
|
|
3
|
+
import "./globals.css";
|
|
4
|
+
|
|
5
|
+
const geistSans = Geist({
|
|
6
|
+
variable: "--font-geist-sans",
|
|
7
|
+
subsets: ["latin"],
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const geistMono = Geist_Mono({
|
|
11
|
+
variable: "--font-geist-mono",
|
|
12
|
+
subsets: ["latin"],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `metadataBase` tells Next.js how to resolve relative URLs in OpenGraph
|
|
17
|
+
* images, Twitter images, and canonical URLs. Set `NEXT_PUBLIC_SITE_URL`
|
|
18
|
+
* in your environment to your production domain (e.g.
|
|
19
|
+
* `https://yourblog.com`). The localhost fallback keeps dev working.
|
|
20
|
+
*/
|
|
21
|
+
export const metadata: Metadata = {
|
|
22
|
+
metadataBase: new URL(
|
|
23
|
+
process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000"
|
|
24
|
+
),
|
|
25
|
+
title: { default: "Nextly", template: "%s — Nextly" },
|
|
26
|
+
description: "A site built with Nextly.",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default function RootLayout({
|
|
30
|
+
children,
|
|
31
|
+
}: Readonly<{
|
|
32
|
+
children: React.ReactNode;
|
|
33
|
+
}>) {
|
|
34
|
+
return (
|
|
35
|
+
<html lang="en" suppressHydrationWarning>
|
|
36
|
+
<body
|
|
37
|
+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import Image from "next/image";
|
|
2
|
+
|
|
3
|
+
export default function Home() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
|
6
|
+
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
|
7
|
+
<Image
|
|
8
|
+
className="dark:invert"
|
|
9
|
+
src="/next.svg"
|
|
10
|
+
alt="Next.js logo"
|
|
11
|
+
width={100}
|
|
12
|
+
height={20}
|
|
13
|
+
priority
|
|
14
|
+
/>
|
|
15
|
+
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
|
16
|
+
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
|
17
|
+
To get started, edit the page.tsx file.
|
|
18
|
+
</h1>
|
|
19
|
+
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
|
20
|
+
Looking for a starting point or more instructions? Head over to{" "}
|
|
21
|
+
<a
|
|
22
|
+
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
23
|
+
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
24
|
+
>
|
|
25
|
+
Templates
|
|
26
|
+
</a>{" "}
|
|
27
|
+
or the{" "}
|
|
28
|
+
<a
|
|
29
|
+
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
30
|
+
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
31
|
+
>
|
|
32
|
+
Learning
|
|
33
|
+
</a>{" "}
|
|
34
|
+
center.
|
|
35
|
+
</p>
|
|
36
|
+
</div>
|
|
37
|
+
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
|
38
|
+
<a
|
|
39
|
+
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
|
40
|
+
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
41
|
+
target="_blank"
|
|
42
|
+
rel="noopener noreferrer"
|
|
43
|
+
>
|
|
44
|
+
<Image
|
|
45
|
+
className="dark:invert"
|
|
46
|
+
src="/vercel.svg"
|
|
47
|
+
alt="Vercel logomark"
|
|
48
|
+
width={16}
|
|
49
|
+
height={16}
|
|
50
|
+
/>
|
|
51
|
+
Deploy Now
|
|
52
|
+
</a>
|
|
53
|
+
<a
|
|
54
|
+
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
|
55
|
+
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
56
|
+
target="_blank"
|
|
57
|
+
rel="noopener noreferrer"
|
|
58
|
+
>
|
|
59
|
+
Documentation
|
|
60
|
+
</a>
|
|
61
|
+
</div>
|
|
62
|
+
</main>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nextly Generated Types
|
|
3
|
+
*
|
|
4
|
+
* This file will be auto-generated when you run:
|
|
5
|
+
* npx nextly dev
|
|
6
|
+
* npx nextly generate:types
|
|
7
|
+
*
|
|
8
|
+
* Do not edit this file manually - it will be overwritten.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Types will be generated here after running nextly dev
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"],
|
|
23
|
+
"@nextly-config": ["./nextly.config.ts"]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"include": [
|
|
27
|
+
"next-env.d.ts",
|
|
28
|
+
"**/*.ts",
|
|
29
|
+
"**/*.tsx",
|
|
30
|
+
".next/types/**/*.ts",
|
|
31
|
+
".next/dev/types/**/*.ts",
|
|
32
|
+
"**/*.mts"
|
|
33
|
+
],
|
|
34
|
+
"exclude": ["node_modules"]
|
|
35
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Public site URL used by:
|
|
2
|
+
# - Metadata API (canonical URLs, OpenGraph image resolution)
|
|
3
|
+
# - sitemap.xml, robots.txt, and any RSS feed route handlers you add
|
|
4
|
+
# - JSON-LD structured data links
|
|
5
|
+
#
|
|
6
|
+
# Set this to your production domain when deploying (e.g. https://yourapp.com).
|
|
7
|
+
# The localhost fallback keeps local development working.
|
|
8
|
+
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Blank template
|
|
2
|
+
|
|
3
|
+
A minimal Nextly project with the conventional folder layout already
|
|
4
|
+
scaffolded. Empty `nextly.config.ts`, no collections, one landing
|
|
5
|
+
page, and READMEs in every convention folder so you know where things
|
|
6
|
+
go as you grow.
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
Choose `blank` when you want to build everything from scratch but
|
|
11
|
+
appreciate having the right folder structure waiting for you. Pick
|
|
12
|
+
[`blog`](../blog/README.md) instead if you want a populated example
|
|
13
|
+
with collections, singles, frontend pages, RSS, search, and demo
|
|
14
|
+
content already wired up.
|
|
15
|
+
|
|
16
|
+
## Scaffold
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm create nextly-app@alpha my-app --template blank
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## What you get
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
my-app/
|
|
26
|
+
├── nextly.config.ts # Empty config — add your collections / singles
|
|
27
|
+
├── .env.example # NEXT_PUBLIC_SITE_URL etc.
|
|
28
|
+
├── next.config.ts # (inherited from base)
|
|
29
|
+
├── postcss.config.mjs # (inherited from base)
|
|
30
|
+
├── tsconfig.json # (inherited from base)
|
|
31
|
+
└── src/
|
|
32
|
+
├── access/ # RBAC functions (anyone, authenticated, ...)
|
|
33
|
+
├── collections/ # defineCollection() definitions
|
|
34
|
+
├── singles/ # defineSingle() definitions
|
|
35
|
+
├── components/ # React components (ThemeToggle ships as an example)
|
|
36
|
+
├── lib/ # Project-wide helpers
|
|
37
|
+
└── app/
|
|
38
|
+
├── layout.tsx # Root layout (font + metadata)
|
|
39
|
+
├── globals.css # Design tokens
|
|
40
|
+
└── (frontend)/ # Public-facing routes (route group)
|
|
41
|
+
└── page.tsx # Landing page — flips between "Set up admin" / "Open admin"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `(frontend)` route group keeps your public pages cleanly separated
|
|
45
|
+
from `app/admin/...` and `app/api/...` routes that Nextly mounts. As
|
|
46
|
+
you add routes, drop them inside `(frontend)/` and they'll inherit
|
|
47
|
+
your blog/marketing layout instead of the admin's.
|
|
48
|
+
|
|
49
|
+
Every convention folder ships with a `README.md` explaining what
|
|
50
|
+
belongs there. Replace them with real code as you build.
|
|
51
|
+
|
|
52
|
+
## Next steps
|
|
53
|
+
|
|
54
|
+
- **Add a collection** — create `src/collections/Posts.ts`, register
|
|
55
|
+
it in `nextly.config.ts`, run `pnpm dev`. The admin will surface it
|
|
56
|
+
immediately.
|
|
57
|
+
- **Add a public page** — drop a new file under
|
|
58
|
+
`src/app/(frontend)/blog/page.tsx`. It'll be live at `/blog`.
|
|
59
|
+
- **Configure storage / email** — see
|
|
60
|
+
[`nextlyhq.com/docs`](https://nextlyhq.com/docs) for adapter setup.
|
|
61
|
+
|
|
62
|
+
See [`templates/blog/README.md`](../blog/README.md) for a fully
|
|
63
|
+
populated example showing all of the above wired up.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from "nextly/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
// Add your collections here
|
|
5
|
+
collections: [],
|
|
6
|
+
|
|
7
|
+
// Add your singles (globals) here
|
|
8
|
+
singles: [],
|
|
9
|
+
|
|
10
|
+
// TypeScript type generation
|
|
11
|
+
typescript: {
|
|
12
|
+
outputFile: "./src/types/generated/nextly-types.ts",
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Access
|
|
2
|
+
|
|
3
|
+
Access-control functions used by `defineCollection({ access: ... })`
|
|
4
|
+
and `defineSingle({ access: ... })`. Each function decides whether the
|
|
5
|
+
current user can perform an action (read, create, update, delete) on a
|
|
6
|
+
collection or single.
|
|
7
|
+
|
|
8
|
+
Conventions:
|
|
9
|
+
|
|
10
|
+
- One function per file. File name matches the function name.
|
|
11
|
+
- Export the function as a named export.
|
|
12
|
+
- Type signature: `AccessControlFunction` from `nextly`.
|
|
13
|
+
|
|
14
|
+
Common patterns shipped in the blog template (for reference):
|
|
15
|
+
|
|
16
|
+
- `anyone.ts` — always allow (use for public reads).
|
|
17
|
+
- `authenticated.ts` — require any logged-in user.
|
|
18
|
+
- `is-admin.ts` — require the `admin` role.
|
|
19
|
+
- `is-author-or-editor.ts` — require one of several roles.
|
|
20
|
+
|
|
21
|
+
Add your own here as your project grows. Wire them up in your
|
|
22
|
+
collections / singles config.
|