From c0cc64bc584e652ecf1ad7b92504688b69ca1c7e Mon Sep 17 00:00:00 2001 From: Josephat-S Date: Mon, 25 May 2026 22:12:55 +0200 Subject: [PATCH] add dynamic Navigation Generation Based on navCount --- src/generate.js | 6 +- src/navigation.js | 157 ++++++++++++++++++ templates/blog/nextjs-monolith/app/layout.tsx | 16 +- .../ecommerce/nextjs-monolith/app/layout.tsx | 8 +- .../portfolio/nextjs-monolith/app/layout.tsx | 36 +++- templates/saas/nextjs-monolith/app/layout.tsx | 15 +- .../saas/vite-react/src/components/Layout.tsx | 9 +- .../school/nextjs-monolith/app/layout.tsx | 10 +- 8 files changed, 202 insertions(+), 55 deletions(-) create mode 100644 src/navigation.js diff --git a/src/generate.js b/src/generate.js index 5b20be7..ac1e2f9 100644 --- a/src/generate.js +++ b/src/generate.js @@ -6,6 +6,7 @@ import tiged from 'tiged'; import Handlebars from 'handlebars'; import { execSync } from 'child_process'; import { resolveDependencies } from './dependencies.js'; +import { generateNavigation } from './navigation.js'; // Register custom Handlebars helpers Handlebars.registerHelper('eq', function (a, b) { @@ -101,7 +102,10 @@ export async function generateProject(config) { const configFilePath = path.join(projectPath, 'opusify.config.json'); fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2)); - // 5. Resolve dynamic dependencies based on user choices + // 5. Generate dynamic navigation based on navCount + generateNavigation(projectPath, config); + + // 6. Resolve dynamic dependencies based on user choices resolveDependencies(projectPath, config); // 6. AUTOMATION PHASE: Install Dependencies diff --git a/src/navigation.js b/src/navigation.js new file mode 100644 index 0000000..41dd73d --- /dev/null +++ b/src/navigation.js @@ -0,0 +1,157 @@ +import fs from 'fs'; +import path from 'path'; + +// Page pools for each template — ordered by priority +const PAGE_POOLS = { + portfolio: [ + { label: 'Home', href: '/', slug: null }, + { label: 'About', href: '/about', slug: 'about' }, + { label: 'Projects', href: '/projects', slug: 'projects' }, + { label: 'Skills', href: '/skills', slug: 'skills' }, + { label: 'Contact', href: '/contact', slug: 'contact' }, + { label: 'Blog', href: '/blog', slug: 'blog' }, + { label: 'Testimonials', href: '/testimonials', slug: 'testimonials' }, + { label: 'Resume', href: '/resume', slug: 'resume' }, + { label: 'Services', href: '/services', slug: 'services' }, + ], + ecommerce: [ + { label: 'Home', href: '/', slug: null }, + { label: 'Products', href: '/products', slug: 'products' }, + { label: 'Cart', href: '/cart', slug: 'cart' }, + { label: 'Account', href: '/account', slug: 'account' }, + { label: 'Wishlist', href: '/wishlist', slug: 'wishlist' }, + { label: 'Orders', href: '/orders', slug: 'orders' }, + { label: 'Categories', href: '/categories', slug: 'categories' }, + { label: 'Search', href: '/search', slug: 'search' }, + { label: 'Support', href: '/support', slug: 'support' }, + ], + school: [ + { label: 'Dashboard', href: '/', slug: null }, + { label: 'Students', href: '/students', slug: 'students' }, + { label: 'Courses', href: '/courses', slug: 'courses' }, + { label: 'Grades', href: '/grades', slug: 'grades' }, + { label: 'Attendance', href: '/attendance', slug: 'attendance' }, + { label: 'Schedule', href: '/schedule', slug: 'schedule' }, + { label: 'Payments', href: '/payments', slug: 'payments' }, + { label: 'Reports', href: '/reports', slug: 'reports' }, + { label: 'Staff', href: '/staff', slug: 'staff' }, + ], + saas: [ + { label: 'Dashboard', href: '/', slug: null }, + { label: 'Analytics', href: '/analytics', slug: 'analytics' }, + { label: 'Users', href: '/users', slug: 'users' }, + { label: 'Billing', href: '/billing', slug: 'billing' }, + { label: 'Settings', href: '/settings', slug: 'settings' }, + { label: 'Reports', href: '/reports', slug: 'reports' }, + { label: 'Integrations', href: '/integrations', slug: 'integrations' }, + { label: 'Team', href: '/team', slug: 'team' }, + { label: 'Support', href: '/support', slug: 'support' }, + ], + blog: [ + { label: 'Home', href: '/', slug: null }, + { label: 'Articles', href: '/articles', slug: 'articles' }, + { label: 'Categories', href: '/categories', slug: 'categories' }, + { label: 'Authors', href: '/authors', slug: 'authors' }, + { label: 'Newsletter', href: '/newsletter', slug: 'newsletter' }, + { label: 'About', href: '/about', slug: 'about' }, + { label: 'Tags', href: '/tags', slug: 'tags' }, + { label: 'Archive', href: '/archive', slug: 'archive' }, + { label: 'Contact', href: '/contact', slug: 'contact' }, + ], +}; + +function generateNavTs(selectedPages) { + return `export interface NavItem { + label: string; + href: string; +} + +export const navLinks: NavItem[] = [ +${selectedPages.map((p) => ` { label: '${p.label}', href: '${p.href}' },`).join('\n')} +]; +`; +} + +function generatePageComponent(page, config) { + return `export default function ${page.label.replace(/\s+/g, '')}Page() { + return ( +
+
+

