implement a multi-file template interpreter
This commit is contained in:
54
examples/react-app-generator/templates/api-base.tmpl
Normal file
54
examples/react-app-generator/templates/api-base.tmpl
Normal file
@ -0,0 +1,54 @@
|
||||
// Base API client configuration
|
||||
{{range .AST.Definitions}}{{if .Server}}const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://{{.Server.Settings | getHost}}:{{.Server.Settings | getPort}}';
|
||||
{{end}}{{end}}
|
||||
|
||||
export interface ApiError {
|
||||
message: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number;
|
||||
|
||||
constructor(message: string, status: number) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.name = 'ApiError';
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiRequest<T>(
|
||||
method: string,
|
||||
endpoint: string,
|
||||
data?: any
|
||||
): Promise<T> {
|
||||
const config: RequestInit = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
if (data) {
|
||||
config.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, config);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(`HTTP error! status: ${response.status}`, response.status);
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return null as T;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) {
|
||||
throw error;
|
||||
}
|
||||
throw new ApiError(`Network error: ${error instanceof Error ? error.message : 'Unknown error'}`, 0);
|
||||
}
|
||||
}
|
33
examples/react-app-generator/templates/api-client.tmpl
Normal file
33
examples/react-app-generator/templates/api-client.tmpl
Normal file
@ -0,0 +1,33 @@
|
||||
// API client for {{.Entity.Name}}
|
||||
import { apiRequest } from './client';
|
||||
|
||||
export interface {{.Entity.Name}} {
|
||||
{{range .Entity.Fields}} {{.Name}}: {{if eq .Type "string"}}string{{else if eq .Type "int"}}number{{else if eq .Type "boolean"}}boolean{{else if eq .Type "timestamp"}}Date{{else if eq .Type "text"}}string{{else}}any{{end}};
|
||||
{{end}}}
|
||||
|
||||
export const {{.Entity.Name | lower}}Api = {
|
||||
// Get all {{.Entity.Name | lower}}s
|
||||
getAll: async (): Promise<{{.Entity.Name}}[]> => {
|
||||
return apiRequest('GET', '/{{.Entity.Name | lower}}s');
|
||||
},
|
||||
|
||||
// Get {{.Entity.Name | lower}} by ID
|
||||
getById: async (id: string): Promise<{{.Entity.Name}}> => {
|
||||
return apiRequest('GET', `/{{.Entity.Name | lower}}s/${id}`);
|
||||
},
|
||||
|
||||
// Create new {{.Entity.Name | lower}}
|
||||
create: async (data: Omit<{{.Entity.Name}}, 'id' | 'created_at' | 'updated_at'>): Promise<{{.Entity.Name}}> => {
|
||||
return apiRequest('POST', '/{{.Entity.Name | lower}}s', data);
|
||||
},
|
||||
|
||||
// Update {{.Entity.Name | lower}}
|
||||
update: async (id: string, data: Partial<{{.Entity.Name}}>): Promise<{{.Entity.Name}}> => {
|
||||
return apiRequest('PUT', `/{{.Entity.Name | lower}}s/${id}`, data);
|
||||
},
|
||||
|
||||
// Delete {{.Entity.Name | lower}}
|
||||
delete: async (id: string): Promise<void> => {
|
||||
return apiRequest('DELETE', `/{{.Entity.Name | lower}}s/${id}`);
|
||||
},
|
||||
};
|
13
examples/react-app-generator/templates/app-component.tmpl
Normal file
13
examples/react-app-generator/templates/app-component.tmpl
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import AppRouter from './router';
|
||||
// import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<AppRouter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
46
examples/react-app-generator/templates/index-html.tmpl
Normal file
46
examples/react-app-generator/templates/index-html.tmpl
Normal file
@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="{{range .AST.Definitions}}{{if .Page}}{{if .Page.Description}}{{.Page.Description | derefString}}{{else}}Web site created using Masonry{{end}}{{break}}{{end}}{{end}}"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>{{range .AST.Definitions}}{{if .Page}}{{if .Page.Title}}{{.Page.Title | derefString}}{{else}}{{.Page.Name}}{{end}}{{break}}{{else}}Masonry App{{end}}{{end}}</title>
|
||||
|
||||
<!-- Tailwind CSS for styling -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
14
examples/react-app-generator/templates/index.tmpl
Normal file
14
examples/react-app-generator/templates/index.tmpl
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
// import './index.css';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
67
examples/react-app-generator/templates/manifest.yaml
Normal file
67
examples/react-app-generator/templates/manifest.yaml
Normal file
@ -0,0 +1,67 @@
|
||||
name: "React App Generator"
|
||||
description: "Generates a complete React application with pages, components, and API integration"
|
||||
outputs:
|
||||
# Generate public pages
|
||||
- path: "src/pages/{{.Page.Name}}.tsx"
|
||||
template: "page-public"
|
||||
iterator: "pages"
|
||||
item_context: "Page"
|
||||
condition: "layout_public"
|
||||
|
||||
# Generate admin pages
|
||||
- path: "src/pages/admin/{{.Page.Name}}.tsx"
|
||||
template: "page-admin"
|
||||
iterator: "pages"
|
||||
item_context: "Page"
|
||||
condition: "layout_admin"
|
||||
|
||||
# Generate component files for each section
|
||||
- path: "src/components/sections/{{.Section.Name | title}}Section.tsx"
|
||||
template: "section-component"
|
||||
iterator: "sections"
|
||||
item_context: "Section"
|
||||
|
||||
# Generate component files for each component
|
||||
- path: "src/components/{{.Component.Type | title}}Component.tsx"
|
||||
template: "react-component"
|
||||
iterator: "components"
|
||||
item_context: "Component"
|
||||
|
||||
# Generate API clients per entity
|
||||
- path: "src/api/{{.Entity.Name | lower}}.ts"
|
||||
template: "api-client"
|
||||
iterator: "entities"
|
||||
item_context: "Entity"
|
||||
|
||||
# Generate TypeScript types per entity
|
||||
- path: "src/types/{{.Entity.Name}}.ts"
|
||||
template: "typescript-types"
|
||||
iterator: "entities"
|
||||
item_context: "Entity"
|
||||
|
||||
# Single files - global configuration
|
||||
- path: "src/router.tsx"
|
||||
template: "react-router"
|
||||
condition: "has_pages"
|
||||
|
||||
- path: "src/api/client.ts"
|
||||
template: "api-base"
|
||||
condition: "has_entities"
|
||||
|
||||
- path: "package.json"
|
||||
template: "package-json"
|
||||
|
||||
- path: "src/App.tsx"
|
||||
template: "app-component"
|
||||
condition: "has_pages"
|
||||
|
||||
- path: "src/index.tsx"
|
||||
template: "index"
|
||||
|
||||
# Add the missing index.html file
|
||||
- path: "public/index.html"
|
||||
template: "index-html"
|
||||
|
||||
# Add TypeScript configuration file
|
||||
- path: "tsconfig.json"
|
||||
template: "tsconfig"
|
45
examples/react-app-generator/templates/package-json.tmpl
Normal file
45
examples/react-app-generator/templates/package-json.tmpl
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "masonry-generated-blog-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"@types/react": "^18.0.27",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.13.0",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"postcss": "^8.4.21"
|
||||
}
|
||||
}
|
||||
|
67
examples/react-app-generator/templates/page-admin.tmpl
Normal file
67
examples/react-app-generator/templates/page-admin.tmpl
Normal file
@ -0,0 +1,67 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
{{$relativePrefix := relativePrefix .Page.Path}}{{range .AST.Definitions}}{{if .Entity}}import { {{.Entity.Name}} } from '{{$relativePrefix}}types/{{.Entity.Name}}';
|
||||
{{end}}{{end}}
|
||||
{{range .Page.Sections}}import {{.Name | title}}Section from '{{$relativePrefix}}components/sections/{{.Name | title}}Section';
|
||||
{{end}}{{range .Page.Components}}import {{.Type | title}}Component from '{{$relativePrefix}}components/{{.Type | title}}Component';
|
||||
{{end}}
|
||||
|
||||
export default function {{.Page.Name}}Page() {
|
||||
const navigate = useNavigate();
|
||||
const [user] = useState(null); // TODO: Implement actual auth
|
||||
|
||||
// Redirect if not authenticated
|
||||
React.useEffect(() => {
|
||||
if (!user) {
|
||||
navigate('/login');
|
||||
}
|
||||
}, [user, navigate]);
|
||||
|
||||
if (!user) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
<header className="bg-white shadow">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex items-center">
|
||||
<Link to="/admin/dashboard" className="text-xl font-bold text-blue-600">
|
||||
Admin Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<nav className="flex space-x-8">
|
||||
{{range .AST.Definitions}}{{if .Page}}{{if eq .Page.Layout "admin"}}<Link
|
||||
to="{{.Page.Path}}"
|
||||
className="text-gray-500 hover:text-gray-700 px-3 py-2 rounded-md text-sm font-medium"
|
||||
>
|
||||
{{.Page.Name}}
|
||||
</Link>
|
||||
{{end}}{{end}}{{end}}
|
||||
<button className="text-red-600 hover:text-red-700 px-3 py-2 rounded-md text-sm font-medium">
|
||||
Logout
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div className="px-4 py-6 sm:px-0">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-6">
|
||||
{{if .Page.Title}}{{.Page.Title | derefString}}{{else}}{{.Page.Name}}{{end}}
|
||||
</h1>
|
||||
|
||||
{{range .Page.Sections}}
|
||||
<{{.Name | title}}Section />
|
||||
{{end}}
|
||||
|
||||
{{range .Page.Components}}
|
||||
<{{.Type | title}}Component />
|
||||
{{end}}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
50
examples/react-app-generator/templates/page-public.tmpl
Normal file
50
examples/react-app-generator/templates/page-public.tmpl
Normal file
@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
{{$relativePrefix := relativePrefix .Page.Path}}{{range .AST.Definitions}}{{if .Entity}}import { {{.Entity.Name}} } from '{{$relativePrefix}}types/{{.Entity.Name}}';
|
||||
{{end}}{{end}}
|
||||
{{range .Page.Sections}}import {{.Name | title}}Section from '{{$relativePrefix}}components/sections/{{.Name | title}}Section';
|
||||
{{end}}{{range .Page.Components}}import {{.Type | title}}Component from '{{$relativePrefix}}components/{{.Type | title}}Component';
|
||||
{{end}}
|
||||
|
||||
export default function {{.Page.Name}}Page() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<header className="bg-white shadow">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex items-center">
|
||||
<Link to="/" className="text-xl font-bold text-gray-900">
|
||||
My Blog
|
||||
</Link>
|
||||
</div>
|
||||
<nav className="flex space-x-8">
|
||||
{{range .AST.Definitions}}{{if .Page}}{{if ne .Page.Layout "admin"}}<Link
|
||||
to="{{.Page.Path}}"
|
||||
className="text-gray-500 hover:text-gray-700 px-3 py-2 rounded-md text-sm font-medium"
|
||||
>
|
||||
{{.Page.Name}}
|
||||
</Link>
|
||||
{{end}}{{end}}{{end}}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div className="px-4 py-6 sm:px-0">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-6">
|
||||
{{if .Page.Title}}{{.Page.Title | derefString}}{{else}}{{.Page.Name}}{{end}}
|
||||
</h1>
|
||||
|
||||
{{range .Page.Sections}}
|
||||
<{{.Name | title}}Section />
|
||||
{{end}}
|
||||
|
||||
{{range .Page.Components}}
|
||||
<{{.Type | title}}Component />
|
||||
{{end}}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
113
examples/react-app-generator/templates/react-component.tmpl
Normal file
113
examples/react-app-generator/templates/react-component.tmpl
Normal file
@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
{{if .Component.Entity}}import { {{.Component.Entity}} } from '../types/{{.Component.Entity}}';
|
||||
{{end}}
|
||||
|
||||
interface {{.Component.Type | title}}ComponentProps {
|
||||
className?: string;
|
||||
{{if .Component.Entity}}data?: {{.Component.Entity}}[];
|
||||
{{end}}
|
||||
}
|
||||
|
||||
export default function {{.Component.Type | title}}Component({
|
||||
className = '',
|
||||
{{if .Component.Entity}}data{{end}}
|
||||
}: {{.Component.Type | title}}ComponentProps) {
|
||||
{{if eq .Component.Type "form"}}
|
||||
const [formData, setFormData] = React.useState({});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
console.log('Form submitted:', formData);
|
||||
// TODO: Implement form submission
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`bg-white p-6 rounded-lg shadow ${className}`}>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{{range .Component.Elements}}{{if .Field}}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{.Field.Name | title}}
|
||||
</label>
|
||||
<input
|
||||
type="{{if eq .Field.Type "text"}}text{{else if eq .Field.Type "email"}}email{{else}}text{{end}}"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
onChange={(e) => setFormData({...formData, {{.Field.Name}}: e.target.value})}
|
||||
/>
|
||||
</div>
|
||||
{{end}}{{end}}
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
{{else if eq .Component.Type "table"}}
|
||||
return (
|
||||
<div className={`bg-white rounded-lg shadow overflow-hidden ${className}`}>
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
{{range .Component.Elements}}{{if .Field}}
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
{{.Field.Name | title}}
|
||||
</th>
|
||||
{{end}}{{end}}
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{data?.map((item, index) => (
|
||||
<tr key={index}>
|
||||
{{range .Component.Elements}}{{if .Field}}
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{item.{{.Field.Name}}}
|
||||
</td>
|
||||
{{end}}{{end}}
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<button className="text-blue-600 hover:text-blue-900 mr-2">
|
||||
Edit
|
||||
</button>
|
||||
<button className="text-red-600 hover:text-red-900">
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
{{else if eq .Component.Type "list"}}
|
||||
return (
|
||||
<div className={`space-y-4 ${className}`}>
|
||||
{data?.map((item, index) => (
|
||||
<div key={index} className="bg-white p-6 rounded-lg shadow">
|
||||
{{range .Component.Elements}}{{if .Field}}
|
||||
<div className="mb-2">
|
||||
<span className="font-semibold text-gray-700">{{.Field.Name | title}}:</span>
|
||||
<span className="ml-2 text-gray-900">{item.{{.Field.Name}}}</span>
|
||||
</div>
|
||||
{{end}}{{end}}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
{{else}}
|
||||
return (
|
||||
<div className={`bg-white p-6 rounded-lg shadow ${className}`}>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
{{.Component.Type | title}} Component
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
This is a {{.Component.Type}} component. Add your custom implementation here.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
{{end}}
|
||||
}
|
19
examples/react-app-generator/templates/react-router.tmpl
Normal file
19
examples/react-app-generator/templates/react-router.tmpl
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
{{range .AST.Definitions}}{{if .Page}}import {{.Page.Name}}Page from './pages/{{if eq .Page.Layout "admin"}}admin/{{end}}{{.Page.Name}}';
|
||||
{{end}}{{end}}
|
||||
|
||||
export default function AppRouter() {
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
{{range .AST.Definitions}}{{if .Page}}
|
||||
<Route
|
||||
path="{{.Page.Path}}"
|
||||
element={<{{.Page.Name}}Page />}
|
||||
/>
|
||||
{{end}}{{end}}
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
{{if .Section.Entity}}import { {{.Section.Entity}} } from '../../types/{{.Section.Entity}}';
|
||||
{{end}}{{range .Section.Elements}}{{if .Section}}import {{.Section.Name | title}}Section from '../sections/{{.Section.Name | title}}Section';{{end}}{{if .Component}}import {{.Component.Type | title}}Component from '../{{.Component.Type | title}}Component';{{end}}{{end}}
|
||||
|
||||
interface {{.Section.Name | title}}SectionProps {
|
||||
className?: string;
|
||||
{{if .Section.Entity}}data?: {{.Section.Entity}}[];
|
||||
{{end}}
|
||||
}
|
||||
|
||||
export default function {{.Section.Name | title}}Section({
|
||||
className = ''
|
||||
{{if .Section.Entity}}, data{{end}}
|
||||
}: {{.Section.Name | title}}SectionProps) {
|
||||
return (
|
||||
<section className={`{{if .Section.Type}}{{.Section.Type}}{{else}}container{{end}} ${className}`}>
|
||||
{{if .Section.Label}}<h2 className="text-2xl font-bold mb-4">{{.Section.Label | derefString}}</h2>
|
||||
{{end}}
|
||||
|
||||
<div className="space-y-6">
|
||||
{{range .Section.Elements}}
|
||||
{{if .Component}}
|
||||
<{{.Component.Type | title}}Component />
|
||||
{{end}}
|
||||
{{if .Section}}
|
||||
<{{.Section.Name | title}}Section />
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
27
examples/react-app-generator/templates/tsconfig.tmpl
Normal file
27
examples/react-app-generator/templates/tsconfig.tmpl
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"es6"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
export interface {{.Entity.Name}} {
|
||||
{{range .Entity.Fields}} {{.Name}}: {{if eq .Type "string"}}string{{else if eq .Type "int"}}number{{else if eq .Type "boolean"}}boolean{{else if eq .Type "timestamp"}}Date{{else if eq .Type "text"}}string{{else}}any{{end}}{{if .Required}} // Required{{end}}{{if .Default}} // Default: {{.Default | derefString}}{{end}};
|
||||
{{end}}}
|
||||
|
||||
{{if .Entity.Fields}}
|
||||
// Additional validation and utility types can be added here
|
||||
{{end}}
|
Reference in New Issue
Block a user