@revealui/cli 0.0.0-canary-20260402004330
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/LICENSE +22 -0
- package/README.md +99 -0
- package/bin/create-revealui.js +6 -0
- package/bin/revealui.js +6 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +2769 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2767 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
- package/templates/basic-blog/.env.example +36 -0
- package/templates/basic-blog/_gitignore +26 -0
- package/templates/basic-blog/next.config.mjs +8 -0
- package/templates/basic-blog/package.json +36 -0
- package/templates/basic-blog/postcss.config.mjs +5 -0
- package/templates/basic-blog/revealui.config.ts +19 -0
- package/templates/basic-blog/src/app/globals.css +6 -0
- package/templates/basic-blog/src/app/layout.tsx +15 -0
- package/templates/basic-blog/src/app/page.tsx +57 -0
- package/templates/basic-blog/src/app/posts/[slug]/page.tsx +66 -0
- package/templates/basic-blog/src/app/posts/page.tsx +61 -0
- package/templates/basic-blog/src/collections/Posts.ts +42 -0
- package/templates/basic-blog/src/seed.ts +73 -0
- package/templates/basic-blog/tsconfig.json +34 -0
- package/templates/e-commerce/.env.example +36 -0
- package/templates/e-commerce/_gitignore +26 -0
- package/templates/e-commerce/next.config.mjs +8 -0
- package/templates/e-commerce/package.json +36 -0
- package/templates/e-commerce/postcss.config.mjs +5 -0
- package/templates/e-commerce/revealui.config.ts +20 -0
- package/templates/e-commerce/src/app/globals.css +6 -0
- package/templates/e-commerce/src/app/layout.tsx +15 -0
- package/templates/e-commerce/src/app/page.tsx +82 -0
- package/templates/e-commerce/src/app/products/[slug]/page.tsx +80 -0
- package/templates/e-commerce/src/app/products/page.tsx +72 -0
- package/templates/e-commerce/src/collections/Orders.ts +63 -0
- package/templates/e-commerce/src/collections/Products.ts +50 -0
- package/templates/e-commerce/src/seed.ts +72 -0
- package/templates/e-commerce/tsconfig.json +34 -0
- package/templates/portfolio/.env.example +36 -0
- package/templates/portfolio/_gitignore +26 -0
- package/templates/portfolio/next.config.mjs +8 -0
- package/templates/portfolio/package.json +36 -0
- package/templates/portfolio/postcss.config.mjs +5 -0
- package/templates/portfolio/revealui.config.ts +19 -0
- package/templates/portfolio/src/app/globals.css +6 -0
- package/templates/portfolio/src/app/layout.tsx +15 -0
- package/templates/portfolio/src/app/page.tsx +60 -0
- package/templates/portfolio/src/app/projects/[slug]/page.tsx +95 -0
- package/templates/portfolio/src/app/projects/page.tsx +85 -0
- package/templates/portfolio/src/collections/Projects.ts +49 -0
- package/templates/portfolio/src/seed.ts +73 -0
- package/templates/portfolio/tsconfig.json +34 -0
- package/templates/starter/.env.example +36 -0
- package/templates/starter/_gitignore +26 -0
- package/templates/starter/next.config.mjs +8 -0
- package/templates/starter/package.json +36 -0
- package/templates/starter/postcss.config.mjs +5 -0
- package/templates/starter/revealui.config.ts +18 -0
- package/templates/starter/src/app/globals.css +6 -0
- package/templates/starter/src/app/layout.tsx +15 -0
- package/templates/starter/src/app/page.tsx +18 -0
- package/templates/starter/src/seed.ts +40 -0
- package/templates/starter/tsconfig.json +34 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import Image from 'next/image';
|
|
2
|
+
|
|
3
|
+
const API_URL = process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:4000';
|
|
4
|
+
|
|
5
|
+
interface Product {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
description: unknown;
|
|
10
|
+
price: number;
|
|
11
|
+
status: string;
|
|
12
|
+
image?: { url: string; alt?: string } | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatPrice(cents: number): string {
|
|
16
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function getProduct(slug: string): Promise<Product | null> {
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(`${API_URL}/api/products?where[slug][equals]=${slug}&limit=1`, {
|
|
22
|
+
cache: 'no-store',
|
|
23
|
+
});
|
|
24
|
+
if (!res.ok) return null;
|
|
25
|
+
const data = await res.json();
|
|
26
|
+
return data.docs?.[0] ?? null;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
33
|
+
const { slug } = await params;
|
|
34
|
+
const product = await getProduct(slug);
|
|
35
|
+
|
|
36
|
+
if (!product) {
|
|
37
|
+
return (
|
|
38
|
+
<main className="mx-auto max-w-2xl px-4 py-16">
|
|
39
|
+
<h1 className="text-2xl font-bold">Product not found</h1>
|
|
40
|
+
<p className="mt-4">
|
|
41
|
+
<a href="/products" className="text-accent underline">
|
|
42
|
+
Back to products
|
|
43
|
+
</a>
|
|
44
|
+
</p>
|
|
45
|
+
</main>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<main className="mx-auto max-w-2xl px-4 py-16">
|
|
51
|
+
<nav className="mb-8">
|
|
52
|
+
<a href="/products" className="text-sm text-accent underline">
|
|
53
|
+
← Back to products
|
|
54
|
+
</a>
|
|
55
|
+
</nav>
|
|
56
|
+
<div className="grid grid-cols-1 gap-8 md:grid-cols-2">
|
|
57
|
+
{product.image?.url && (
|
|
58
|
+
<Image
|
|
59
|
+
src={product.image.url}
|
|
60
|
+
alt={product.image.alt || product.name}
|
|
61
|
+
width={600}
|
|
62
|
+
height={600}
|
|
63
|
+
className="aspect-square w-full rounded-lg object-cover"
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
<div>
|
|
67
|
+
<h1 className="text-3xl font-bold">{product.name}</h1>
|
|
68
|
+
<p className="mt-2 text-2xl font-bold text-gray-900">{formatPrice(product.price)}</p>
|
|
69
|
+
<div className="prose mt-6">
|
|
70
|
+
{typeof product.description === 'string' ? (
|
|
71
|
+
<p>{product.description}</p>
|
|
72
|
+
) : (
|
|
73
|
+
<p className="text-gray-500">Product description will render here.</p>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</main>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import Image from 'next/image';
|
|
2
|
+
|
|
3
|
+
const API_URL = process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:4000';
|
|
4
|
+
|
|
5
|
+
interface Product {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
price: number;
|
|
10
|
+
status: string;
|
|
11
|
+
image?: { url: string; alt?: string } | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function formatPrice(cents: number): string {
|
|
15
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function getProducts(): Promise<Product[]> {
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch(`${API_URL}/api/products?where[status][equals]=active&sort=name`, {
|
|
21
|
+
cache: 'no-store',
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok) return [];
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
return data.docs ?? [];
|
|
26
|
+
} catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default async function ProductsPage() {
|
|
32
|
+
const products = await getProducts();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<main className="mx-auto max-w-4xl px-4 py-16">
|
|
36
|
+
<h1 className="mb-8 text-3xl font-bold">Products</h1>
|
|
37
|
+
|
|
38
|
+
{products.length === 0 ? (
|
|
39
|
+
<p className="text-gray-500">
|
|
40
|
+
No products yet. Add products in the{' '}
|
|
41
|
+
<a href="/admin/collections/products" className="text-accent underline">
|
|
42
|
+
admin panel
|
|
43
|
+
</a>
|
|
44
|
+
, or run <code className="rounded bg-gray-100 px-1">pnpm db:seed</code> to add sample
|
|
45
|
+
data.
|
|
46
|
+
</p>
|
|
47
|
+
) : (
|
|
48
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
49
|
+
{products.map((product) => (
|
|
50
|
+
<a
|
|
51
|
+
key={product.id}
|
|
52
|
+
href={`/products/${product.slug}`}
|
|
53
|
+
className="group rounded-lg border border-gray-200 p-4 transition-shadow hover:shadow-md"
|
|
54
|
+
>
|
|
55
|
+
{product.image?.url && (
|
|
56
|
+
<Image
|
|
57
|
+
src={product.image.url}
|
|
58
|
+
alt={product.image.alt || product.name}
|
|
59
|
+
width={400}
|
|
60
|
+
height={400}
|
|
61
|
+
className="mb-4 aspect-square w-full rounded object-cover"
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
<h2 className="font-semibold group-hover:text-accent">{product.name}</h2>
|
|
65
|
+
<p className="mt-1 text-lg font-bold text-gray-900">{formatPrice(product.price)}</p>
|
|
66
|
+
</a>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</main>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { CollectionConfig } from '@revealui/contracts';
|
|
2
|
+
|
|
3
|
+
export const Orders: CollectionConfig = {
|
|
4
|
+
slug: 'orders',
|
|
5
|
+
labels: { singular: 'Order', plural: 'Orders' },
|
|
6
|
+
fields: [
|
|
7
|
+
{
|
|
8
|
+
name: 'customer',
|
|
9
|
+
type: 'text',
|
|
10
|
+
required: true,
|
|
11
|
+
admin: {
|
|
12
|
+
description: 'Customer email address',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'items',
|
|
17
|
+
type: 'array',
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
name: 'product',
|
|
21
|
+
type: 'relationship',
|
|
22
|
+
relationTo: 'products',
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'quantity',
|
|
27
|
+
type: 'number',
|
|
28
|
+
required: true,
|
|
29
|
+
min: 1,
|
|
30
|
+
defaultValue: 1,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'price',
|
|
34
|
+
type: 'number',
|
|
35
|
+
required: true,
|
|
36
|
+
min: 0,
|
|
37
|
+
admin: {
|
|
38
|
+
description: 'Price at time of purchase (cents)',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'total',
|
|
45
|
+
type: 'number',
|
|
46
|
+
required: true,
|
|
47
|
+
min: 0,
|
|
48
|
+
admin: {
|
|
49
|
+
description: 'Order total in cents',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'status',
|
|
54
|
+
type: 'select',
|
|
55
|
+
defaultValue: 'pending',
|
|
56
|
+
options: [
|
|
57
|
+
{ label: 'Pending', value: 'pending' },
|
|
58
|
+
{ label: 'Paid', value: 'paid' },
|
|
59
|
+
{ label: 'Shipped', value: 'shipped' },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CollectionConfig } from '@revealui/contracts';
|
|
2
|
+
|
|
3
|
+
export const Products: CollectionConfig = {
|
|
4
|
+
slug: 'products',
|
|
5
|
+
labels: { singular: 'Product', plural: 'Products' },
|
|
6
|
+
fields: [
|
|
7
|
+
{
|
|
8
|
+
name: 'name',
|
|
9
|
+
type: 'text',
|
|
10
|
+
required: true,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'slug',
|
|
14
|
+
type: 'text',
|
|
15
|
+
required: true,
|
|
16
|
+
unique: true,
|
|
17
|
+
admin: {
|
|
18
|
+
description: 'URL-friendly identifier (e.g. "wireless-headphones")',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'description',
|
|
23
|
+
type: 'richText',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'price',
|
|
27
|
+
type: 'number',
|
|
28
|
+
required: true,
|
|
29
|
+
min: 0,
|
|
30
|
+
admin: {
|
|
31
|
+
description: 'Price in cents (e.g. 2999 = $29.99)',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'image',
|
|
36
|
+
type: 'upload',
|
|
37
|
+
relationTo: 'media',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'status',
|
|
41
|
+
type: 'select',
|
|
42
|
+
defaultValue: 'draft',
|
|
43
|
+
options: [
|
|
44
|
+
{ label: 'Draft', value: 'draft' },
|
|
45
|
+
{ label: 'Active', value: 'active' },
|
|
46
|
+
{ label: 'Archived', value: 'archived' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed script for the e-commerce template.
|
|
3
|
+
* Creates 3 sample products via the RevealUI REST API.
|
|
4
|
+
*
|
|
5
|
+
* Usage: pnpm db:seed (requires the dev server to be running)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const API_URL = process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:4000';
|
|
9
|
+
|
|
10
|
+
interface SeedProduct {
|
|
11
|
+
name: string;
|
|
12
|
+
slug: string;
|
|
13
|
+
description: string;
|
|
14
|
+
price: number;
|
|
15
|
+
status: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const products: SeedProduct[] = [
|
|
19
|
+
{
|
|
20
|
+
name: 'Wireless Headphones',
|
|
21
|
+
slug: 'wireless-headphones',
|
|
22
|
+
description:
|
|
23
|
+
'Premium wireless headphones with active noise cancellation and 30-hour battery life.',
|
|
24
|
+
price: 9999,
|
|
25
|
+
status: 'active',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'Mechanical Keyboard',
|
|
29
|
+
slug: 'mechanical-keyboard',
|
|
30
|
+
description:
|
|
31
|
+
'Compact 75% mechanical keyboard with hot-swappable switches and RGB backlighting.',
|
|
32
|
+
price: 14999,
|
|
33
|
+
status: 'active',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'USB-C Hub',
|
|
37
|
+
slug: 'usb-c-hub',
|
|
38
|
+
description: '7-in-1 USB-C hub with HDMI, USB-A, SD card reader, and 100W power delivery.',
|
|
39
|
+
price: 4999,
|
|
40
|
+
status: 'active',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const log = (...args: unknown[]) => process.stdout.write(`${args.join(' ')}\n`);
|
|
45
|
+
const logErr = (...args: unknown[]) => process.stderr.write(`${args.join(' ')}\n`);
|
|
46
|
+
|
|
47
|
+
async function seed(): Promise<void> {
|
|
48
|
+
log(`Seeding products to ${API_URL}...`);
|
|
49
|
+
|
|
50
|
+
for (const product of products) {
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch(`${API_URL}/api/products`, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
body: JSON.stringify(product),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (res.ok) {
|
|
59
|
+
log(` Created: ${product.name}`);
|
|
60
|
+
} else {
|
|
61
|
+
const error = await res.text();
|
|
62
|
+
logErr(` Failed to create "${product.name}": ${error}`);
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
logErr(` Error creating "${product.name}":`, err);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
log('Seeding complete.');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
seed();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"strict": true,
|
|
5
|
+
"strictNullChecks": true,
|
|
6
|
+
"strictFunctionTypes": true,
|
|
7
|
+
"noImplicitReturns": true,
|
|
8
|
+
"noFallthroughCasesInSwitch": true,
|
|
9
|
+
"noUncheckedIndexedAccess": true,
|
|
10
|
+
"noUnusedLocals": true,
|
|
11
|
+
"noUnusedParameters": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"allowJs": true,
|
|
17
|
+
"jsx": "preserve",
|
|
18
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
19
|
+
"module": "ESNext",
|
|
20
|
+
"moduleResolution": "bundler",
|
|
21
|
+
"resolveJsonModule": true,
|
|
22
|
+
"resolvePackageJsonExports": true,
|
|
23
|
+
"target": "ES2022",
|
|
24
|
+
"noEmit": true,
|
|
25
|
+
"incremental": true,
|
|
26
|
+
"plugins": [{ "name": "next" }],
|
|
27
|
+
"baseUrl": ".",
|
|
28
|
+
"paths": {
|
|
29
|
+
"@/*": ["./src/*"]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"include": ["src", "next-env.d.ts", "next.config.mjs", "revealui.config.ts"],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# RevealUI Environment Variables
|
|
2
|
+
# Copy this file to .env.local and fill in your values before running `pnpm dev`
|
|
3
|
+
|
|
4
|
+
# ─── Core ────────────────────────────────────────────────────────────────────
|
|
5
|
+
# 32+ character secret used for signing sessions and tokens
|
|
6
|
+
REVEALUI_SECRET=change-me-to-a-long-random-secret-at-least-32-chars
|
|
7
|
+
|
|
8
|
+
# Public URL of your CMS server (must match NEXT_PUBLIC_SERVER_URL)
|
|
9
|
+
REVEALUI_PUBLIC_SERVER_URL=http://localhost:4000
|
|
10
|
+
NEXT_PUBLIC_SERVER_URL=http://localhost:4000
|
|
11
|
+
|
|
12
|
+
# ─── Database ────────────────────────────────────────────────────────────────
|
|
13
|
+
# PostgreSQL connection string (NeonDB, Supabase, or local Postgres)
|
|
14
|
+
POSTGRES_URL=postgresql://postgres:postgres@localhost:5432/revealui
|
|
15
|
+
|
|
16
|
+
# ─── Storage ─────────────────────────────────────────────────────────────────
|
|
17
|
+
# Vercel Blob token for file uploads (optional — leave placeholder for local dev)
|
|
18
|
+
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_placeholder
|
|
19
|
+
|
|
20
|
+
# ─── Stripe (optional) ───────────────────────────────────────────────────────
|
|
21
|
+
# Use test keys during development: https://dashboard.stripe.com/test/apikeys
|
|
22
|
+
STRIPE_SECRET_KEY=sk_test_placeholder
|
|
23
|
+
STRIPE_WEBHOOK_SECRET=whsec_placeholder
|
|
24
|
+
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_placeholder
|
|
25
|
+
|
|
26
|
+
# ─── Admin Bootstrap ─────────────────────────────────────────────────────────
|
|
27
|
+
# Used on first run only to create the initial admin account
|
|
28
|
+
REVEALUI_ADMIN_EMAIL=admin@example.com
|
|
29
|
+
REVEALUI_ADMIN_PASSWORD=changeme-min-12-chars
|
|
30
|
+
|
|
31
|
+
# ─── Branding (Enterprise white-label) ───────────────────────────────────────
|
|
32
|
+
# Customize the admin UI for your brand. Enterprise license required for full white-label.
|
|
33
|
+
# REVEALUI_BRAND_NAME=My CMS
|
|
34
|
+
# REVEALUI_BRAND_LOGO_URL=https://example.com/logo.png
|
|
35
|
+
# REVEALUI_BRAND_PRIMARY_COLOR=#ea580c
|
|
36
|
+
# REVEALUI_SHOW_POWERED_BY=false
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
.pnp
|
|
4
|
+
.pnp.js
|
|
5
|
+
|
|
6
|
+
# Next.js
|
|
7
|
+
.next/
|
|
8
|
+
out/
|
|
9
|
+
build/
|
|
10
|
+
|
|
11
|
+
# Environment variables
|
|
12
|
+
.env
|
|
13
|
+
.env.local
|
|
14
|
+
.env.development.local
|
|
15
|
+
.env.test.local
|
|
16
|
+
.env.production.local
|
|
17
|
+
|
|
18
|
+
# Turbo
|
|
19
|
+
.turbo/
|
|
20
|
+
|
|
21
|
+
# Misc
|
|
22
|
+
.DS_Store
|
|
23
|
+
*.pem
|
|
24
|
+
npm-debug.log*
|
|
25
|
+
yarn-debug.log*
|
|
26
|
+
yarn-error.log*
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev --port 4000",
|
|
8
|
+
"build": "next build",
|
|
9
|
+
"start": "next start --port 4000",
|
|
10
|
+
"lint": "biome check .",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"db:init": "revealui cms",
|
|
14
|
+
"db:migrate": "drizzle-kit migrate",
|
|
15
|
+
"db:seed": "tsx src/seed.ts"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@revealui/core": "latest",
|
|
19
|
+
"@revealui/config": "latest",
|
|
20
|
+
"@revealui/db": "latest",
|
|
21
|
+
"@revealui/auth": "latest",
|
|
22
|
+
"@revealui/contracts": "latest",
|
|
23
|
+
"next": "^16.0.0",
|
|
24
|
+
"react": "^19.0.0",
|
|
25
|
+
"react-dom": "^19.0.0",
|
|
26
|
+
"sharp": "^0.34.0",
|
|
27
|
+
"zod": "^4.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@biomejs/biome": "^2.0.0",
|
|
31
|
+
"@tailwindcss/postcss": "^4.1.0",
|
|
32
|
+
"tailwindcss": "^4.1.0",
|
|
33
|
+
"typescript": "^6.0.0",
|
|
34
|
+
"vitest": "^4.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import config from '@revealui/config';
|
|
2
|
+
import { buildConfig, universalPostgresAdapter } from '@revealui/core';
|
|
3
|
+
import sharp from 'sharp';
|
|
4
|
+
import { Projects } from './src/collections/Projects';
|
|
5
|
+
|
|
6
|
+
export default buildConfig({
|
|
7
|
+
serverURL: config.reveal.publicServerURL || 'http://localhost:4000',
|
|
8
|
+
secret: config.reveal.secret,
|
|
9
|
+
db: config.database.url
|
|
10
|
+
? universalPostgresAdapter({ connectionString: config.database.url })
|
|
11
|
+
: universalPostgresAdapter({ provider: 'electric' }),
|
|
12
|
+
admin: {
|
|
13
|
+
user: 'users',
|
|
14
|
+
},
|
|
15
|
+
collections: [Projects],
|
|
16
|
+
globals: [],
|
|
17
|
+
plugins: [],
|
|
18
|
+
sharp,
|
|
19
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import './globals.css';
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: 'RevealUI App',
|
|
6
|
+
description: 'Built with RevealUI',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
10
|
+
return (
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<body>{children}</body>
|
|
13
|
+
</html>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
|
|
3
|
+
export default function HomePage() {
|
|
4
|
+
return (
|
|
5
|
+
<main className="mx-auto max-w-2xl px-4 py-20">
|
|
6
|
+
<p className="text-sm font-medium text-accent">Portfolio</p>
|
|
7
|
+
<h1 className="mt-2 text-4xl font-bold tracking-tight text-gray-900">
|
|
8
|
+
Hi, I'm [Your Name]
|
|
9
|
+
</h1>
|
|
10
|
+
<p className="mt-4 text-lg leading-relaxed text-gray-600">
|
|
11
|
+
I build things for the web. This portfolio is powered by{' '}
|
|
12
|
+
<a
|
|
13
|
+
href="https://revealui.com"
|
|
14
|
+
className="font-medium text-accent hover:text-accent-hover"
|
|
15
|
+
target="_blank"
|
|
16
|
+
rel="noopener noreferrer"
|
|
17
|
+
>
|
|
18
|
+
RevealUI
|
|
19
|
+
</a>{' '}
|
|
20
|
+
— edit your projects from the admin panel, no code changes needed.
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<div className="mt-8 flex gap-3">
|
|
24
|
+
<Link
|
|
25
|
+
href="/projects"
|
|
26
|
+
className="rounded-lg bg-accent px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-hover"
|
|
27
|
+
>
|
|
28
|
+
View projects
|
|
29
|
+
</Link>
|
|
30
|
+
<a
|
|
31
|
+
href="/admin"
|
|
32
|
+
className="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50"
|
|
33
|
+
>
|
|
34
|
+
Admin panel
|
|
35
|
+
</a>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div className="mt-16 border-t border-gray-200 pt-8">
|
|
39
|
+
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">Get started</h2>
|
|
40
|
+
<ol className="mt-3 space-y-2 text-sm text-gray-700">
|
|
41
|
+
<li>
|
|
42
|
+
Replace <code className="rounded bg-gray-200 px-1.5 py-0.5 text-xs">[Your Name]</code>{' '}
|
|
43
|
+
above with your name
|
|
44
|
+
</li>
|
|
45
|
+
<li>
|
|
46
|
+
<code className="rounded bg-gray-200 px-1.5 py-0.5 text-xs">pnpm db:seed</code> — add
|
|
47
|
+
sample projects
|
|
48
|
+
</li>
|
|
49
|
+
<li>
|
|
50
|
+
Visit{' '}
|
|
51
|
+
<a href="/admin" className="text-accent hover:text-accent-hover">
|
|
52
|
+
/admin
|
|
53
|
+
</a>{' '}
|
|
54
|
+
— add your own projects, links, and tags
|
|
55
|
+
</li>
|
|
56
|
+
</ol>
|
|
57
|
+
</div>
|
|
58
|
+
</main>
|
|
59
|
+
);
|
|
60
|
+
}
|