mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 21:54:56 +00:00
Generated by Spark: Work on remaining roadmap
This commit is contained in:
@@ -11,6 +11,7 @@ A comprehensive visual low-code platform for generating production-ready Next.js
|
||||
## ✨ Features
|
||||
|
||||
### 🎯 Core Capabilities
|
||||
- **Project Dashboard** - At-a-glance overview of project status, completion metrics, and quick tips
|
||||
- **Monaco Code Editor** - Full-featured IDE with syntax highlighting, autocomplete, and multi-file editing
|
||||
- **Prisma Schema Designer** - Visual database model builder with relations and field configuration
|
||||
- **Component Tree Builder** - Hierarchical React component designer with Material UI integration
|
||||
@@ -18,6 +19,7 @@ A comprehensive visual low-code platform for generating production-ready Next.js
|
||||
- **Sass Styling System** - Custom Material UI components with Sass, including utilities, mixins, and animations
|
||||
- **Flask Backend Designer** - Python REST API designer with blueprints, endpoints, and CORS configuration
|
||||
- **Project Settings** - Configure Next.js options, npm packages, scripts, and build settings
|
||||
- **Keyboard Shortcuts** - Power-user shortcuts for rapid navigation and actions
|
||||
|
||||
### 🤖 AI-Powered Generation
|
||||
- **Complete App Generation** - Describe your app and get a full project structure
|
||||
@@ -89,7 +91,8 @@ Access documentation by clicking the **Documentation** tab in the application.
|
||||
|
||||
## 🗺️ Roadmap
|
||||
|
||||
### ✅ Completed (v1.0 - v4.0)
|
||||
### ✅ Completed (v1.0 - v4.1)
|
||||
- Project dashboard with completion metrics
|
||||
- Monaco code editor integration
|
||||
- Visual designers for models, components, and themes
|
||||
- AI-powered generation across all features
|
||||
@@ -99,6 +102,8 @@ Access documentation by clicking the **Documentation** tab in the application.
|
||||
- Flask backend designer
|
||||
- Project settings and npm management
|
||||
- Custom Sass styling system with utilities and mixins
|
||||
- ZIP file export with README generation
|
||||
- Keyboard shortcuts for power users
|
||||
|
||||
### 🔮 Planned
|
||||
- Real-time preview with hot reload
|
||||
|
||||
21
ROADMAP.md
21
ROADMAP.md
@@ -96,9 +96,24 @@ Backend and configuration tools:
|
||||
- ✅ Package manager selection (npm/yarn/pnpm)
|
||||
- ✅ Complete project settings control
|
||||
|
||||
### v4.1 - Enhanced Export & Documentation (Completed)
|
||||
**Release Date:** Week 9
|
||||
|
||||
Improved export and comprehensive documentation:
|
||||
- ✅ ZIP file download for complete project export
|
||||
- ✅ Auto-generated README in exported projects
|
||||
- ✅ Copy all files to clipboard functionality
|
||||
- ✅ Comprehensive in-app documentation system
|
||||
- ✅ Sass styles guide with live examples
|
||||
- ✅ Complete roadmap documentation
|
||||
- ✅ AI agents architecture documentation
|
||||
- ✅ Project dashboard with completion metrics
|
||||
- ✅ Keyboard shortcuts for power users
|
||||
- ✅ Search functionality in documentation
|
||||
|
||||
## Upcoming Releases
|
||||
|
||||
### v4.1 - Real-Time Preview (In Planning)
|
||||
### v4.2 - Real-Time Preview (In Planning)
|
||||
**Estimated:** Q2 2024
|
||||
|
||||
Live application preview:
|
||||
@@ -115,7 +130,7 @@ Live application preview:
|
||||
- State preservation across reloads
|
||||
- Error boundary implementation
|
||||
|
||||
### v4.2 - Data Management (In Planning)
|
||||
### v4.3 - Data Management (In Planning)
|
||||
**Estimated:** Q2 2024
|
||||
|
||||
Database and API integration:
|
||||
@@ -132,7 +147,7 @@ Database and API integration:
|
||||
- TypeScript API client with fetch/axios
|
||||
- Automatic type inference from endpoints
|
||||
|
||||
### v4.3 - Form Builder (In Planning)
|
||||
### v4.4 - Form Builder (In Planning)
|
||||
**Estimated:** Q2-Q3 2024
|
||||
|
||||
Visual form design:
|
||||
|
||||
100
package-lock.json
generated
100
package-lock.json
generated
@@ -52,6 +52,7 @@
|
||||
"embla-carousel-react": "^8.5.2",
|
||||
"framer-motion": "^12.6.2",
|
||||
"input-otp": "^1.4.2",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.484.0",
|
||||
"marked": "^15.0.7",
|
||||
"next-themes": "^0.4.6",
|
||||
@@ -5692,6 +5693,12 @@
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -7550,6 +7557,12 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
|
||||
@@ -7732,6 +7745,12 @@
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
@@ -7829,6 +7848,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jszip": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
||||
"license": "(MIT OR GPL-3.0-or-later)",
|
||||
"dependencies": {
|
||||
"lie": "~3.3.0",
|
||||
"pako": "~1.0.2",
|
||||
"readable-stream": "~2.3.6",
|
||||
"setimmediate": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -7853,6 +7884,15 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
||||
@@ -8543,6 +8583,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@@ -8752,6 +8798,12 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@@ -9058,6 +9110,27 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
@@ -9447,6 +9520,12 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
@@ -9644,6 +9723,21 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/strip-indent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||
@@ -10153,6 +10247,12 @@
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"embla-carousel-react": "^8.5.2",
|
||||
"framer-motion": "^12.6.2",
|
||||
"input-otp": "^1.4.2",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.484.0",
|
||||
"marked": "^15.0.7",
|
||||
"next-themes": "^0.4.6",
|
||||
|
||||
179
src/App.tsx
179
src/App.tsx
@@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
|
||||
import { Code, Database, Tree, PaintBrush, Download, Sparkle, Flask, BookOpen, Play, Wrench, Gear, Cube, FileText } from '@phosphor-icons/react'
|
||||
import { Code, Database, Tree, PaintBrush, Download, Sparkle, Flask, BookOpen, Play, Wrench, Gear, Cube, FileText, ChartBar, Keyboard } from '@phosphor-icons/react'
|
||||
import { ProjectFile, PrismaModel, ComponentNode, ThemeConfig, PlaywrightTest, StorybookStory, UnitTest, FlaskConfig, NextJsConfig, NpmSettings } from '@/types/project'
|
||||
import { CodeEditor } from '@/components/CodeEditor'
|
||||
import { ModelDesigner } from '@/components/ModelDesigner'
|
||||
@@ -21,9 +21,13 @@ import { ProjectSettingsDesigner } from '@/components/ProjectSettingsDesigner'
|
||||
import { ErrorPanel } from '@/components/ErrorPanel'
|
||||
import { DocumentationView } from '@/components/DocumentationView'
|
||||
import { SassStylesShowcase } from '@/components/SassStylesShowcase'
|
||||
import { ProjectDashboard } from '@/components/ProjectDashboard'
|
||||
import { KeyboardShortcutsDialog } from '@/components/KeyboardShortcutsDialog'
|
||||
import { useKeyboardShortcuts } from '@/hooks/use-keyboard-shortcuts'
|
||||
import { generateNextJSProject, generatePrismaSchema, generateMUITheme, generatePlaywrightTests, generateStorybookStories, generateUnitTests, generateFlaskApp } from '@/lib/generators'
|
||||
import { AIService } from '@/lib/ai-service'
|
||||
import { toast } from 'sonner'
|
||||
import JSZip from 'jszip'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -144,8 +148,9 @@ function App() {
|
||||
const [nextjsConfig, setNextjsConfig] = useKV<NextJsConfig>('project-nextjs-config', DEFAULT_NEXTJS_CONFIG)
|
||||
const [npmSettings, setNpmSettings] = useKV<NpmSettings>('project-npm-settings', DEFAULT_NPM_SETTINGS)
|
||||
const [activeFileId, setActiveFileId] = useState<string | null>((files || [])[0]?.id || null)
|
||||
const [activeTab, setActiveTab] = useState('code')
|
||||
const [activeTab, setActiveTab] = useState('dashboard')
|
||||
const [exportDialogOpen, setExportDialogOpen] = useState(false)
|
||||
const [shortcutsDialogOpen, setShortcutsDialogOpen] = useState(false)
|
||||
const [generatedCode, setGeneratedCode] = useState<Record<string, string>>({})
|
||||
|
||||
const safeFiles = files || []
|
||||
@@ -161,6 +166,57 @@ function App() {
|
||||
|
||||
const { errors: autoDetectedErrors } = useAutoRepair(safeFiles, false)
|
||||
|
||||
useKeyboardShortcuts([
|
||||
{
|
||||
key: '1',
|
||||
ctrl: true,
|
||||
description: 'Go to Dashboard',
|
||||
action: () => setActiveTab('dashboard'),
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
ctrl: true,
|
||||
description: 'Go to Code Editor',
|
||||
action: () => setActiveTab('code'),
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
ctrl: true,
|
||||
description: 'Go to Models',
|
||||
action: () => setActiveTab('models'),
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
ctrl: true,
|
||||
description: 'Go to Components',
|
||||
action: () => setActiveTab('components'),
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
ctrl: true,
|
||||
description: 'Go to Styling',
|
||||
action: () => setActiveTab('styling'),
|
||||
},
|
||||
{
|
||||
key: 'e',
|
||||
ctrl: true,
|
||||
description: 'Export Project',
|
||||
action: () => handleExportProject(),
|
||||
},
|
||||
{
|
||||
key: 'k',
|
||||
ctrl: true,
|
||||
description: 'AI Generate',
|
||||
action: () => handleGenerateWithAI(),
|
||||
},
|
||||
{
|
||||
key: '/',
|
||||
ctrl: true,
|
||||
description: 'Show Keyboard Shortcuts',
|
||||
action: () => setShortcutsDialogOpen(true),
|
||||
},
|
||||
])
|
||||
|
||||
const handleFileChange = (fileId: string, content: string) => {
|
||||
setFiles((currentFiles) =>
|
||||
(currentFiles || []).map((f) => (f.id === fileId ? { ...f, content } : f))
|
||||
@@ -232,6 +288,75 @@ function App() {
|
||||
toast.success('Project files generated!')
|
||||
}
|
||||
|
||||
const handleDownloadZip = async () => {
|
||||
try {
|
||||
toast.info('Creating ZIP file...')
|
||||
|
||||
const zip = new JSZip()
|
||||
|
||||
Object.entries(generatedCode).forEach(([path, content]) => {
|
||||
const cleanPath = path.startsWith('/') ? path.slice(1) : path
|
||||
zip.file(cleanPath, content)
|
||||
})
|
||||
|
||||
zip.file('README.md', `# ${safeNextjsConfig.appName}
|
||||
|
||||
Generated with CodeForge
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Install dependencies:
|
||||
\`\`\`bash
|
||||
npm install
|
||||
\`\`\`
|
||||
|
||||
2. Set up Prisma (if using database):
|
||||
\`\`\`bash
|
||||
npx prisma generate
|
||||
npx prisma db push
|
||||
\`\`\`
|
||||
|
||||
3. Run the development server:
|
||||
\`\`\`bash
|
||||
npm run dev
|
||||
\`\`\`
|
||||
|
||||
4. Open [http://localhost:3000](http://localhost:3000) in your browser.
|
||||
|
||||
## Testing
|
||||
|
||||
Run E2E tests:
|
||||
\`\`\`bash
|
||||
npm run test:e2e
|
||||
\`\`\`
|
||||
|
||||
Run unit tests:
|
||||
\`\`\`bash
|
||||
npm run test
|
||||
\`\`\`
|
||||
|
||||
## Flask Backend (Optional)
|
||||
|
||||
Navigate to the backend directory and follow the setup instructions.
|
||||
`)
|
||||
|
||||
const blob = await zip.generateAsync({ type: 'blob' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `${safeNextjsConfig.appName}.zip`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
toast.success('Project downloaded successfully!')
|
||||
} catch (error) {
|
||||
console.error('Failed to create ZIP:', error)
|
||||
toast.error('Failed to create ZIP file')
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenerateWithAI = async () => {
|
||||
const description = prompt('Describe the application you want to generate:')
|
||||
if (!description) return
|
||||
@@ -287,6 +412,14 @@ function App() {
|
||||
{autoDetectedErrors.length} {autoDetectedErrors.length === 1 ? 'Error' : 'Errors'}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setShortcutsDialogOpen(true)}
|
||||
title="Keyboard Shortcuts (Ctrl+/)"
|
||||
>
|
||||
<Keyboard size={20} />
|
||||
</Button>
|
||||
<Button variant="outline" onClick={handleGenerateWithAI}>
|
||||
<Sparkle size={16} className="mr-2" weight="duotone" />
|
||||
AI Generate
|
||||
@@ -302,6 +435,10 @@ function App() {
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col">
|
||||
<div className="border-b border-border bg-card px-6">
|
||||
<TabsList className="h-auto bg-transparent flex-wrap py-2">
|
||||
<TabsTrigger value="dashboard" className="gap-2">
|
||||
<ChartBar size={18} />
|
||||
Dashboard
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="code" className="gap-2">
|
||||
<Code size={18} />
|
||||
Code Editor
|
||||
@@ -359,6 +496,19 @@ function App() {
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<TabsContent value="dashboard" className="h-full m-0">
|
||||
<ProjectDashboard
|
||||
files={safeFiles}
|
||||
models={safeModels}
|
||||
components={safeComponents}
|
||||
theme={safeTheme}
|
||||
playwrightTests={safePlaywrightTests}
|
||||
storybookStories={safeStorybookStories}
|
||||
unitTests={safeUnitTests}
|
||||
flaskConfig={safeFlaskConfig}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="code" className="h-full m-0">
|
||||
<ResizablePanelGroup direction="horizontal">
|
||||
<ResizablePanel defaultSize={20} minSize={15} maxSize={30}>
|
||||
@@ -445,9 +595,27 @@ function App() {
|
||||
<DialogHeader>
|
||||
<DialogTitle>Generated Project Files</DialogTitle>
|
||||
<DialogDescription>
|
||||
Copy these files to create your Next.js application
|
||||
Download as ZIP or copy individual files to create your Next.js application
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex gap-2 mb-4">
|
||||
<Button onClick={handleDownloadZip} className="flex-1">
|
||||
<Download size={16} className="mr-2" />
|
||||
Download as ZIP
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const allCode = Object.entries(generatedCode)
|
||||
.map(([path, content]) => `// ${path}\n${content}`)
|
||||
.join('\n\n---\n\n')
|
||||
navigator.clipboard.writeText(allCode)
|
||||
toast.success('All files copied to clipboard!')
|
||||
}}
|
||||
>
|
||||
Copy All
|
||||
</Button>
|
||||
</div>
|
||||
<ScrollArea className="h-96">
|
||||
<div className="space-y-4">
|
||||
{Object.entries(generatedCode).map(([path, content]) => (
|
||||
@@ -478,6 +646,11 @@ function App() {
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<KeyboardShortcutsDialog
|
||||
open={shortcutsDialogOpen}
|
||||
onOpenChange={setShortcutsDialogOpen}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
BookOpen,
|
||||
MapPin,
|
||||
@@ -23,34 +24,47 @@ import {
|
||||
Package,
|
||||
Rocket,
|
||||
Target,
|
||||
Lightbulb
|
||||
Lightbulb,
|
||||
MagnifyingGlass
|
||||
} from '@phosphor-icons/react'
|
||||
|
||||
export function DocumentationView() {
|
||||
const [activeTab, setActiveTab] = useState('readme')
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col bg-background">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col">
|
||||
<div className="border-b border-border bg-card px-6 py-3">
|
||||
<TabsList className="bg-muted/50">
|
||||
<TabsTrigger value="readme" className="gap-2">
|
||||
<BookOpen size={18} />
|
||||
README
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="roadmap" className="gap-2">
|
||||
<MapPin size={18} />
|
||||
Roadmap
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="agents" className="gap-2">
|
||||
<FileCode size={18} />
|
||||
Agents Files
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="sass" className="gap-2">
|
||||
<PaintBrush size={18} />
|
||||
Sass Styles Guide
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="border-b border-border bg-card px-6 py-3 space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<TabsList className="bg-muted/50">
|
||||
<TabsTrigger value="readme" className="gap-2">
|
||||
<BookOpen size={18} />
|
||||
README
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="roadmap" className="gap-2">
|
||||
<MapPin size={18} />
|
||||
Roadmap
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="agents" className="gap-2">
|
||||
<FileCode size={18} />
|
||||
Agents Files
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="sass" className="gap-2">
|
||||
<PaintBrush size={18} />
|
||||
Sass Styles Guide
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<MagnifyingGlass size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search documentation..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-1">
|
||||
|
||||
130
src/components/KeyboardShortcutsDialog.tsx
Normal file
130
src/components/KeyboardShortcutsDialog.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Keyboard } from '@phosphor-icons/react'
|
||||
|
||||
interface KeyboardShortcutsDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
|
||||
export function KeyboardShortcutsDialog({ open, onOpenChange }: KeyboardShortcutsDialogProps) {
|
||||
const isMac = navigator.platform.includes('Mac')
|
||||
const ctrlKey = isMac ? '⌘' : 'Ctrl'
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Keyboard size={24} weight="duotone" />
|
||||
Keyboard Shortcuts
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Speed up your workflow with these shortcuts
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Navigation</h3>
|
||||
<div className="space-y-2">
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '1']}
|
||||
description="Go to Dashboard"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '2']}
|
||||
description="Go to Code Editor"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '3']}
|
||||
description="Go to Models"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '4']}
|
||||
description="Go to Components"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '5']}
|
||||
description="Go to Styling"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Actions</h3>
|
||||
<div className="space-y-2">
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'E']}
|
||||
description="Export Project"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'K']}
|
||||
description="AI Generate"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'S']}
|
||||
description="Save (Auto-save enabled)"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, '/']}
|
||||
description="Show Keyboard Shortcuts"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Code Editor</h3>
|
||||
<div className="space-y-2">
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'F']}
|
||||
description="Find in file"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'H']}
|
||||
description="Find and replace"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'Z']}
|
||||
description="Undo"
|
||||
/>
|
||||
<ShortcutRow
|
||||
keys={[ctrlKey, 'Shift', 'Z']}
|
||||
description="Redo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
function ShortcutRow({ keys, description }: { keys: string[]; description: string }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<span className="text-sm text-muted-foreground">{description}</span>
|
||||
<div className="flex gap-1">
|
||||
{keys.map((key, index) => (
|
||||
<kbd
|
||||
key={index}
|
||||
className="px-2 py-1 text-xs font-semibold bg-muted border border-border rounded"
|
||||
>
|
||||
{key}
|
||||
</kbd>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
273
src/components/ProjectDashboard.tsx
Normal file
273
src/components/ProjectDashboard.tsx
Normal file
@@ -0,0 +1,273 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import {
|
||||
Code,
|
||||
Database,
|
||||
Tree,
|
||||
PaintBrush,
|
||||
Flask,
|
||||
Play,
|
||||
Cube,
|
||||
FileText,
|
||||
CheckCircle,
|
||||
Warning
|
||||
} from '@phosphor-icons/react'
|
||||
import { ProjectFile, PrismaModel, ComponentNode, ThemeConfig, PlaywrightTest, StorybookStory, UnitTest, FlaskConfig } from '@/types/project'
|
||||
|
||||
interface ProjectDashboardProps {
|
||||
files: ProjectFile[]
|
||||
models: PrismaModel[]
|
||||
components: ComponentNode[]
|
||||
theme: ThemeConfig
|
||||
playwrightTests: PlaywrightTest[]
|
||||
storybookStories: StorybookStory[]
|
||||
unitTests: UnitTest[]
|
||||
flaskConfig: FlaskConfig
|
||||
}
|
||||
|
||||
export function ProjectDashboard({
|
||||
files,
|
||||
models,
|
||||
components,
|
||||
theme,
|
||||
playwrightTests,
|
||||
storybookStories,
|
||||
unitTests,
|
||||
flaskConfig,
|
||||
}: ProjectDashboardProps) {
|
||||
const totalFiles = files.length
|
||||
const totalModels = models.length
|
||||
const totalComponents = components.length
|
||||
const totalThemeVariants = theme.variants.length
|
||||
const totalEndpoints = flaskConfig.blueprints.reduce((acc, bp) => acc + bp.endpoints.length, 0)
|
||||
const totalTests = playwrightTests.length + storybookStories.length + unitTests.length
|
||||
|
||||
const completionScore = calculateCompletionScore({
|
||||
files: totalFiles,
|
||||
models: totalModels,
|
||||
components: totalComponents,
|
||||
tests: totalTests,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-auto p-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold mb-2">Project Dashboard</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Overview of your CodeForge project
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="bg-gradient-to-br from-primary/10 to-accent/10 border-primary/20">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CheckCircle size={24} weight="duotone" className="text-primary" />
|
||||
Project Completeness
|
||||
</CardTitle>
|
||||
<CardDescription>Overall progress of your application</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-4xl font-bold">{completionScore}%</span>
|
||||
<Badge variant={completionScore >= 70 ? 'default' : 'secondary'} className="text-sm">
|
||||
{completionScore >= 70 ? 'Ready to Export' : 'In Progress'}
|
||||
</Badge>
|
||||
</div>
|
||||
<Progress value={completionScore} className="h-3" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{getCompletionMessage(completionScore)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<StatCard
|
||||
icon={<Code size={24} weight="duotone" />}
|
||||
title="Code Files"
|
||||
value={totalFiles}
|
||||
description={`${totalFiles} file${totalFiles !== 1 ? 's' : ''} in your project`}
|
||||
color="text-blue-500"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
icon={<Database size={24} weight="duotone" />}
|
||||
title="Database Models"
|
||||
value={totalModels}
|
||||
description={`${totalModels} Prisma model${totalModels !== 1 ? 's' : ''} defined`}
|
||||
color="text-purple-500"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
icon={<Tree size={24} weight="duotone" />}
|
||||
title="Components"
|
||||
value={totalComponents}
|
||||
description={`${totalComponents} component${totalComponents !== 1 ? 's' : ''} in tree`}
|
||||
color="text-green-500"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
icon={<PaintBrush size={24} weight="duotone" />}
|
||||
title="Theme Variants"
|
||||
value={totalThemeVariants}
|
||||
description={`${totalThemeVariants} theme${totalThemeVariants !== 1 ? 's' : ''} configured`}
|
||||
color="text-pink-500"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
icon={<Flask size={24} weight="duotone" />}
|
||||
title="API Endpoints"
|
||||
value={totalEndpoints}
|
||||
description={`${totalEndpoints} Flask endpoint${totalEndpoints !== 1 ? 's' : ''}`}
|
||||
color="text-orange-500"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
icon={<Cube size={24} weight="duotone" />}
|
||||
title="Tests"
|
||||
value={totalTests}
|
||||
description={`${totalTests} test${totalTests !== 1 ? 's' : ''} written`}
|
||||
color="text-cyan-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Project Details</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<DetailRow
|
||||
icon={<Play size={18} />}
|
||||
label="Playwright Tests"
|
||||
value={playwrightTests.length}
|
||||
/>
|
||||
<DetailRow
|
||||
icon={<FileText size={18} />}
|
||||
label="Storybook Stories"
|
||||
value={storybookStories.length}
|
||||
/>
|
||||
<DetailRow
|
||||
icon={<Cube size={18} />}
|
||||
label="Unit Tests"
|
||||
value={unitTests.length}
|
||||
/>
|
||||
<DetailRow
|
||||
icon={<Flask size={18} />}
|
||||
label="Flask Blueprints"
|
||||
value={flaskConfig.blueprints.length}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{(totalModels === 0 || totalFiles === 0) && (
|
||||
<Card className="bg-yellow-500/10 border-yellow-500/20">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Warning size={24} weight="duotone" className="text-yellow-500" />
|
||||
Quick Tips
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 text-sm">
|
||||
{totalFiles === 0 && (
|
||||
<p>• Start by creating some code files in the Code Editor tab</p>
|
||||
)}
|
||||
{totalModels === 0 && (
|
||||
<p>• Define your data models in the Models tab to set up your database</p>
|
||||
)}
|
||||
{totalComponents === 0 && (
|
||||
<p>• Build your UI structure in the Components tab</p>
|
||||
)}
|
||||
{totalThemeVariants <= 1 && (
|
||||
<p>• Create additional theme variants (dark mode) in the Styling tab</p>
|
||||
)}
|
||||
{totalTests === 0 && (
|
||||
<p>• Add tests for better code quality and reliability</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StatCard({
|
||||
icon,
|
||||
title,
|
||||
value,
|
||||
description,
|
||||
color
|
||||
}: {
|
||||
icon: React.ReactNode
|
||||
title: string
|
||||
value: number
|
||||
description: string
|
||||
color: string
|
||||
}) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
||||
<p className="text-3xl font-bold">{value}</p>
|
||||
<p className="text-xs text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
<div className={color}>{icon}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function DetailRow({
|
||||
icon,
|
||||
label,
|
||||
value
|
||||
}: {
|
||||
icon: React.ReactNode
|
||||
label: string
|
||||
value: number
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between py-2 border-b border-border last:border-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground">{icon}</span>
|
||||
<span className="text-sm font-medium">{label}</span>
|
||||
</div>
|
||||
<Badge variant="secondary">{value}</Badge>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function calculateCompletionScore(data: {
|
||||
files: number
|
||||
models: number
|
||||
components: number
|
||||
tests: number
|
||||
}): number {
|
||||
const weights = {
|
||||
files: 25,
|
||||
models: 25,
|
||||
components: 25,
|
||||
tests: 25,
|
||||
}
|
||||
|
||||
const scores = {
|
||||
files: Math.min(data.files / 5, 1) * weights.files,
|
||||
models: Math.min(data.models / 3, 1) * weights.models,
|
||||
components: Math.min(data.components / 5, 1) * weights.components,
|
||||
tests: Math.min(data.tests / 5, 1) * weights.tests,
|
||||
}
|
||||
|
||||
return Math.round(
|
||||
scores.files + scores.models + scores.components + scores.tests
|
||||
)
|
||||
}
|
||||
|
||||
function getCompletionMessage(score: number): string {
|
||||
if (score >= 90) return 'Excellent! Your project is comprehensive and ready to deploy.'
|
||||
if (score >= 70) return 'Great progress! Your project has most essential features.'
|
||||
if (score >= 50) return 'Good start! Keep adding more features and tests.'
|
||||
if (score >= 30) return 'Getting there! Add more components and models.'
|
||||
return 'Just starting! Begin by creating models and components.'
|
||||
}
|
||||
49
src/hooks/use-keyboard-shortcuts.ts
Normal file
49
src/hooks/use-keyboard-shortcuts.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
interface KeyboardShortcut {
|
||||
key: string
|
||||
ctrl?: boolean
|
||||
shift?: boolean
|
||||
alt?: boolean
|
||||
action: () => void
|
||||
description: string
|
||||
}
|
||||
|
||||
export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]) {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
for (const shortcut of shortcuts) {
|
||||
const ctrlMatch = shortcut.ctrl ? (event.ctrlKey || event.metaKey) : !event.ctrlKey && !event.metaKey
|
||||
const shiftMatch = shortcut.shift ? event.shiftKey : !event.shiftKey
|
||||
const altMatch = shortcut.alt ? event.altKey : !event.altKey
|
||||
const keyMatch = event.key.toLowerCase() === shortcut.key.toLowerCase()
|
||||
|
||||
if (ctrlMatch && shiftMatch && altMatch && keyMatch) {
|
||||
event.preventDefault()
|
||||
shortcut.action()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [shortcuts])
|
||||
}
|
||||
|
||||
export function getShortcutDisplay(shortcut: Omit<KeyboardShortcut, 'action'>): string {
|
||||
const parts: string[] = []
|
||||
|
||||
if (shortcut.ctrl) {
|
||||
parts.push(navigator.platform.includes('Mac') ? '⌘' : 'Ctrl')
|
||||
}
|
||||
if (shortcut.shift) {
|
||||
parts.push('Shift')
|
||||
}
|
||||
if (shortcut.alt) {
|
||||
parts.push('Alt')
|
||||
}
|
||||
parts.push(shortcut.key.toUpperCase())
|
||||
|
||||
return parts.join(' + ')
|
||||
}
|
||||
Reference in New Issue
Block a user