mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-25 14:14:57 +00:00
Generated by Spark: Fix all reported errors.
This commit is contained in:
33
src/App.tsx
33
src/App.tsx
@@ -191,7 +191,7 @@ 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 [featureToggles, setFeatureToggles] = useKV<FeatureToggles>('project-feature-toggles', DEFAULT_FEATURE_TOGGLES)
|
||||
const [activeFileId, setActiveFileId] = useState<string | null>((files || [])[0]?.id || null)
|
||||
const [activeFileId, setActiveFileId] = useState<string | null>(null)
|
||||
const [activeTab, setActiveTab] = useState('dashboard')
|
||||
const [exportDialogOpen, setExportDialogOpen] = useState(false)
|
||||
const [shortcutsDialogOpen, setShortcutsDialogOpen] = useState(false)
|
||||
@@ -199,21 +199,27 @@ function App() {
|
||||
const [generatedCode, setGeneratedCode] = useState<Record<string, string>>({})
|
||||
const [lastSaved, setLastSaved] = useState<number | null>(Date.now())
|
||||
|
||||
const safeFiles = files || []
|
||||
const safeModels = models || []
|
||||
const safeComponents = components || []
|
||||
const safeComponentTrees = componentTrees || []
|
||||
const safeWorkflows = workflows || []
|
||||
const safeLambdas = lambdas || []
|
||||
const safeTheme = (theme && theme.variants && theme.variants.length > 0) ? theme : DEFAULT_THEME
|
||||
const safePlaywrightTests = playwrightTests || []
|
||||
const safeStorybookStories = storybookStories || []
|
||||
const safeUnitTests = unitTests || []
|
||||
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
|
||||
|
||||
useEffect(() => {
|
||||
if (safeFiles.length > 0 && !activeFileId) {
|
||||
setActiveFileId(safeFiles[0].id)
|
||||
}
|
||||
}, [safeFiles, activeFileId])
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const shortcut = params.get('shortcut')
|
||||
@@ -253,7 +259,8 @@ function App() {
|
||||
featureToggles,
|
||||
])
|
||||
|
||||
const { errors: autoDetectedErrors } = useAutoRepair(safeFiles, false)
|
||||
const { errors: autoDetectedErrors = [] } = useAutoRepair(safeFiles, false)
|
||||
const errorCount = Array.isArray(autoDetectedErrors) ? autoDetectedErrors.length : 0
|
||||
|
||||
useKeyboardShortcuts([
|
||||
{
|
||||
@@ -552,7 +559,7 @@ Navigate to the backend directory and follow the setup instructions.
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
featureToggles={safeFeatureToggles}
|
||||
errorCount={autoDetectedErrors.length}
|
||||
errorCount={errorCount}
|
||||
lastSaved={lastSaved}
|
||||
currentProject={getCurrentProject()}
|
||||
onProjectLoad={handleLoadProject}
|
||||
|
||||
@@ -38,63 +38,61 @@ export function PWAInstallPrompt() {
|
||||
localStorage.setItem('pwa-install-dismissed', 'true')
|
||||
}
|
||||
|
||||
if (!isInstallable || dismissed || !showPrompt) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 50 }}
|
||||
className="fixed bottom-4 right-4 z-50 max-w-sm"
|
||||
>
|
||||
<Card className="p-6 shadow-lg border-2 border-primary/20 bg-card/95 backdrop-blur">
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-primary to-accent flex items-center justify-center">
|
||||
<Download size={24} weight="duotone" className="text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<h3 className="font-bold text-lg">Install CodeForge</h3>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 -mt-1"
|
||||
onClick={handleDismiss}
|
||||
>
|
||||
<X size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Install our app for a faster, offline-capable experience with quick access from your device.
|
||||
</p>
|
||||
<div className="flex gap-2 text-xs text-muted-foreground mb-4">
|
||||
<div className="flex items-center gap-1">
|
||||
<Desktop size={16} />
|
||||
<span>Works offline</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DeviceMobile size={16} />
|
||||
<span>Quick access</span>
|
||||
{isInstallable && !dismissed && showPrompt && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 50 }}
|
||||
className="fixed bottom-4 right-4 z-50 max-w-sm"
|
||||
>
|
||||
<Card className="p-6 shadow-lg border-2 border-primary/20 bg-card/95 backdrop-blur">
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-primary to-accent flex items-center justify-center">
|
||||
<Download size={24} weight="duotone" className="text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={handleInstall} className="flex-1">
|
||||
<Download size={16} className="mr-2" />
|
||||
Install
|
||||
</Button>
|
||||
<Button variant="outline" onClick={handleDismiss}>
|
||||
Not now
|
||||
</Button>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<h3 className="font-bold text-lg">Install CodeForge</h3>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 -mt-1"
|
||||
onClick={handleDismiss}
|
||||
>
|
||||
<X size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Install our app for a faster, offline-capable experience with quick access from your device.
|
||||
</p>
|
||||
<div className="flex gap-2 text-xs text-muted-foreground mb-4">
|
||||
<div className="flex items-center gap-1">
|
||||
<Desktop size={16} />
|
||||
<span>Works offline</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DeviceMobile size={16} />
|
||||
<span>Quick access</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={handleInstall} className="flex-1">
|
||||
<Download size={16} className="mr-2" />
|
||||
Install
|
||||
</Button>
|
||||
<Button variant="outline" onClick={handleDismiss}>
|
||||
Not now
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ export function PWAStatusBar() {
|
||||
useEffect(() => {
|
||||
if (!isOnline) {
|
||||
setShowOffline(true)
|
||||
} else {
|
||||
} else if (showOffline) {
|
||||
const timer = setTimeout(() => {
|
||||
setShowOffline(false)
|
||||
}, 3000)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [isOnline])
|
||||
}, [isOnline, showOffline])
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
|
||||
@@ -17,52 +17,50 @@ export function PWAUpdatePrompt() {
|
||||
setDismissed(true)
|
||||
}
|
||||
|
||||
if (!isUpdateAvailable || dismissed) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -50 }}
|
||||
className="fixed top-4 right-4 z-50 max-w-sm"
|
||||
>
|
||||
<Card className="p-4 shadow-lg border-2 border-accent/20 bg-card/95 backdrop-blur">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-accent to-primary flex items-center justify-center">
|
||||
<CloudArrowDown size={20} weight="duotone" className="text-white" />
|
||||
{isUpdateAvailable && !dismissed && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -50 }}
|
||||
className="fixed top-4 right-4 z-50 max-w-sm"
|
||||
>
|
||||
<Card className="p-4 shadow-lg border-2 border-accent/20 bg-card/95 backdrop-blur">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-accent to-primary flex items-center justify-center">
|
||||
<CloudArrowDown size={20} weight="duotone" className="text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-start justify-between mb-1">
|
||||
<h3 className="font-semibold">Update Available</h3>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 -mt-1"
|
||||
onClick={handleDismiss}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-3">
|
||||
A new version is ready. Update now for the latest features and fixes.
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" onClick={handleUpdate} className="flex-1">
|
||||
Update Now
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={handleDismiss}>
|
||||
Later
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-start justify-between mb-1">
|
||||
<h3 className="font-semibold">Update Available</h3>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 -mt-1"
|
||||
onClick={handleDismiss}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mb-3">
|
||||
A new version is ready. Update now for the latest features and fixes.
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" onClick={handleUpdate} className="flex-1">
|
||||
Update Now
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={handleDismiss}>
|
||||
Later
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,12 +7,16 @@ interface StatusIconProps {
|
||||
}
|
||||
|
||||
export function StatusIcon({ type, size = 14, animate = false }: StatusIconProps) {
|
||||
const baseClassName = type === 'saved' ? 'text-accent' : ''
|
||||
const animateClassName = animate ? 'animate-in zoom-in duration-200' : ''
|
||||
const className = [baseClassName, animateClassName].filter(Boolean).join(' ')
|
||||
|
||||
if (type === 'saved') {
|
||||
return (
|
||||
<CheckCircle
|
||||
size={size}
|
||||
weight="fill"
|
||||
className={`text-accent ${animate ? 'animate-in zoom-in duration-200' : ''}`}
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,20 +11,25 @@ export function useAutoRepair(
|
||||
const [isScanning, setIsScanning] = useState(false)
|
||||
|
||||
const scanFiles = useCallback(async () => {
|
||||
if (!enabled || files.length === 0) return
|
||||
if (!enabled || !files || files.length === 0) return
|
||||
|
||||
setIsScanning(true)
|
||||
try {
|
||||
const allErrors: CodeError[] = []
|
||||
|
||||
for (const file of files) {
|
||||
const fileErrors = await ErrorRepairService.detectErrors(file)
|
||||
allErrors.push(...fileErrors)
|
||||
if (file && file.content) {
|
||||
const fileErrors = await ErrorRepairService.detectErrors(file)
|
||||
if (Array.isArray(fileErrors)) {
|
||||
allErrors.push(...fileErrors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setErrors(allErrors)
|
||||
} catch (error) {
|
||||
console.error('Auto-scan failed:', error)
|
||||
setErrors([])
|
||||
} finally {
|
||||
setIsScanning(false)
|
||||
}
|
||||
@@ -41,7 +46,7 @@ export function useAutoRepair(
|
||||
}, [files, enabled, scanFiles])
|
||||
|
||||
return {
|
||||
errors,
|
||||
errors: Array.isArray(errors) ? errors : [],
|
||||
isScanning,
|
||||
scanFiles,
|
||||
}
|
||||
|
||||
@@ -11,18 +11,24 @@ interface KeyboardShortcut {
|
||||
|
||||
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 (typeof window === 'undefined') return
|
||||
|
||||
if (ctrlMatch && shiftMatch && altMatch && keyMatch) {
|
||||
event.preventDefault()
|
||||
shortcut.action()
|
||||
break
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
try {
|
||||
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
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Keyboard Shortcuts] Error handling keydown:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +41,7 @@ export function getShortcutDisplay(shortcut: Omit<KeyboardShortcut, 'action'>):
|
||||
const parts: string[] = []
|
||||
|
||||
if (shortcut.ctrl) {
|
||||
parts.push(navigator.platform.includes('Mac') ? '⌘' : 'Ctrl')
|
||||
parts.push(typeof navigator !== 'undefined' && navigator.platform.includes('Mac') ? '⌘' : 'Ctrl')
|
||||
}
|
||||
if (shortcut.shift) {
|
||||
parts.push('Shift')
|
||||
|
||||
@@ -17,26 +17,36 @@ export function usePWA() {
|
||||
const [state, setState] = useState<PWAState>({
|
||||
isInstallable: false,
|
||||
isInstalled: false,
|
||||
isOnline: navigator.onLine,
|
||||
isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,
|
||||
isUpdateAvailable: false,
|
||||
registration: null,
|
||||
})
|
||||
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
const checkInstalled = () => {
|
||||
const isStandalone = window.matchMedia('(display-mode: standalone)').matches
|
||||
const isIOSStandalone = (window.navigator as any).standalone === true
|
||||
setState(prev => ({ ...prev, isInstalled: isStandalone || isIOSStandalone }))
|
||||
try {
|
||||
const isStandalone = window.matchMedia && window.matchMedia('(display-mode: standalone)').matches
|
||||
const isIOSStandalone = (window.navigator as any).standalone === true
|
||||
setState(prev => ({ ...prev, isInstalled: isStandalone || isIOSStandalone }))
|
||||
} catch (error) {
|
||||
console.error('[PWA] Error checking install status:', error)
|
||||
}
|
||||
}
|
||||
|
||||
checkInstalled()
|
||||
|
||||
const handleBeforeInstallPrompt = (e: Event) => {
|
||||
e.preventDefault()
|
||||
const installEvent = e as BeforeInstallPromptEvent
|
||||
setDeferredPrompt(installEvent)
|
||||
setState(prev => ({ ...prev, isInstallable: true }))
|
||||
try {
|
||||
e.preventDefault()
|
||||
const installEvent = e as BeforeInstallPromptEvent
|
||||
setDeferredPrompt(installEvent)
|
||||
setState(prev => ({ ...prev, isInstallable: true }))
|
||||
} catch (error) {
|
||||
console.error('[PWA] Error handling beforeinstallprompt:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAppInstalled = () => {
|
||||
|
||||
Reference in New Issue
Block a user