diff --git a/package-lock.json b/package-lock.json index ad9ccad..3db076c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1034,6 +1034,27 @@ "giget": "dist/cli.mjs" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", diff --git a/src/generate.js b/src/generate.js index 7598a6f..adefa61 100644 --- a/src/generate.js +++ b/src/generate.js @@ -1,9 +1,10 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; +import crypto from 'crypto'; import chalk from 'chalk'; import ora from 'ora'; -import { downloadTemplate } from 'giget'; // ⬅Swapped tiged for giget +import { downloadTemplate } from 'giget'; import Handlebars from 'handlebars'; import { execSync } from 'child_process'; import { resolveDependencies } from './dependencies.js'; @@ -33,6 +34,11 @@ function getAllFiles(dirPath, arrayOfFiles = []) { return arrayOfFiles; } +// Utility function to generate a SHA-256 hash from file contents +function getFileHash(data) { + return crypto.createHash('sha256').update(data).digest('hex'); +} + export async function generateProject(config) { const verbose = config.verbose || false; const totalStart = Date.now(); @@ -53,34 +59,35 @@ export async function generateProject(config) { let projectName = config.projectName; let projectPath = path.join(process.cwd(), projectName); - // 1. Resolve naming collisions - while (fs.existsSync(projectPath)) { - const randomSuffix = Math.floor(Math.random() * 10000); - projectName = `${config.projectName}-${randomSuffix}`; - projectPath = path.join(process.cwd(), projectName); + if (!fs.existsSync(projectPath)) { + fs.mkdirSync(projectPath, { recursive: true }); } - config.projectName = projectName; // Update config with final name // 2. Check for Local vs GitHub const localTemplatePath = path.join( __dirname, - '..', // Go up one level from 'src' to reach the root where 'templates' is + '..', 'templates', config.template, config.architecture, ); + // Create a temporary staging directory + const stagingPath = path.join(process.cwd(), `.opusify-staging-${Date.now()}`); + try { if (fs.existsSync(localTemplatePath)) { // 🟢 DEVELOPMENT MODE: Local folder found const spinner = ora({ - text: 'DEV MODE: Copying local template...', + text: 'DEV MODE: Copying local template to staging...', spinner: 'dots', color: 'blue' }).start(); const copyStart = Date.now(); - fs.cpSync(localTemplatePath, projectPath, { recursive: true }); - spinner.succeed(`Files copied to ./${projectName}`); + + fs.cpSync(localTemplatePath, stagingPath, { recursive: true }); + + spinner.succeed(`Template resolved for ./${projectName}`); if (verbose) { console.log(chalk.gray(` [copy] Source: ${localTemplatePath}`)); console.log(chalk.gray(` [copy] Duration: ${Date.now() - copyStart}ms`)); @@ -88,7 +95,6 @@ export async function generateProject(config) { } else { // 🔵 PRODUCTION MODE: Fetch from GitHub using GIGET const targetRepo = config.repo || 'Ebyte-Lab/opusify-templates'; - // giget syntax requires the provider prefix: github:org/repo/subdir const repoInput = `github:${targetRepo}/${config.template}/${config.architecture}`; const spinner = ora({ @@ -99,84 +105,117 @@ export async function generateProject(config) { try { await downloadTemplate(repoInput, { - dir: projectPath, + dir: stagingPath, force: true, - auth: process.env.GITHUB_TOKEN // Passes token natively to giget + auth: process.env.GITHUB_TOKEN }); - spinner.succeed(`Files copied to ./${projectName}`); + spinner.succeed(`Template fetched for ./${projectName}`); } catch (fetchError) { spinner.fail(`Failed to fetch template from GitHub: ${repoInput}`); - - // SPECIFIC NETWORK & GITHUB ERROR HANDLING - const errStr = fetchError.toString().toLowerCase(); - if (errStr.includes('could not resolve') || errStr.includes('econnrefused') || errStr.includes('offline') || errStr.includes('network')) { - console.log(chalk.red(' ✖ Error: Could not reach GitHub. Check your internet connection.')); - console.log(chalk.gray(' Suggested fix: Ensure you are connected to the internet and try again.')); - } else if (errStr.includes('404') || errStr.includes('not found')) { - console.log(chalk.red(' ✖ Error: The specified template or repository does not exist.')); - if (!config.token) { - console.log(chalk.yellow(' ⚠️ Hint: If this repository is private, you must provide a GitHub token using --token or set the OPUSIFY_GITHUB_TOKEN environment variable.')); - } - } else { - console.log(chalk.red(` ✖ Error details: ${fetchError.message}`)); - } throw new Error('FETCH_FAILED', { cause: fetchError }); } } - // 3. TRANSFORM PHASE: Process Handlebars Tags + // 3. TRANSFORM & DEDUPLICATE PHASE const compileSpinner = ora({ - text: 'Compiling template tags...', + text: 'Compiling templates and verifying file hashes...', spinner: 'dots', color: 'cyan' }).start(); const compileStart = Date.now(); - const allFiles = getAllFiles(projectPath); + + const allFiles = getAllFiles(stagingPath); let compiledCount = 0; + let skippedCount = 0; + + for (const tempFile of allFiles) { + const relativePath = path.relative(stagingPath, tempFile); + const targetFile = path.join(projectPath, relativePath); + + const targetDir = path.dirname(targetFile); + if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, { recursive: true }); + } + + let finalContent; + const isTextFile = tempFile.match(/\.(tsx|ts|json|md|html|css|mjs|js|jsx)$/); + + if (isTextFile) { + let content = fs.readFileSync(tempFile, 'utf-8'); + + const hasStructuralBlocks = /\{\{\s*(#if|#unless|else|\/if|\/unless)\b/.test(content); + + if (hasStructuralBlocks) { + const safeRegex = /\{\{(\s*)(?!(?:#if|#unless|else|\/if|\/unless|eq|projectName|template|variant|architecture|design|navCount|includeSidebar|enableSecurity)(?:\s|\}))/g; + content = content.replace(safeRegex, '\\{{$1'); - for (const file of allFiles) { - if (file.match(/\.(tsx|ts|json|md|html|css|mjs)$/)) { - let content = fs.readFileSync(file, 'utf-8'); - if (content.includes('{{')) { const template = Handlebars.compile(content); - const result = template(config); - fs.writeFileSync(file, result); - compiledCount++; - if (verbose) { - const relPath = path.relative(projectPath, file); - console.log(chalk.gray(` [compile] Processed — ${relPath}`)); + content = template(config); + } else { + const placeholders = ['projectName', 'template', 'variant', 'architecture', 'design', 'navCount', 'includeSidebar', 'enableSecurity']; + for (const key of placeholders) { + const token = `{{${key}}}`; + if (content.includes(token)) { + content = content.replaceAll(token, config[key] !== undefined ? config[key] : ''); + } } } + + if (content.includes('\\{{')) { + content = content.replaceAll('\\{{', '{{'); + } + + finalContent = content; + } else { + finalContent = fs.readFileSync(tempFile); + } + + const newHash = getFileHash(finalContent); + let shouldWrite = true; + + if (fs.existsSync(targetFile)) { + const existingContent = fs.readFileSync(targetFile); + const existingHash = getFileHash(existingContent); + + if (newHash === existingHash) { + shouldWrite = false; + } + } + + if (shouldWrite) { + fs.writeFileSync(targetFile, finalContent); + compiledCount++; + if (verbose) { + console.log(chalk.gray(` [write] Updated — ${relativePath}`)); + } + } else { + skippedCount++; + if (verbose) { + console.log(chalk.gray(` [skip] Unchanged (SHA-256 matched) — ${relativePath}`)); + } } } - compileSpinner.succeed('Template customization complete!'); + + fs.rmSync(stagingPath, { recursive: true, force: true }); + + compileSpinner.succeed('Template compilation & deduplication complete!'); if (verbose) { - console.log(chalk.gray(` [compile] ${compiledCount} files compiled, ${allFiles.length} total scanned (${Date.now() - compileStart}ms)`)); + console.log(chalk.gray(` [audit] ${compiledCount} files written, ${skippedCount} files skipped (${Date.now() - compileStart}ms)`)); } - // 4. Save the config blueprint const configFilePath = path.join(projectPath, 'opusify.config.json'); fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2)); - // 5. Generate dynamic navigation based on navCount generateNavigation(projectPath, config); - - // 6. Resolve dynamic dependencies based on user choices resolveDependencies(projectPath, config); - - // 7. Apply security hardening if enabled applySecurity(projectPath, config); - // 8. AUTOMATION PHASE: Install Dependencies if (config.noInstall) { console.log(chalk.gray('\n⏭️ Skipping npm install (--no-install).')); } else { - - // CHECK FOR EXISTING NODE_MODULES const nodeModulesPath = path.join(projectPath, 'node_modules'); if (fs.existsSync(nodeModulesPath)) { console.log(chalk.yellow('\n⚠️ WARNING: A node_modules directory already exists in the target directory.')); - console.log(chalk.gray(' This can happen if you are testing locally and left it inside your template folder.')); console.log(chalk.gray(' Suggested fix: Delete node_modules from your template source to avoid copy bloat.')); } @@ -199,7 +238,6 @@ export async function generateProject(config) { } } - // 7. Git Initialization if (config.initGit) { const gitSpinner = ora({ text: 'Initializing Git repository...', @@ -222,7 +260,6 @@ export async function generateProject(config) { console.log(chalk.gray('\n⏭️ Skipping Git initialization.')); } - // 8. Final Success Message console.log(chalk.magenta(`\n🎉 Project ${projectName} is ready!`)); if (verbose) { console.log(chalk.gray(` [total] Generation completed in ${((Date.now() - totalStart) / 1000).toFixed(1)}s`)); @@ -233,27 +270,18 @@ export async function generateProject(config) { } catch (error) { console.log(chalk.red('\n🚨 Generation failed.')); - // Detailed System Error Classification + if (fs.existsSync(stagingPath)) { + try { + fs.rmSync(stagingPath, { recursive: true, force: true }); + } catch(e) { /* silent fail on cleanup */ } + } + if (error.code === 'ENOSPC') { console.log(chalk.red(' ✖ Error: Not enough disk space.')); - console.log(chalk.gray(' Suggested fix: Free up some space on your hard drive and try again.')); } else if (error.code === 'EACCES' || error.code === 'EPERM') { console.log(chalk.red(' ✖ Error: Permission denied.')); - console.log(chalk.gray(' Suggested fix: Check your folder permissions or run your terminal as an administrator/sudo.')); } else if (error.message !== 'FETCH_FAILED') { - // Print generic errors if it's not one we already handled above console.log(chalk.gray(` Details: ${error.message}`)); } - - // AUTOMATED CLEANUP - if (fs.existsSync(projectPath)) { - console.log(chalk.yellow(`\n🧹 Cleaning up partial project directory: ./${projectName}...`)); - try { - fs.rmSync(projectPath, { recursive: true, force: true }); - console.log(chalk.green(' ✔ Cleanup complete.')); - } catch { - console.log(chalk.red(` ✖ Failed to clean up directory. You may need to delete ./${projectName} manually.`)); - } - } } } \ No newline at end of file diff --git a/templates/blog/nextjs-monolith/components/AnimationProvider.tsx b/templates/blog/nextjs-monolith/components/AnimationProvider.tsx index feaf51c..d07ea91 100644 --- a/templates/blog/nextjs-monolith/components/AnimationProvider.tsx +++ b/templates/blog/nextjs-monolith/components/AnimationProvider.tsx @@ -11,10 +11,10 @@ export default function AnimationProvider({ return ( {children} diff --git a/templates/ecommerce/nextjs-monolith/app/cart/page.tsx b/templates/ecommerce/nextjs-monolith/app/cart/page.tsx index 208a5fc..abb5335 100644 --- a/templates/ecommerce/nextjs-monolith/app/cart/page.tsx +++ b/templates/ecommerce/nextjs-monolith/app/cart/page.tsx @@ -26,7 +26,7 @@ export default function CartPage() { {/* Image */}
{/* Info */} diff --git a/templates/ecommerce/nextjs-monolith/app/checkout/page.tsx b/templates/ecommerce/nextjs-monolith/app/checkout/page.tsx index 9cd32f8..ceb1e7a 100644 --- a/templates/ecommerce/nextjs-monolith/app/checkout/page.tsx +++ b/templates/ecommerce/nextjs-monolith/app/checkout/page.tsx @@ -193,7 +193,7 @@ export default function CheckoutPage() {

{item.name}

diff --git a/templates/ecommerce/nextjs-monolith/app/page.tsx b/templates/ecommerce/nextjs-monolith/app/page.tsx index 9d0297e..ffdf6e0 100644 --- a/templates/ecommerce/nextjs-monolith/app/page.tsx +++ b/templates/ecommerce/nextjs-monolith/app/page.tsx @@ -25,7 +25,7 @@ export default function Home() {
{/* Hero Banner */}
-
+
@@ -70,7 +70,7 @@ export default function Home() { >

{cat.name}

@@ -99,7 +99,7 @@ export default function Home() {
{product.badge && ( diff --git a/templates/ecommerce/nextjs-monolith/app/products/[slug]/page.tsx b/templates/ecommerce/nextjs-monolith/app/products/[slug]/page.tsx index 118e4fd..d941f40 100644 --- a/templates/ecommerce/nextjs-monolith/app/products/[slug]/page.tsx +++ b/templates/ecommerce/nextjs-monolith/app/products/[slug]/page.tsx @@ -41,7 +41,7 @@ export default function ProductDetailPage({ {/* Main Image */}
{/* Thumbnails */}
@@ -51,7 +51,7 @@ export default function ProductDetailPage({ className={`aspect-square rounded-theme border cursor-pointer transition ${ i === 0 ? 'border-primary ring-2 ring-primary/20' : 'border-border hover:border-primary/50' }`} - style=\{{ backgroundColor: i === 0 ? '#f1f5f9' : i === 1 ? '#e2e8f0' : i === 2 ? '#cbd5e1' : '#94a3b8' }} + style={{ backgroundColor: i === 0 ? '#f1f5f9' : i === 1 ? '#e2e8f0' : i === 2 ? '#cbd5e1' : '#94a3b8' }} /> ))}
@@ -100,7 +100,7 @@ export default function ProductDetailPage({ className={`w-8 h-8 rounded-full border-2 transition ${ i === 0 ? 'border-primary ring-2 ring-primary/20' : 'border-border hover:border-primary/50' }`} - style=\{{ backgroundColor: c.hex }} + style={{ backgroundColor: c.hex }} aria-label={c.name} /> ))} @@ -219,7 +219,7 @@ export default function ProductDetailPage({
diff --git a/templates/ecommerce/nextjs-monolith/app/products/page.tsx b/templates/ecommerce/nextjs-monolith/app/products/page.tsx index bc43036..660d248 100644 --- a/templates/ecommerce/nextjs-monolith/app/products/page.tsx +++ b/templates/ecommerce/nextjs-monolith/app/products/page.tsx @@ -99,7 +99,7 @@ export default function ProductsPage() {
{product.badge && ( diff --git a/templates/ecommerce/nextjs-monolith/components/AnimationProvider.tsx b/templates/ecommerce/nextjs-monolith/components/AnimationProvider.tsx index feaf51c..d07ea91 100644 --- a/templates/ecommerce/nextjs-monolith/components/AnimationProvider.tsx +++ b/templates/ecommerce/nextjs-monolith/components/AnimationProvider.tsx @@ -11,10 +11,10 @@ export default function AnimationProvider({ return ( {children} diff --git a/templates/ecommerce/nextjs-monolith/components/CartItem.tsx b/templates/ecommerce/nextjs-monolith/components/CartItem.tsx index 7531425..1ab23ca 100644 --- a/templates/ecommerce/nextjs-monolith/components/CartItem.tsx +++ b/templates/ecommerce/nextjs-monolith/components/CartItem.tsx @@ -15,7 +15,7 @@ export default function CartItem({ name, price, quantity, color, size, imageColo {/* Image */}
{/* Info */} diff --git a/templates/ecommerce/nextjs-monolith/components/ProductCard.tsx b/templates/ecommerce/nextjs-monolith/components/ProductCard.tsx index 733b368..8635e7a 100644 --- a/templates/ecommerce/nextjs-monolith/components/ProductCard.tsx +++ b/templates/ecommerce/nextjs-monolith/components/ProductCard.tsx @@ -19,7 +19,7 @@ export default function ProductCard({ name, price, originalPrice, imageColor, ba
{/* Badge */} {badge && ( diff --git a/templates/ecommerce/vite-react/src/pages/Cart.tsx b/templates/ecommerce/vite-react/src/pages/Cart.tsx index d7d1c70..c31e05a 100644 --- a/templates/ecommerce/vite-react/src/pages/Cart.tsx +++ b/templates/ecommerce/vite-react/src/pages/Cart.tsx @@ -23,7 +23,7 @@ export default function Cart() {
{items.map((item, index) => (
-
+
diff --git a/templates/ecommerce/vite-react/src/pages/Home.tsx b/templates/ecommerce/vite-react/src/pages/Home.tsx index a29177b..4fce55a 100644 --- a/templates/ecommerce/vite-react/src/pages/Home.tsx +++ b/templates/ecommerce/vite-react/src/pages/Home.tsx @@ -19,7 +19,7 @@ export default function Home() {
{/* Hero */}
-
+

@@ -46,7 +46,7 @@ export default function Home() {
{categories.map((cat) => ( -
+

{cat.name}

{cat.count} items

@@ -66,7 +66,7 @@ export default function Home() { {featured.map((product) => (
-
+

{product.name}

diff --git a/templates/ecommerce/vite-react/src/pages/ProductDetail.tsx b/templates/ecommerce/vite-react/src/pages/ProductDetail.tsx index 8df6720..9dccb0b 100644 --- a/templates/ecommerce/vite-react/src/pages/ProductDetail.tsx +++ b/templates/ecommerce/vite-react/src/pages/ProductDetail.tsx @@ -30,7 +30,7 @@ export default function ProductDetail() {
{[0, 1, 2, 3].map((i) => ( -
+
))}
@@ -63,7 +63,7 @@ export default function ProductDetail() {

Color

{colors.map((c, i) => ( -
diff --git a/templates/ecommerce/vite-react/src/pages/Products.tsx b/templates/ecommerce/vite-react/src/pages/Products.tsx index 3d1e0a4..9e83a0d 100644 --- a/templates/ecommerce/vite-react/src/pages/Products.tsx +++ b/templates/ecommerce/vite-react/src/pages/Products.tsx @@ -51,7 +51,7 @@ export default function Products() { {products.map((product) => (
-
+
{product.badge && ( {product.badge} diff --git a/templates/portfolio/nextjs-monolith/app/page.tsx b/templates/portfolio/nextjs-monolith/app/page.tsx index ab18076..ac197f1 100644 --- a/templates/portfolio/nextjs-monolith/app/page.tsx +++ b/templates/portfolio/nextjs-monolith/app/page.tsx @@ -39,7 +39,7 @@ export default function Home() { {/* Decorative gradient blob */}
diff --git a/templates/portfolio/nextjs-monolith/app/projects/page.tsx b/templates/portfolio/nextjs-monolith/app/projects/page.tsx index 70c5a9d..013ec61 100644 --- a/templates/portfolio/nextjs-monolith/app/projects/page.tsx +++ b/templates/portfolio/nextjs-monolith/app/projects/page.tsx @@ -86,7 +86,7 @@ export default function ProjectsPage() { {/* Image Placeholder */}
Project Preview @@ -133,4 +133,4 @@ export default function ProjectsPage() {
); -} +} \ No newline at end of file diff --git a/templates/portfolio/nextjs-monolith/app/skills/page.tsx b/templates/portfolio/nextjs-monolith/app/skills/page.tsx index c8bbd94..f2a8b93 100644 --- a/templates/portfolio/nextjs-monolith/app/skills/page.tsx +++ b/templates/portfolio/nextjs-monolith/app/skills/page.tsx @@ -77,7 +77,7 @@ export default function SkillsPage() {
diff --git a/templates/portfolio/nextjs-monolith/components/AnimationProvider.tsx b/templates/portfolio/nextjs-monolith/components/AnimationProvider.tsx index feaf51c..d07ea91 100644 --- a/templates/portfolio/nextjs-monolith/components/AnimationProvider.tsx +++ b/templates/portfolio/nextjs-monolith/components/AnimationProvider.tsx @@ -11,10 +11,10 @@ export default function AnimationProvider({ return ( {children} diff --git a/templates/portfolio/nextjs-monolith/components/ProjectCard.tsx b/templates/portfolio/nextjs-monolith/components/ProjectCard.tsx index 16e8fd3..80fd744 100644 --- a/templates/portfolio/nextjs-monolith/components/ProjectCard.tsx +++ b/templates/portfolio/nextjs-monolith/components/ProjectCard.tsx @@ -20,7 +20,7 @@ export default function ProjectCard({ {/* Image Placeholder */}
Project Preview @@ -61,4 +61,4 @@ export default function ProjectCard({
); -} +} \ No newline at end of file diff --git a/templates/portfolio/vite-react/src/pages/Home.tsx b/templates/portfolio/vite-react/src/pages/Home.tsx index f6700cc..3725954 100644 --- a/templates/portfolio/vite-react/src/pages/Home.tsx +++ b/templates/portfolio/vite-react/src/pages/Home.tsx @@ -15,7 +15,7 @@ export default function Home() {

@@ -70,7 +70,7 @@ export default function Home() {
{featuredProjects.map((project) => (
-
+
Project Preview
diff --git a/templates/portfolio/vite-react/src/pages/Projects.tsx b/templates/portfolio/vite-react/src/pages/Projects.tsx index 1d60bcb..f0f3427 100644 --- a/templates/portfolio/vite-react/src/pages/Projects.tsx +++ b/templates/portfolio/vite-react/src/pages/Projects.tsx @@ -38,7 +38,7 @@ export default function Projects() {
{projects.map((project) => (
-
+
Project Preview
diff --git a/templates/portfolio/vite-react/src/pages/Skills.tsx b/templates/portfolio/vite-react/src/pages/Skills.tsx index 2551df9..5ffa19f 100644 --- a/templates/portfolio/vite-react/src/pages/Skills.tsx +++ b/templates/portfolio/vite-react/src/pages/Skills.tsx @@ -77,7 +77,7 @@ export default function Skills() {
diff --git a/templates/saas/nextjs-monolith/app/analytics/page.tsx b/templates/saas/nextjs-monolith/app/analytics/page.tsx index c9650fa..09089e9 100644 --- a/templates/saas/nextjs-monolith/app/analytics/page.tsx +++ b/templates/saas/nextjs-monolith/app/analytics/page.tsx @@ -141,7 +141,7 @@ export default function AnalyticsPage() {
{stage.count.toLocaleString()}
diff --git a/templates/saas/nextjs-monolith/app/billing/page.tsx b/templates/saas/nextjs-monolith/app/billing/page.tsx index b28f88a..b2d2cd6 100644 --- a/templates/saas/nextjs-monolith/app/billing/page.tsx +++ b/templates/saas/nextjs-monolith/app/billing/page.tsx @@ -61,7 +61,7 @@ export default function BillingPage() { 67GB / 100GB
-
+
diff --git a/templates/saas/nextjs-monolith/components/AnimationProvider.tsx b/templates/saas/nextjs-monolith/components/AnimationProvider.tsx index feaf51c..d07ea91 100644 --- a/templates/saas/nextjs-monolith/components/AnimationProvider.tsx +++ b/templates/saas/nextjs-monolith/components/AnimationProvider.tsx @@ -11,10 +11,10 @@ export default function AnimationProvider({ return ( {children} diff --git a/templates/saas/nextjs-monolith/components/BarChart.tsx b/templates/saas/nextjs-monolith/components/BarChart.tsx index 07b7f4f..bc48d8c 100644 --- a/templates/saas/nextjs-monolith/components/BarChart.tsx +++ b/templates/saas/nextjs-monolith/components/BarChart.tsx @@ -8,7 +8,7 @@ export default function BarChart({ data, height = 200 }: BarChartProps) { return (
-
+
{data.map((item) => { const barHeight = (item.value / max) * 100; return ( @@ -18,7 +18,7 @@ export default function BarChart({ data, height = 200 }: BarChartProps) {
); diff --git a/templates/saas/nextjs-monolith/components/DonutChart.tsx b/templates/saas/nextjs-monolith/components/DonutChart.tsx index 2fe0426..1c610b6 100644 --- a/templates/saas/nextjs-monolith/components/DonutChart.tsx +++ b/templates/saas/nextjs-monolith/components/DonutChart.tsx @@ -29,7 +29,7 @@ export default function DonutChart({ segments, size = 180 }: DonutChartProps) { return (
-
+
{seg.label} diff --git a/templates/saas/vite-react/src/pages/Analytics.tsx b/templates/saas/vite-react/src/pages/Analytics.tsx index ec8250c..97eea30 100644 --- a/templates/saas/vite-react/src/pages/Analytics.tsx +++ b/templates/saas/vite-react/src/pages/Analytics.tsx @@ -44,7 +44,7 @@ export default function Analytics() {
diff --git a/templates/saas/vite-react/src/pages/Dashboard.tsx b/templates/saas/vite-react/src/pages/Dashboard.tsx index 5efd44c..44d87f9 100644 --- a/templates/saas/vite-react/src/pages/Dashboard.tsx +++ b/templates/saas/vite-react/src/pages/Dashboard.tsx @@ -47,7 +47,7 @@ export default function Dashboard() {
{point.month}
diff --git a/templates/school/nextjs-monolith/app/courses/page.tsx b/templates/school/nextjs-monolith/app/courses/page.tsx index dbaa850..48d6a7b 100644 --- a/templates/school/nextjs-monolith/app/courses/page.tsx +++ b/templates/school/nextjs-monolith/app/courses/page.tsx @@ -115,7 +115,7 @@ export default function CoursesPage() {
diff --git a/templates/school/nextjs-monolith/components/AnimationProvider.tsx b/templates/school/nextjs-monolith/components/AnimationProvider.tsx index feaf51c..d07ea91 100644 --- a/templates/school/nextjs-monolith/components/AnimationProvider.tsx +++ b/templates/school/nextjs-monolith/components/AnimationProvider.tsx @@ -11,10 +11,10 @@ export default function AnimationProvider({ return ( {children}