Initial react editor website template

This commit is contained in:
Rami Bitar
2026-08-29 14:03:50 -04:00
commit 3e072967e6
61 changed files with 7041 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
import { NextResponse } from 'next/server';
import { z } from 'zod';
const contactSchema = z.object({
name: z.string().trim().min(2, 'Please enter your name.').max(100),
email: z.string().trim().email('Please enter a valid email address.').max(254),
company: z.string().trim().max(120).optional().default(''),
message: z.string().trim().min(10, 'Please share a little more detail.').max(5000),
website: z.string().max(0).optional().default(''),
});
export async function POST(request: Request) {
try {
const payload: unknown = await request.json();
const result = contactSchema.safeParse(payload);
if (!result.success) {
return NextResponse.json(
{ error: result.error.issues[0]?.message ?? 'Please check the form and try again.' },
{ status: 400 }
);
}
// Store the validated contact data here.
// For example: await db.contactSubmission.create({ data: result.data });
return NextResponse.json({ ok: true }, { status: 202 });
} catch {
return NextResponse.json({ error: 'Invalid request.' }, { status: 400 });
}
}
+6
View File
@@ -0,0 +1,6 @@
import PageEditor from '@/components/page-editor';
import page from '../page.json';
export default function ContactEditorPage() {
return <PageEditor pagePath="app/contact/page.json" page={page} />;
}
+29
View File
@@ -0,0 +1,29 @@
{
"root": {
"props": {
"title": "Contact — Your Company",
"description": "Tell us what you are working on and how we can help.",
"ogImage": ""
}
},
"content": [
{ "type": "header", "props": { "id": "header" }, "synced": true },
{
"type": "contact-form",
"props": {
"id": "contact-form",
"heading": "Let’s make something meaningful.",
"description": "Tell us what you are working on, where you are stuck, or what you want to make possible.",
"email": "hello@example.com",
"location": "New York, NY",
"nameLabel": "Name",
"emailLabel": "Email",
"companyLabel": "Company",
"messageLabel": "How can we help?",
"submitLabel": "Send message",
"successMessage": "Thanks — we will be in touch soon."
}
},
{ "type": "footer", "props": { "id": "footer" }, "synced": true }
]
}
+9
View File
@@ -0,0 +1,9 @@
import PageRender from '@/components/page-render';
import { pageMetadata } from '@/lib/page-metadata';
import page from './page.json';
export const metadata = pageMetadata(page, { path: '/contact' });
export default function ContactPage() {
return <PageRender page={page} />;
}
+6
View File
@@ -0,0 +1,6 @@
import PageEditor from '@/components/page-editor';
import page from '../page.json';
export default function EditorPage() {
return <PageEditor pagePath="app/page.json" page={page} />;
}
+137
View File
@@ -0,0 +1,137 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.22 0.02 245);
--card: oklch(1 0 0);
--card-foreground: oklch(0.22 0.02 245);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.22 0.02 245);
--primary: oklch(0.23 0.025 245);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.96 0.008 245);
--secondary-foreground: oklch(0.23 0.025 245);
--muted: oklch(0.97 0.007 245);
--muted-foreground: oklch(0.5 0.015 245);
--accent: oklch(0.94 0.025 250);
--accent-foreground: oklch(0.34 0.12 255);
--destructive: oklch(0.58 0.22 27);
--border: oklch(0.88 0.01 245);
--input: oklch(0.88 0.01 245);
--ring: oklch(0.56 0.2 255);
--brand: oklch(0.56 0.21 255);
--brand-soft: oklch(0.94 0.03 250);
--radius: 0.625rem;
}
.dark {
--background: oklch(0.15 0.014 264);
--foreground: oklch(0.95 0.006 85);
--card: oklch(0.19 0.016 264);
--card-foreground: oklch(0.95 0.006 85);
--popover: oklch(0.19 0.016 264);
--popover-foreground: oklch(0.95 0.006 85);
--primary: oklch(0.92 0.012 85);
--primary-foreground: oklch(0.18 0.012 264);
--secondary: oklch(0.24 0.018 264);
--secondary-foreground: oklch(0.93 0.008 85);
--muted: oklch(0.22 0.016 264);
--muted-foreground: oklch(0.69 0.012 85);
--accent: oklch(0.27 0.035 155);
--accent-foreground: oklch(0.9 0.04 155);
--destructive: oklch(0.7 0.18 27);
--border: oklch(1 0 0 / 12%);
--input: oklch(1 0 0 / 16%);
--ring: oklch(0.7 0.1 155);
--brand: oklch(0.7 0.16 250);
--brand-soft: oklch(0.27 0.04 250);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-brand: var(--brand);
--color-brand-soft: var(--brand-soft);
--radius-sm: calc(var(--radius) - 6px);
--radius-md: calc(var(--radius) - 3px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 6px);
--font-sans: var(--font-outfit), ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
}
@layer base {
* {
border-color: var(--border);
}
html {
scroll-behavior: smooth;
}
body {
background: var(--background);
color: var(--foreground);
font-feature-settings: 'kern' 1, 'tnum' 1;
}
h1,
h2,
h3 {
text-wrap: balance;
}
p {
text-wrap: pretty;
}
}
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
.animate-marquee {
animation: marquee var(--marquee-duration, 28s) linear infinite;
}
.marquee-reverse {
animation-direction: reverse;
}
.marquee-pause:hover {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.animate-marquee { animation: none; transform: none; }
}
.section-shell {
width: min(100% - 2rem, 80rem);
margin-inline: auto;
}
@media (min-width: 768px) {
.section-shell { width: min(100% - 5rem, 80rem); }
}
+44
View File
@@ -0,0 +1,44 @@
import type { Metadata } from 'next';
import { Outfit } from 'next/font/google';
import { ThemeProvider } from '@/components/theme-provider';
import { site } from '@/config/site';
import './globals.css';
const outfit = Outfit({
subsets: ['latin'],
variable: '--font-outfit',
});
export const metadata: Metadata = {
metadataBase: new URL(site.url),
title: { default: site.name, template: `%s — ${site.name}` },
description: site.description,
openGraph: {
type: 'website',
siteName: site.name,
title: site.name,
description: site.description,
},
twitter: {
card: 'summary_large_image',
title: site.name,
description: site.description,
},
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning className={outfit.variable}>
<body className="min-h-screen bg-background font-sans text-foreground antialiased">
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
);
}
+142
View File
@@ -0,0 +1,142 @@
{
"root": {
"props": {
"title": "Your Company — A clear website headline",
"description": "A short description of your business and the value it provides.",
"ogImage": ""
}
},
"content": [
{ "type": "header", "props": { "id": "header" }, "synced": true },
{
"type": "hero",
"props": {
"id": "hero",
"heading": "A clear headline about what your business does.",
"description": "Use this space to explain your value in one or two simple sentences.",
"primaryLabel": "Get started",
"primaryUrl": "/contact",
"secondaryLabel": "",
"secondaryUrl": "#features",
"imageUrl": "https://api.dicebear.com/10.x/glass/svg",
"imageAlt": "Abstract glass avatar illustration",
"imagePosition": "right"
}
},
{
"type": "logo-marquee",
"props": {
"id": "logos",
"heading": "Trusted by teams at",
"logos": [
{ "name": "Logo One", "imageUrl": "" },
{ "name": "Logo Two", "imageUrl": "" },
{ "name": "Logo Three", "imageUrl": "" },
{ "name": "Logo Four", "imageUrl": "" },
{ "name": "Logo Five", "imageUrl": "" }
],
"speed": "normal",
"direction": "left",
"pauseOnHover": "yes"
}
},
{
"type": "features",
"props": {
"id": "features",
"heading": "Explain the most important benefits of your business.",
"description": "",
"imageUrl": "https://api.dicebear.com/10.x/glass/svg",
"imageAlt": "Abstract glass avatar illustration",
"imagePosition": "left",
"items": [
{ "title": "First key benefit", "description": "Add a concise explanation of the value this benefit creates for your customers.", "ctaLabel": "", "ctaUrl": "#" },
{ "title": "Second key benefit", "description": "Use a clear, outcome-focused description that is easy to scan.", "ctaLabel": "", "ctaUrl": "#" },
{ "title": "Third key benefit", "description": "Give visitors one more reason to understand and trust your offer.", "ctaLabel": "", "ctaUrl": "#" }
]
}
},
{
"type": "stats",
"props": {
"id": "stats",
"heading": "Use a few clear numbers to build confidence.",
"stats": [
{ "value": "10k+", "label": "Customers", "description": "A short note about this result." },
{ "value": "42%", "label": "Growth", "description": "A short note about this result." },
{ "value": "24/7", "label": "Availability", "description": "A short note about this result." },
{ "value": "98%", "label": "Satisfaction", "description": "A short note about this result." }
]
}
},
{
"type": "testimonials",
"props": {
"id": "testimonials",
"heading": "Share what customers say about your business.",
"testimonials": [
{ "quote": "Add a short customer quote that explains the value of your work.", "name": "Customer name", "role": "Role, Company", "avatarUrl": "" },
{ "quote": "Use a second testimonial to reinforce the outcome customers can expect.", "name": "Customer name", "role": "Role, Company", "avatarUrl": "" },
{ "quote": "Keep each quote focused, credible, and easy for visitors to scan.", "name": "Customer name", "role": "Role, Company", "avatarUrl": "" }
]
}
},
{
"type": "pricing",
"props": {
"id": "pricing",
"heading": "Simple plans for different needs.",
"description": "Replace these sample plans with pricing that fits your business.",
"billingNote": "Prices shown in USD.",
"plans": [
{ "name": "Starter", "price": "$0", "period": "/ month", "description": "A short description of who this plan is for.", "features": "First included feature\nSecond included feature\nThird included feature", "ctaLabel": "Get started", "ctaUrl": "/contact", "badge": "", "highlighted": "no" },
{ "name": "Team", "price": "$24", "period": "/ month", "description": "A short description of who this plan is for.", "features": "Everything in Starter\nAnother useful feature\nPriority support", "ctaLabel": "Get started", "ctaUrl": "/contact", "badge": "Popular", "highlighted": "yes" },
{ "name": "Business", "price": "Custom", "period": "", "description": "A short description of who this plan is for.", "features": "Everything in Team\nAdvanced controls\nDedicated support", "ctaLabel": "Contact us", "ctaUrl": "/contact", "badge": "", "highlighted": "no" }
]
}
},
{
"type": "posts-carousel",
"props": {
"id": "posts-carousel",
"heading": "Explore useful articles, guides, and stories.",
"description": "",
"posts": [
{ "title": "Add the title of your first article or resource", "category": "Guide", "date": "August 20, 2026", "imageUrl": "https://api.dicebear.com/10.x/glass/svg", "imageAlt": "Abstract glass avatar illustration", "url": "#", "typeLabel": "Read" },
{ "title": "Share an update that helps customers learn more", "category": "News", "date": "August 14, 2026", "imageUrl": "https://api.dicebear.com/10.x/glass/svg", "imageAlt": "Abstract glass avatar illustration", "url": "#", "typeLabel": "Read" },
{ "title": "Feature a customer story or helpful perspective", "category": "Story", "date": "August 8, 2026", "imageUrl": "https://api.dicebear.com/10.x/glass/svg", "imageAlt": "Abstract glass avatar illustration", "url": "#", "typeLabel": "Read" },
{ "title": "Use additional cards to demonstrate the carousel", "category": "Article", "date": "August 1, 2026", "imageUrl": "https://api.dicebear.com/10.x/glass/svg", "imageAlt": "Abstract glass avatar illustration", "url": "#", "typeLabel": "Read" }
],
"viewAllLabel": "View all",
"viewAllUrl": "#"
}
},
{
"type": "faq",
"props": {
"id": "faq",
"heading": "Get answers to frequently asked questions.",
"description": "",
"items": [
{ "question": "What does your business offer?", "answer": "Use this space to provide a clear, concise answer to a common customer question." },
{ "question": "Who is this for?", "answer": "Describe the people, teams, or organizations that will benefit most from your offer." },
{ "question": "How do I get started?", "answer": "Explain the simplest next step and what customers can expect after they begin." },
{ "question": "Where can I learn more?", "answer": "Point visitors toward your contact page, documentation, or another helpful resource." }
]
}
},
{
"type": "cta",
"props": {
"id": "cta",
"heading": "Add a clear call to action.",
"description": "Use this space to explain what visitors should do next and why it matters.",
"primaryLabel": "Get started",
"primaryUrl": "/contact",
"secondaryLabel": "",
"secondaryUrl": "/contact"
}
},
{ "type": "footer", "props": { "id": "footer" }, "synced": true }
]
}
+9
View File
@@ -0,0 +1,9 @@
import PageRender from '@/components/page-render';
import { pageMetadata } from '@/lib/page-metadata';
import page from './page.json';
export const metadata = pageMetadata(page, { path: '/' });
export default function Page() {
return <PageRender page={page} />;
}