${page.label}

+

+ This is the ${page.label} page for ${config.projectName}. +

+
+

+ Content for this page will be added here. This placeholder was generated + by Opusify CLI based on your navigation configuration. +

+
+
+
+ ); +} +`; +} + +function generateVitePageComponent(page, config) { + return `export default function ${page.label.replace(/\s+/g, '')}() { + return ( +
+
+

${page.label}

+

+ This is the ${page.label} page for ${config.projectName}. +

+
+

+ Content for this page will be added here. This placeholder was generated + by Opusify CLI based on your navigation configuration. +

+
+
+
+ ); +} +`; +} + +export function generateNavigation(projectPath, config) { + const pool = PAGE_POOLS[config.template]; + if (!pool) return; + + const navCount = Math.min(Math.max(config.navCount || 5, 3), 9); + const selectedPages = pool.slice(0, navCount); + const isVite = config.architecture === 'vite-react'; + + // 1. Generate lib/nav.ts (or src/lib/nav.ts for Vite) + const navDir = isVite + ? path.join(projectPath, 'src', 'lib') + : path.join(projectPath, 'lib'); + + if (!fs.existsSync(navDir)) fs.mkdirSync(navDir, { recursive: true }); + fs.writeFileSync(path.join(navDir, 'nav.ts'), generateNavTs(selectedPages)); + + // 2. Generate route/page files for pages that don't already exist + for (const page of selectedPages) { + if (!page.slug) continue; // Skip home page — already exists as page.tsx + + if (isVite) { + const pagePath = path.join(projectPath, 'src', 'pages', `${page.label.replace(/\s+/g, '')}.tsx`); + if (!fs.existsSync(pagePath)) { + const dir = path.dirname(pagePath); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(pagePath, generateVitePageComponent(page, config)); + } + } else { + const routeDir = path.join(projectPath, 'app', page.slug); + const routeFile = path.join(routeDir, 'page.tsx'); + if (!fs.existsSync(routeFile)) { + if (!fs.existsSync(routeDir)) fs.mkdirSync(routeDir, { recursive: true }); + fs.writeFileSync(routeFile, generatePageComponent(page, config)); + } + } + } + + return selectedPages; +} diff --git a/templates/blog/nextjs-monolith/app/layout.tsx b/templates/blog/nextjs-monolith/app/layout.tsx index 297b2c1..167e16a 100644 --- a/templates/blog/nextjs-monolith/app/layout.tsx +++ b/templates/blog/nextjs-monolith/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import Link from 'next/link'; import './globals.css'; +import { navLinks } from '../lib/nav'; import AnimationProvider from '../components/AnimationProvider'; {{#if (eq design "Dark Terminal")}} import { Terminal } from 'lucide-react'; @@ -11,13 +12,6 @@ export const metadata: Metadata = { description: 'A {{variant}} blog built with Opusify CLI.', }; -const navLinks = [ - { label: 'Home', href: '/' }, - { label: 'Articles', href: '/articles' }, - { label: 'Categories', href: '/categories' }, - { label: 'Authors', href: '/authors' }, -]; - function Navbar() { return (
@@ -40,11 +34,9 @@ function Navbar() { ))} -
- -
+
); diff --git a/templates/ecommerce/nextjs-monolith/app/layout.tsx b/templates/ecommerce/nextjs-monolith/app/layout.tsx index 23d3441..c9fa802 100644 --- a/templates/ecommerce/nextjs-monolith/app/layout.tsx +++ b/templates/ecommerce/nextjs-monolith/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import Link from 'next/link'; import './globals.css'; +import { navLinks } from '../lib/nav'; import AnimationProvider from '../components/AnimationProvider'; {{#if (eq design "Dark Terminal")}} import { Terminal } from 'lucide-react'; @@ -11,13 +12,6 @@ export const metadata: Metadata = { description: 'A {{variant}} e-commerce store built with Opusify CLI.', }; -const navLinks = [ - { label: 'Home', href: '/' }, - { label: 'Products', href: '/products' }, - { label: 'Cart', href: '/cart' }, - { label: 'Account', href: '/account' }, -]; - function Navbar() { return (
diff --git a/templates/portfolio/nextjs-monolith/app/layout.tsx b/templates/portfolio/nextjs-monolith/app/layout.tsx index dcc7872..e77f3b2 100644 --- a/templates/portfolio/nextjs-monolith/app/layout.tsx +++ b/templates/portfolio/nextjs-monolith/app/layout.tsx @@ -1,5 +1,7 @@ import type { Metadata } from 'next'; +import Link from 'next/link'; import './globals.css'; +import { navLinks } from '../lib/nav'; import AnimationProvider from '../components/AnimationProvider'; {{#if (eq design "Dark Terminal")}} import { Terminal } from 'lucide-react'; @@ -10,6 +12,33 @@ export const metadata: Metadata = { description: 'A {{variant}} portfolio built with Opusify CLI.', }; +function Navbar() { + return ( +
+ +
+ ); +} + export default function RootLayout({ children, }: { @@ -18,12 +47,7 @@ export default function RootLayout({ return ( - {{#if (eq design "Dark Terminal")}} -
- - {{projectName}} -
- {{/if}} + {children} diff --git a/templates/saas/nextjs-monolith/app/layout.tsx b/templates/saas/nextjs-monolith/app/layout.tsx index 538ee7f..4e40e07 100644 --- a/templates/saas/nextjs-monolith/app/layout.tsx +++ b/templates/saas/nextjs-monolith/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import Link from 'next/link'; import './globals.css'; +import { navLinks } from '../lib/nav'; import AnimationProvider from '../components/AnimationProvider'; {{#if (eq design "Dark Terminal")}} import { Terminal } from 'lucide-react'; @@ -11,14 +12,6 @@ export const metadata: Metadata = { description: 'A {{variant}} SaaS dashboard built with Opusify CLI.', }; -const navLinks = [ - { label: 'Dashboard', href: '/' }, - { label: 'Analytics', href: '/analytics' }, - { label: 'Users', href: '/users' }, - { label: 'Billing', href: '/billing' }, - { label: 'Settings', href: '/settings' }, -]; - function Navbar() { return (
@@ -41,10 +34,8 @@ function Navbar() { ))} -
-
- A -
+
+ A
diff --git a/templates/saas/vite-react/src/components/Layout.tsx b/templates/saas/vite-react/src/components/Layout.tsx index d3f2553..0ee2fdd 100644 --- a/templates/saas/vite-react/src/components/Layout.tsx +++ b/templates/saas/vite-react/src/components/Layout.tsx @@ -1,12 +1,5 @@ import { Link, Outlet } from 'react-router-dom'; - -const navLinks = [ - { label: 'Dashboard', href: '/' }, - { label: 'Analytics', href: '/analytics' }, - { label: 'Users', href: '/users' }, - { label: 'Billing', href: '/billing' }, - { label: 'Settings', href: '/settings' }, -]; +import { navLinks } from '../lib/nav'; function Navbar() { return ( diff --git a/templates/school/nextjs-monolith/app/layout.tsx b/templates/school/nextjs-monolith/app/layout.tsx index 2bee723..94ef914 100644 --- a/templates/school/nextjs-monolith/app/layout.tsx +++ b/templates/school/nextjs-monolith/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import Link from 'next/link'; import './globals.css'; +import { navLinks } from '../lib/nav'; import AnimationProvider from '../components/AnimationProvider'; {{#if (eq design "Dark Terminal")}} import { Terminal } from 'lucide-react'; @@ -11,15 +12,6 @@ export const metadata: Metadata = { description: 'A {{variant}} school management system built with Opusify CLI.', }; -const navLinks = [ - { label: 'Dashboard', href: '/' }, - { label: 'Students', href: '/students' }, - { label: 'Courses', href: '/courses' }, - { label: 'Grades', href: '/grades' }, - { label: 'Attendance', href: '/attendance' }, - { label: 'Schedule', href: '/schedule' }, -]; - function Navbar() { return (