mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-29 08:04:56 +00:00
Generated by Spark: Too risky making changes without refactoring now. Create hook library, All components <150LOC.
This commit is contained in:
15
src/hooks/index.ts
Normal file
15
src/hooks/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export { useProjectState } from './use-project-state'
|
||||
export { useFileOperations } from './use-file-operations'
|
||||
export { useAIOperations } from './use-ai-operations'
|
||||
export { useProjectExport } from './use-project-export'
|
||||
export { useDialogState, useMultipleDialogs } from './use-dialog-state'
|
||||
export { useActiveSelection } from './use-active-selection'
|
||||
export { useLastSaved } from './use-last-saved'
|
||||
export { useTabNavigation } from './use-tab-navigation'
|
||||
export { useCodeExplanation } from './use-code-explanation'
|
||||
export { useFileFilters } from './use-file-filters'
|
||||
export { useProjectLoader } from './use-project-loader'
|
||||
export { useAutoRepair } from './use-auto-repair'
|
||||
export { useKeyboardShortcuts } from './use-keyboard-shortcuts'
|
||||
export { useIsMobile } from './use-mobile'
|
||||
export { usePWA } from './use-pwa'
|
||||
49
src/hooks/use-active-selection.ts
Normal file
49
src/hooks/use-active-selection.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export function useActiveSelection<T extends { id: string }>(items: T[], defaultId?: string | null) {
|
||||
const [activeId, setActiveId] = useState<string | null>(defaultId || null)
|
||||
|
||||
useEffect(() => {
|
||||
if (items.length > 0 && !activeId) {
|
||||
setActiveId(items[0].id)
|
||||
}
|
||||
}, [items, activeId])
|
||||
|
||||
const activeItem = items.find(item => item.id === activeId)
|
||||
|
||||
const selectNext = () => {
|
||||
if (!activeId || items.length === 0) return
|
||||
const currentIndex = items.findIndex(item => item.id === activeId)
|
||||
const nextIndex = (currentIndex + 1) % items.length
|
||||
setActiveId(items[nextIndex].id)
|
||||
}
|
||||
|
||||
const selectPrevious = () => {
|
||||
if (!activeId || items.length === 0) return
|
||||
const currentIndex = items.findIndex(item => item.id === activeId)
|
||||
const previousIndex = currentIndex === 0 ? items.length - 1 : currentIndex - 1
|
||||
setActiveId(items[previousIndex].id)
|
||||
}
|
||||
|
||||
const selectFirst = () => {
|
||||
if (items.length > 0) {
|
||||
setActiveId(items[0].id)
|
||||
}
|
||||
}
|
||||
|
||||
const selectLast = () => {
|
||||
if (items.length > 0) {
|
||||
setActiveId(items[items.length - 1].id)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
activeId,
|
||||
setActiveId,
|
||||
activeItem,
|
||||
selectNext,
|
||||
selectPrevious,
|
||||
selectFirst,
|
||||
selectLast,
|
||||
}
|
||||
}
|
||||
73
src/hooks/use-ai-operations.ts
Normal file
73
src/hooks/use-ai-operations.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { useState } from 'react'
|
||||
import { AIService } from '@/lib/ai-service'
|
||||
import { toast } from 'sonner'
|
||||
import { ProjectFile, PrismaModel, ThemeConfig } from '@/types/project'
|
||||
|
||||
export function useAIOperations() {
|
||||
const [isProcessing, setIsProcessing] = useState(false)
|
||||
|
||||
const improveCode = async (content: string, instruction: string) => {
|
||||
try {
|
||||
setIsProcessing(true)
|
||||
toast.info('Improving code with AI...')
|
||||
const improvedCode = await AIService.improveCode(content, instruction)
|
||||
|
||||
if (improvedCode) {
|
||||
toast.success('Code improved successfully!')
|
||||
return improvedCode
|
||||
} else {
|
||||
toast.error('AI improvement failed. Please try again.')
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Failed to improve code')
|
||||
console.error(error)
|
||||
return null
|
||||
} finally {
|
||||
setIsProcessing(false)
|
||||
}
|
||||
}
|
||||
|
||||
const explainCode = async (content: string) => {
|
||||
try {
|
||||
setIsProcessing(true)
|
||||
const codeExplanation = await AIService.explainCode(content)
|
||||
return codeExplanation || 'Failed to generate explanation. Please try again.'
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return 'Error generating explanation.'
|
||||
} finally {
|
||||
setIsProcessing(false)
|
||||
}
|
||||
}
|
||||
|
||||
const generateCompleteApp = async (description: string) => {
|
||||
try {
|
||||
setIsProcessing(true)
|
||||
toast.info('Generating application with AI...')
|
||||
|
||||
const result = await AIService.generateCompleteApp(description)
|
||||
|
||||
if (result) {
|
||||
toast.success('Application generated successfully!')
|
||||
return result
|
||||
} else {
|
||||
toast.error('AI generation failed. Please try again.')
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('AI generation failed')
|
||||
console.error(error)
|
||||
return null
|
||||
} finally {
|
||||
setIsProcessing(false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isProcessing,
|
||||
improveCode,
|
||||
explainCode,
|
||||
generateCompleteApp,
|
||||
}
|
||||
}
|
||||
31
src/hooks/use-code-explanation.ts
Normal file
31
src/hooks/use-code-explanation.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useState } from 'react'
|
||||
import { useAIOperations } from './use-ai-operations'
|
||||
|
||||
export function useCodeExplanation() {
|
||||
const [explanation, setExplanation] = useState('')
|
||||
const [isExplaining, setIsExplaining] = useState(false)
|
||||
const { explainCode } = useAIOperations()
|
||||
|
||||
const explain = async (code: string) => {
|
||||
try {
|
||||
setIsExplaining(true)
|
||||
setExplanation('Analyzing code...')
|
||||
const result = await explainCode(code)
|
||||
setExplanation(result)
|
||||
} finally {
|
||||
setIsExplaining(false)
|
||||
}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
setExplanation('')
|
||||
setIsExplaining(false)
|
||||
}
|
||||
|
||||
return {
|
||||
explanation,
|
||||
isExplaining,
|
||||
explain,
|
||||
reset,
|
||||
}
|
||||
}
|
||||
43
src/hooks/use-dialog-state.ts
Normal file
43
src/hooks/use-dialog-state.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
export function useDialogState(initialOpen = false) {
|
||||
const [isOpen, setIsOpen] = useState(initialOpen)
|
||||
|
||||
const open = () => setIsOpen(true)
|
||||
const close = () => setIsOpen(false)
|
||||
const toggle = () => setIsOpen(prev => !prev)
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
open,
|
||||
close,
|
||||
toggle,
|
||||
setIsOpen,
|
||||
}
|
||||
}
|
||||
|
||||
export function useMultipleDialogs() {
|
||||
const [dialogs, setDialogs] = useState<Record<string, boolean>>({})
|
||||
|
||||
const openDialog = (name: string) => {
|
||||
setDialogs(prev => ({ ...prev, [name]: true }))
|
||||
}
|
||||
|
||||
const closeDialog = (name: string) => {
|
||||
setDialogs(prev => ({ ...prev, [name]: false }))
|
||||
}
|
||||
|
||||
const toggleDialog = (name: string) => {
|
||||
setDialogs(prev => ({ ...prev, [name]: !prev[name] }))
|
||||
}
|
||||
|
||||
const isDialogOpen = (name: string) => dialogs[name] || false
|
||||
|
||||
return {
|
||||
dialogs,
|
||||
openDialog,
|
||||
closeDialog,
|
||||
toggleDialog,
|
||||
isDialogOpen,
|
||||
}
|
||||
}
|
||||
27
src/hooks/use-file-filters.ts
Normal file
27
src/hooks/use-file-filters.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ProjectFile } from '@/types/project'
|
||||
|
||||
export function useFileFilters(files: ProjectFile[]) {
|
||||
const getOpenFiles = (activeFileId: string | null, maxOpen = 5) => {
|
||||
return files.filter((f) => f.id === activeFileId || files.length < maxOpen)
|
||||
}
|
||||
|
||||
const findFileById = (fileId: string | null) => {
|
||||
if (!fileId) return null
|
||||
return files.find((f) => f.id === fileId) || null
|
||||
}
|
||||
|
||||
const getFilesByLanguage = (language: string) => {
|
||||
return files.filter((f) => f.language === language)
|
||||
}
|
||||
|
||||
const getFilesByPath = (pathPrefix: string) => {
|
||||
return files.filter((f) => f.path.startsWith(pathPrefix))
|
||||
}
|
||||
|
||||
return {
|
||||
getOpenFiles,
|
||||
findFileById,
|
||||
getFilesByLanguage,
|
||||
getFilesByPath,
|
||||
}
|
||||
}
|
||||
44
src/hooks/use-file-operations.ts
Normal file
44
src/hooks/use-file-operations.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useState } from 'react'
|
||||
import { ProjectFile } from '@/types/project'
|
||||
|
||||
export function useFileOperations(
|
||||
files: ProjectFile[],
|
||||
setFiles: (updater: (files: ProjectFile[]) => ProjectFile[]) => void
|
||||
) {
|
||||
const [activeFileId, setActiveFileId] = useState<string | null>(null)
|
||||
|
||||
const handleFileChange = (fileId: string, content: string) => {
|
||||
setFiles((currentFiles) =>
|
||||
currentFiles.map((f) => (f.id === fileId ? { ...f, content } : f))
|
||||
)
|
||||
}
|
||||
|
||||
const handleFileAdd = (file: ProjectFile) => {
|
||||
setFiles((currentFiles) => [...currentFiles, file])
|
||||
setActiveFileId(file.id)
|
||||
}
|
||||
|
||||
const handleFileClose = (fileId: string) => {
|
||||
if (activeFileId === fileId) {
|
||||
const currentIndex = files.findIndex((f) => f.id === fileId)
|
||||
const nextFile = files[currentIndex + 1] || files[currentIndex - 1]
|
||||
setActiveFileId(nextFile?.id || null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileDelete = (fileId: string) => {
|
||||
setFiles((currentFiles) => currentFiles.filter((f) => f.id !== fileId))
|
||||
if (activeFileId === fileId) {
|
||||
setActiveFileId(null)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
activeFileId,
|
||||
setActiveFileId,
|
||||
handleFileChange,
|
||||
handleFileAdd,
|
||||
handleFileClose,
|
||||
handleFileDelete,
|
||||
}
|
||||
}
|
||||
11
src/hooks/use-last-saved.ts
Normal file
11
src/hooks/use-last-saved.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export function useLastSaved(dependencies: any[]) {
|
||||
const [lastSaved, setLastSaved] = useState<number | null>(Date.now())
|
||||
|
||||
useEffect(() => {
|
||||
setLastSaved(Date.now())
|
||||
}, dependencies)
|
||||
|
||||
return lastSaved
|
||||
}
|
||||
169
src/hooks/use-project-export.ts
Normal file
169
src/hooks/use-project-export.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import JSZip from 'jszip'
|
||||
import {
|
||||
ProjectFile,
|
||||
PrismaModel,
|
||||
ComponentNode,
|
||||
ThemeConfig,
|
||||
PlaywrightTest,
|
||||
StorybookStory,
|
||||
UnitTest,
|
||||
FlaskConfig,
|
||||
NextJsConfig,
|
||||
NpmSettings,
|
||||
} from '@/types/project'
|
||||
import {
|
||||
generateNextJSProject,
|
||||
generatePrismaSchema,
|
||||
generateMUITheme,
|
||||
generatePlaywrightTests,
|
||||
generateStorybookStories,
|
||||
generateUnitTests,
|
||||
generateFlaskApp,
|
||||
} from '@/lib/generators'
|
||||
|
||||
export function useProjectExport(
|
||||
files: ProjectFile[],
|
||||
models: PrismaModel[],
|
||||
components: ComponentNode[],
|
||||
theme: ThemeConfig,
|
||||
playwrightTests: PlaywrightTest[],
|
||||
storybookStories: StorybookStory[],
|
||||
unitTests: UnitTest[],
|
||||
flaskConfig: FlaskConfig,
|
||||
nextjsConfig: NextJsConfig,
|
||||
npmSettings: NpmSettings
|
||||
) {
|
||||
const [generatedCode, setGeneratedCode] = useState<Record<string, string>>({})
|
||||
const [exportDialogOpen, setExportDialogOpen] = useState(false)
|
||||
|
||||
const handleExportProject = () => {
|
||||
const projectFiles = generateNextJSProject(nextjsConfig.appName, models, components, theme)
|
||||
|
||||
const prismaSchema = generatePrismaSchema(models)
|
||||
const themeCode = generateMUITheme(theme)
|
||||
const playwrightTestCode = generatePlaywrightTests(playwrightTests)
|
||||
const storybookFiles = generateStorybookStories(storybookStories)
|
||||
const unitTestFiles = generateUnitTests(unitTests)
|
||||
const flaskFiles = generateFlaskApp(flaskConfig)
|
||||
|
||||
const packageJson = {
|
||||
name: nextjsConfig.appName,
|
||||
version: '0.1.0',
|
||||
private: true,
|
||||
scripts: npmSettings.scripts,
|
||||
dependencies: npmSettings.packages
|
||||
.filter(pkg => !pkg.isDev)
|
||||
.reduce((acc, pkg) => {
|
||||
acc[pkg.name] = pkg.version
|
||||
return acc
|
||||
}, {} as Record<string, string>),
|
||||
devDependencies: npmSettings.packages
|
||||
.filter(pkg => pkg.isDev)
|
||||
.reduce((acc, pkg) => {
|
||||
acc[pkg.name] = pkg.version
|
||||
return acc
|
||||
}, {} as Record<string, string>),
|
||||
}
|
||||
|
||||
const allFiles: Record<string, string> = {
|
||||
...projectFiles,
|
||||
'package.json': JSON.stringify(packageJson, null, 2),
|
||||
'prisma/schema.prisma': prismaSchema,
|
||||
'src/theme.ts': themeCode,
|
||||
'e2e/tests.spec.ts': playwrightTestCode,
|
||||
...storybookFiles,
|
||||
...unitTestFiles,
|
||||
}
|
||||
|
||||
Object.entries(flaskFiles).forEach(([path, content]) => {
|
||||
allFiles[`backend/${path}`] = content
|
||||
})
|
||||
|
||||
files.forEach(file => {
|
||||
allFiles[file.path] = file.content
|
||||
})
|
||||
|
||||
setGeneratedCode(allFiles)
|
||||
setExportDialogOpen(true)
|
||||
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', `# ${nextjsConfig.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 = `${nextjsConfig.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')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
generatedCode,
|
||||
exportDialogOpen,
|
||||
setExportDialogOpen,
|
||||
handleExportProject,
|
||||
handleDownloadZip,
|
||||
}
|
||||
}
|
||||
37
src/hooks/use-project-loader.ts
Normal file
37
src/hooks/use-project-loader.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Project } from '@/types/project'
|
||||
|
||||
export function useProjectLoader(
|
||||
setFiles: (updater: any) => void,
|
||||
setModels: (updater: any) => void,
|
||||
setComponents: (updater: any) => void,
|
||||
setComponentTrees: (updater: any) => void,
|
||||
setWorkflows: (updater: any) => void,
|
||||
setLambdas: (updater: any) => void,
|
||||
setTheme: (updater: any) => void,
|
||||
setPlaywrightTests: (updater: any) => void,
|
||||
setStorybookStories: (updater: any) => void,
|
||||
setUnitTests: (updater: any) => void,
|
||||
setFlaskConfig: (updater: any) => void,
|
||||
setNextjsConfig: (updater: any) => void,
|
||||
setNpmSettings: (updater: any) => void,
|
||||
setFeatureToggles: (updater: any) => void
|
||||
) {
|
||||
const loadProject = (project: Project) => {
|
||||
if (project.files) setFiles(project.files)
|
||||
if (project.models) setModels(project.models)
|
||||
if (project.components) setComponents(project.components)
|
||||
if (project.componentTrees) setComponentTrees(project.componentTrees)
|
||||
if (project.workflows) setWorkflows(project.workflows)
|
||||
if (project.lambdas) setLambdas(project.lambdas)
|
||||
if (project.theme) setTheme(project.theme)
|
||||
if (project.playwrightTests) setPlaywrightTests(project.playwrightTests)
|
||||
if (project.storybookStories) setStorybookStories(project.storybookStories)
|
||||
if (project.unitTests) setUnitTests(project.unitTests)
|
||||
if (project.flaskConfig) setFlaskConfig(project.flaskConfig)
|
||||
if (project.nextjsConfig) setNextjsConfig(project.nextjsConfig)
|
||||
if (project.npmSettings) setNpmSettings(project.npmSettings)
|
||||
if (project.featureToggles) setFeatureToggles(project.featureToggles)
|
||||
}
|
||||
|
||||
return { loadProject }
|
||||
}
|
||||
214
src/hooks/use-project-state.ts
Normal file
214
src/hooks/use-project-state.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import { useKV } from '@github/spark/hooks'
|
||||
import {
|
||||
ProjectFile,
|
||||
PrismaModel,
|
||||
ComponentNode,
|
||||
ComponentTree,
|
||||
ThemeConfig,
|
||||
PlaywrightTest,
|
||||
StorybookStory,
|
||||
UnitTest,
|
||||
FlaskConfig,
|
||||
NextJsConfig,
|
||||
NpmSettings,
|
||||
Workflow,
|
||||
Lambda,
|
||||
FeatureToggles
|
||||
} from '@/types/project'
|
||||
|
||||
const DEFAULT_FLASK_CONFIG: FlaskConfig = {
|
||||
blueprints: [],
|
||||
corsOrigins: ['http://localhost:3000'],
|
||||
enableSwagger: true,
|
||||
port: 5000,
|
||||
debug: true,
|
||||
}
|
||||
|
||||
const DEFAULT_NEXTJS_CONFIG: NextJsConfig = {
|
||||
appName: 'my-nextjs-app',
|
||||
typescript: true,
|
||||
eslint: true,
|
||||
tailwind: true,
|
||||
srcDirectory: true,
|
||||
appRouter: true,
|
||||
importAlias: '@/*',
|
||||
turbopack: false,
|
||||
}
|
||||
|
||||
const DEFAULT_NPM_SETTINGS: NpmSettings = {
|
||||
packages: [
|
||||
{ id: '1', name: 'react', version: '^18.2.0', isDev: false },
|
||||
{ id: '2', name: 'react-dom', version: '^18.2.0', isDev: false },
|
||||
{ id: '3', name: 'next', version: '^14.0.0', isDev: false },
|
||||
{ id: '4', name: '@mui/material', version: '^5.14.0', isDev: false },
|
||||
{ id: '5', name: 'typescript', version: '^5.0.0', isDev: true },
|
||||
{ id: '6', name: '@types/react', version: '^18.2.0', isDev: true },
|
||||
],
|
||||
scripts: {
|
||||
dev: 'next dev',
|
||||
build: 'next build',
|
||||
start: 'next start',
|
||||
lint: 'next lint',
|
||||
},
|
||||
packageManager: 'npm',
|
||||
}
|
||||
|
||||
const DEFAULT_FEATURE_TOGGLES: FeatureToggles = {
|
||||
codeEditor: true,
|
||||
models: true,
|
||||
components: true,
|
||||
componentTrees: true,
|
||||
workflows: true,
|
||||
lambdas: true,
|
||||
styling: true,
|
||||
flaskApi: true,
|
||||
playwright: true,
|
||||
storybook: true,
|
||||
unitTests: true,
|
||||
errorRepair: true,
|
||||
documentation: true,
|
||||
sassStyles: true,
|
||||
faviconDesigner: true,
|
||||
ideaCloud: true,
|
||||
}
|
||||
|
||||
const DEFAULT_THEME: ThemeConfig = {
|
||||
variants: [
|
||||
{
|
||||
id: 'light',
|
||||
name: 'Light',
|
||||
colors: {
|
||||
primaryColor: '#1976d2',
|
||||
secondaryColor: '#dc004e',
|
||||
errorColor: '#f44336',
|
||||
warningColor: '#ff9800',
|
||||
successColor: '#4caf50',
|
||||
background: '#ffffff',
|
||||
surface: '#f5f5f5',
|
||||
text: '#000000',
|
||||
textSecondary: '#666666',
|
||||
border: '#e0e0e0',
|
||||
customColors: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'dark',
|
||||
name: 'Dark',
|
||||
colors: {
|
||||
primaryColor: '#90caf9',
|
||||
secondaryColor: '#f48fb1',
|
||||
errorColor: '#f44336',
|
||||
warningColor: '#ffa726',
|
||||
successColor: '#66bb6a',
|
||||
background: '#121212',
|
||||
surface: '#1e1e1e',
|
||||
text: '#ffffff',
|
||||
textSecondary: '#b0b0b0',
|
||||
border: '#333333',
|
||||
customColors: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
activeVariantId: 'light',
|
||||
fontFamily: 'Roboto, Arial, sans-serif',
|
||||
fontSize: { small: 12, medium: 14, large: 20 },
|
||||
spacing: 8,
|
||||
borderRadius: 4,
|
||||
}
|
||||
|
||||
const DEFAULT_FILES: ProjectFile[] = [
|
||||
{
|
||||
id: 'file-1',
|
||||
name: 'page.tsx',
|
||||
path: '/src/app/page.tsx',
|
||||
content: `'use client'\n\nimport { ThemeProvider } from '@mui/material/styles'\nimport CssBaseline from '@mui/material/CssBaseline'\nimport { theme } from '@/theme'\nimport { Box, Typography, Button } from '@mui/material'\n\nexport default function Home() {\n return (\n <ThemeProvider theme={theme}>\n <CssBaseline />\n <Box sx={{ p: 4 }}>\n <Typography variant="h3" gutterBottom>\n Welcome to Your App\n </Typography>\n <Button variant="contained" color="primary">\n Get Started\n </Button>\n </Box>\n </ThemeProvider>\n )\n}`,
|
||||
language: 'typescript',
|
||||
},
|
||||
{
|
||||
id: 'file-2',
|
||||
name: 'layout.tsx',
|
||||
path: '/src/app/layout.tsx',
|
||||
content: `export const metadata = {\n title: 'My Next.js App',\n description: 'Generated with CodeForge',\n}\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode\n}) {\n return (\n <html lang="en">\n <body>{children}</body>\n </html>\n )\n}`,
|
||||
language: 'typescript',
|
||||
},
|
||||
]
|
||||
|
||||
export function useProjectState() {
|
||||
const [files, setFiles] = useKV<ProjectFile[]>('project-files', DEFAULT_FILES)
|
||||
const [models, setModels] = useKV<PrismaModel[]>('project-models', [])
|
||||
const [components, setComponents] = useKV<ComponentNode[]>('project-components', [])
|
||||
const [componentTrees, setComponentTrees] = useKV<ComponentTree[]>('project-component-trees', [
|
||||
{
|
||||
id: 'default-tree',
|
||||
name: 'Main App',
|
||||
description: 'Default component tree',
|
||||
rootNodes: [],
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
])
|
||||
const [workflows, setWorkflows] = useKV<Workflow[]>('project-workflows', [])
|
||||
const [lambdas, setLambdas] = useKV<Lambda[]>('project-lambdas', [])
|
||||
const [theme, setTheme] = useKV<ThemeConfig>('project-theme', DEFAULT_THEME)
|
||||
const [playwrightTests, setPlaywrightTests] = useKV<PlaywrightTest[]>('project-playwright-tests', [])
|
||||
const [storybookStories, setStorybookStories] = useKV<StorybookStory[]>('project-storybook-stories', [])
|
||||
const [unitTests, setUnitTests] = useKV<UnitTest[]>('project-unit-tests', [])
|
||||
const [flaskConfig, setFlaskConfig] = useKV<FlaskConfig>('project-flask-config', DEFAULT_FLASK_CONFIG)
|
||||
const [nextjsConfig, setNextjsConfig] = useKV<NextJsConfig>('project-nextjs-config', DEFAULT_NEXTJS_CONFIG)
|
||||
const [npmSettings, setNpmSettings] = useKV<NpmSettings>('project-npm-settings', DEFAULT_NPM_SETTINGS)
|
||||
const [featureToggles, setFeatureToggles] = useKV<FeatureToggles>('project-feature-toggles', DEFAULT_FEATURE_TOGGLES)
|
||||
|
||||
const safeFiles = Array.isArray(files) ? files : []
|
||||
const safeModels = Array.isArray(models) ? models : []
|
||||
const safeComponents = Array.isArray(components) ? components : []
|
||||
const safeComponentTrees = Array.isArray(componentTrees) ? componentTrees : []
|
||||
const safeWorkflows = Array.isArray(workflows) ? workflows : []
|
||||
const safeLambdas = Array.isArray(lambdas) ? lambdas : []
|
||||
const safeTheme = (theme && theme.variants && Array.isArray(theme.variants) && theme.variants.length > 0) ? theme : DEFAULT_THEME
|
||||
const safePlaywrightTests = Array.isArray(playwrightTests) ? playwrightTests : []
|
||||
const safeStorybookStories = Array.isArray(storybookStories) ? storybookStories : []
|
||||
const safeUnitTests = Array.isArray(unitTests) ? unitTests : []
|
||||
const safeFlaskConfig = flaskConfig || DEFAULT_FLASK_CONFIG
|
||||
const safeNextjsConfig = nextjsConfig || DEFAULT_NEXTJS_CONFIG
|
||||
const safeNpmSettings = npmSettings || DEFAULT_NPM_SETTINGS
|
||||
const safeFeatureToggles = featureToggles || DEFAULT_FEATURE_TOGGLES
|
||||
|
||||
return {
|
||||
files: safeFiles,
|
||||
setFiles,
|
||||
models: safeModels,
|
||||
setModels,
|
||||
components: safeComponents,
|
||||
setComponents,
|
||||
componentTrees: safeComponentTrees,
|
||||
setComponentTrees,
|
||||
workflows: safeWorkflows,
|
||||
setWorkflows,
|
||||
lambdas: safeLambdas,
|
||||
setLambdas,
|
||||
theme: safeTheme,
|
||||
setTheme,
|
||||
playwrightTests: safePlaywrightTests,
|
||||
setPlaywrightTests,
|
||||
storybookStories: safeStorybookStories,
|
||||
setStorybookStories,
|
||||
unitTests: safeUnitTests,
|
||||
setUnitTests,
|
||||
flaskConfig: safeFlaskConfig,
|
||||
setFlaskConfig,
|
||||
nextjsConfig: safeNextjsConfig,
|
||||
setNextjsConfig,
|
||||
npmSettings: safeNpmSettings,
|
||||
setNpmSettings,
|
||||
featureToggles: safeFeatureToggles,
|
||||
setFeatureToggles,
|
||||
defaults: {
|
||||
DEFAULT_FLASK_CONFIG,
|
||||
DEFAULT_NEXTJS_CONFIG,
|
||||
DEFAULT_NPM_SETTINGS,
|
||||
DEFAULT_FEATURE_TOGGLES,
|
||||
DEFAULT_THEME,
|
||||
DEFAULT_FILES,
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/hooks/use-tab-navigation.ts
Normal file
18
src/hooks/use-tab-navigation.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export function useTabNavigation(defaultTab: string) {
|
||||
const [activeTab, setActiveTab] = useState(defaultTab)
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const shortcut = params.get('shortcut')
|
||||
if (shortcut) {
|
||||
setActiveTab(shortcut)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user