Generated by Spark: Now just use said json data as seed data and load it all from database

This commit is contained in:
2026-01-16 23:45:29 +00:00
committed by GitHub
parent 838abcb618
commit d2e29363f5
11 changed files with 793 additions and 1 deletions

199
SEED_DATA_GUIDE.md Normal file
View File

@@ -0,0 +1,199 @@
# Seed Data System
## Overview
The application now includes a comprehensive seed data system that loads initial data from JSON configuration into the database (KV store) on first launch.
## Features
### 1. Automatic Data Loading
- Seed data is automatically loaded on application startup
- Only loads if data doesn't already exist (safe to re-run)
- All data is stored in the KV database for persistence
### 2. Seed Data Management UI
Navigate to **Settings → Data** tab to access the Seed Data Manager with three options:
- **Load Seed Data**: Populates database with initial data (only if not already loaded)
- **Reset to Defaults**: Overwrites all data with fresh seed data
- **Clear All Data**: Removes all data from the database (destructive action)
### 3. Dashboard Integration
The Project Dashboard displays available seed data including:
- Number of pre-configured files
- Number of sample models
- Number of example components
- And more...
## Seed Data Contents
The system includes the following pre-configured data:
### Files (`project-files`)
- Sample Next.js pages (page.tsx, layout.tsx)
- Material UI integration examples
- TypeScript configuration
### Models (`project-models`)
- User model with authentication fields
- Post model with relationships
- Complete Prisma schema examples
### Components (`project-components`)
- Button components with variants
- Card components with styling
- Reusable UI elements
### Workflows (`project-workflows`)
- User registration flow
- Complete workflow with triggers and actions
- Visual workflow nodes and connections
### Lambdas (`project-lambdas`)
- User data processing function
- HTTP trigger configuration
- Environment variable examples
### Tests (`project-playwright-tests`, `project-storybook-stories`, `project-unit-tests`)
- E2E test examples
- Component story examples
- Unit test templates
### Component Trees (`project-component-trees`)
- Application layout tree
- Nested component structures
- Material UI component hierarchies
## Configuration
### Adding New Seed Data
Edit `/src/config/seed-data.json` to add or modify seed data:
```json
{
"project-files": [...],
"project-models": [...],
"your-custom-key": [...]
}
```
### Seed Data Schema
Each data type follows the TypeScript interfaces defined in `/src/types/project.ts`:
- `ProjectFile`
- `PrismaModel`
- `ComponentNode`
- `Workflow`
- `Lambda`
- `PlaywrightTest`
- `StorybookStory`
- `UnitTest`
- `ComponentTree`
## API Reference
### Hook: `useSeedData()`
```typescript
import { useSeedData } from '@/hooks/data/use-seed-data'
const { isLoaded, isLoading, loadSeedData, resetSeedData, clearAllData } = useSeedData()
```
**Returns:**
- `isLoaded`: Boolean indicating if seed data has been loaded
- `isLoading`: Boolean indicating if an operation is in progress
- `loadSeedData()`: Function to load seed data (if not already loaded)
- `resetSeedData()`: Function to reset all data to seed defaults
- `clearAllData()`: Function to clear all data from database
### Direct KV API
You can also interact with seed data directly:
```typescript
// Get specific seed data
const files = await window.spark.kv.get('project-files')
// Update seed data
await window.spark.kv.set('project-files', updatedFiles)
// Delete seed data
await window.spark.kv.delete('project-files')
```
## Components
### `<SeedDataManager />`
Management UI for seed data operations (used in Settings → Data tab)
### `<SeedDataStatus />`
Display component showing available seed data (used on Dashboard)
## Best Practices
1. **Always use the useKV hook** for reactive state management
2. **Use functional updates** when modifying arrays/objects to prevent data loss
3. **Test seed data** thoroughly before deploying
4. **Document custom seed data** for team members
5. **Keep seed data minimal** - only include essential examples
## Example: Custom Seed Data
```typescript
// 1. Add to seed-data.json
{
"my-custom-data": [
{ "id": "1", "name": "Example 1" },
{ "id": "2", "name": "Example 2" }
]
}
// 2. Use in component
import { useKV } from '@github/spark/hooks'
function MyComponent() {
const [data, setData] = useKV('my-custom-data', [])
// Always use functional updates
const addItem = (newItem) => {
setData(current => [...current, newItem])
}
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
)
}
```
## Troubleshooting
### Data Not Loading
- Check browser console for errors
- Verify seed-data.json is valid JSON
- Ensure data keys match KV store keys
### Data Persisting After Clear
- Hard refresh the browser (Ctrl+Shift+R)
- Check for cached service workers (PWA)
- Verify clearAllData completed successfully
### Type Errors
- Ensure seed data matches TypeScript interfaces
- Update types in `/src/types/project.ts` if needed
- Run type checking: `npm run type-check`
## Future Enhancements
Planned improvements:
- Import/export seed data as JSON files
- Version control for seed data
- Seed data migration system
- Seed data validation UI
- Partial seed data loading

View File

@@ -1,4 +1,4 @@
import { useState, lazy, Suspense, useMemo } from 'react'
import { useState, lazy, Suspense, useMemo, useEffect } from 'react'
import { Tabs, TabsContent } from '@/components/ui/tabs'
import { AppHeader, PageHeader } from '@/components/organisms'
import { LoadingFallback } from '@/components/molecules'
@@ -6,6 +6,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/componen
import { useProjectState } from '@/hooks/use-project-state'
import { useFileOperations } from '@/hooks/use-file-operations'
import { useKeyboardShortcuts } from '@/hooks/use-keyboard-shortcuts'
import { useSeedData } from '@/hooks/data/use-seed-data'
import { getPageConfig, getEnabledPages, getPageShortcuts, resolveProps } from '@/config/page-loader'
import { toast } from 'sonner'
@@ -74,6 +75,7 @@ function App() {
const fileOps = useFileOperations(files, setFiles)
const { activeFileId, setActiveFileId, handleFileChange, handleFileAdd, handleFileClose } = fileOps
const { loadSeedData } = useSeedData()
const [activeTab, setActiveTab] = useState('dashboard')
const [searchOpen, setSearchOpen] = useState(false)
@@ -81,6 +83,10 @@ function App() {
const [lastSaved] = useState<number | null>(Date.now())
const [errorCount] = useState(0)
useEffect(() => {
loadSeedData()
}, [])
const pageConfig = useMemo(() => getPageConfig(), [])
const enabledPages = useMemo(() => getEnabledPages(featureToggles), [featureToggles])
const shortcuts = useMemo(() => getPageShortcuts(featureToggles), [featureToggles])

View File

@@ -14,6 +14,7 @@ import {
Warning
} from '@phosphor-icons/react'
import { ProjectFile, PrismaModel, ComponentNode, ThemeConfig, PlaywrightTest, StorybookStory, UnitTest, FlaskConfig } from '@/types/project'
import { SeedDataStatus } from '@/components/atoms'
interface ProjectDashboardProps {
files: ProjectFile[]
@@ -131,6 +132,8 @@ export function ProjectDashboard({
/>
</div>
<SeedDataStatus />
<Card>
<CardHeader>
<CardTitle>Project Details</CardTitle>

View File

@@ -11,6 +11,7 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Plus, Trash, Package, Cube, Code } from '@phosphor-icons/react'
import { Badge } from '@/components/ui/badge'
import { SeedDataManager } from '@/components/molecules'
interface ProjectSettingsDesignerProps {
nextjsConfig: NextJsConfig
@@ -134,6 +135,7 @@ export function ProjectSettingsDesigner({
<TabsTrigger value="nextjs">Next.js Config</TabsTrigger>
<TabsTrigger value="packages">NPM Packages</TabsTrigger>
<TabsTrigger value="scripts">Scripts</TabsTrigger>
<TabsTrigger value="data">Data</TabsTrigger>
</TabsList>
</div>
@@ -504,6 +506,12 @@ export function ProjectSettingsDesigner({
</div>
</div>
</TabsContent>
<TabsContent value="data" className="mt-0">
<div className="max-w-2xl">
<SeedDataManager />
</div>
</TabsContent>
</div>
</ScrollArea>
</Tabs>

View File

@@ -0,0 +1,60 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Database, Check, X } from '@phosphor-icons/react'
import seedDataConfig from '@/config/seed-data.json'
export function SeedDataStatus() {
const dataKeys = Object.keys(seedDataConfig)
const getDataCount = (key: string): number => {
const data = seedDataConfig[key as keyof typeof seedDataConfig]
return Array.isArray(data) ? data.length : 0
}
const getLabelForKey = (key: string): string => {
const labels: Record<string, string> = {
'project-files': 'Files',
'project-models': 'Models',
'project-components': 'Components',
'project-workflows': 'Workflows',
'project-lambdas': 'Lambdas',
'project-playwright-tests': 'Playwright Tests',
'project-storybook-stories': 'Storybook Stories',
'project-unit-tests': 'Unit Tests',
'project-component-trees': 'Component Trees',
}
return labels[key] || key
}
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Database size={20} weight="duotone" />
Seed Data Available
</CardTitle>
<CardDescription>
Pre-configured data ready to load from database
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
{dataKeys.map((key) => {
const count = getDataCount(key)
return (
<div
key={key}
className="flex items-center justify-between p-3 rounded-lg border border-border bg-muted/50"
>
<span className="text-sm font-medium">{getLabelForKey(key)}</span>
<Badge variant="secondary" className="ml-2">
{count}
</Badge>
</div>
)
})}
</div>
</CardContent>
</Card>
)
}

View File

@@ -8,3 +8,4 @@ export { EmptyStateIcon } from './EmptyStateIcon'
export { TreeIcon } from './TreeIcon'
export { FileIcon } from './FileIcon'
export { ActionIcon } from './ActionIcon'
export { SeedDataStatus } from './SeedDataStatus'

View File

@@ -0,0 +1,97 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { useSeedData } from '@/hooks/data/use-seed-data'
import { Database, ArrowClockwise, Trash, CheckCircle, CircleNotch } from '@phosphor-icons/react'
export function SeedDataManager() {
const { isLoaded, isLoading, loadSeedData, resetSeedData, clearAllData } = useSeedData()
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database size={24} weight="duotone" />
Seed Data Management
</CardTitle>
<CardDescription>
Load, reset, or clear application seed data from the database
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
{isLoaded && (
<Alert>
<CheckCircle className="h-4 w-4" weight="fill" />
<AlertDescription>
Seed data is loaded and available
</AlertDescription>
</Alert>
)}
<div className="flex flex-col gap-3">
<div className="flex gap-2 flex-wrap">
<Button
onClick={loadSeedData}
disabled={isLoading || isLoaded}
variant="default"
>
{isLoading ? (
<>
<CircleNotch className="animate-spin" size={16} />
Loading...
</>
) : (
<>
<Database size={16} weight="fill" />
Load Seed Data
</>
)}
</Button>
<Button
onClick={resetSeedData}
disabled={isLoading}
variant="outline"
>
{isLoading ? (
<>
<CircleNotch className="animate-spin" size={16} />
Resetting...
</>
) : (
<>
<ArrowClockwise size={16} weight="bold" />
Reset to Defaults
</>
)}
</Button>
<Button
onClick={clearAllData}
disabled={isLoading}
variant="destructive"
>
{isLoading ? (
<>
<CircleNotch className="animate-spin" size={16} />
Clearing...
</>
) : (
<>
<Trash size={16} weight="fill" />
Clear All Data
</>
)}
</Button>
</div>
<div className="text-sm text-muted-foreground space-y-1">
<p><strong>Load Seed Data:</strong> Populates database with initial data if not already loaded</p>
<p><strong>Reset to Defaults:</strong> Overwrites all data with fresh seed data</p>
<p><strong>Clear All Data:</strong> Removes all data from the database (destructive action)</p>
</div>
</div>
</CardContent>
</Card>
)
}

View File

@@ -13,6 +13,7 @@ export { NavigationGroupHeader } from './NavigationGroupHeader'
export { NavigationItem } from './NavigationItem'
export { PageHeaderContent } from './PageHeaderContent'
export { SaveIndicator } from './SaveIndicator'
export { SeedDataManager } from './SeedDataManager'
export { StatCard } from './StatCard'
export { ToolbarButton } from './ToolbarButton'
export { TreeCard } from './TreeCard'

351
src/config/seed-data.json Normal file
View File

@@ -0,0 +1,351 @@
{
"project-files": [
{
"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"
}
],
"project-models": [
{
"id": "model-1",
"name": "User",
"fields": [
{
"id": "field-1",
"name": "id",
"type": "String",
"isRequired": true,
"isUnique": true,
"isArray": false,
"defaultValue": "uuid()"
},
{
"id": "field-2",
"name": "email",
"type": "String",
"isRequired": true,
"isUnique": true,
"isArray": false
},
{
"id": "field-3",
"name": "name",
"type": "String",
"isRequired": false,
"isUnique": false,
"isArray": false
},
{
"id": "field-4",
"name": "createdAt",
"type": "DateTime",
"isRequired": true,
"isUnique": false,
"isArray": false,
"defaultValue": "now()"
}
]
},
{
"id": "model-2",
"name": "Post",
"fields": [
{
"id": "field-5",
"name": "id",
"type": "String",
"isRequired": true,
"isUnique": true,
"isArray": false,
"defaultValue": "uuid()"
},
{
"id": "field-6",
"name": "title",
"type": "String",
"isRequired": true,
"isUnique": false,
"isArray": false
},
{
"id": "field-7",
"name": "content",
"type": "String",
"isRequired": false,
"isUnique": false,
"isArray": false
},
{
"id": "field-8",
"name": "published",
"type": "Boolean",
"isRequired": true,
"isUnique": false,
"isArray": false,
"defaultValue": "false"
},
{
"id": "field-9",
"name": "authorId",
"type": "String",
"isRequired": true,
"isUnique": false,
"isArray": false,
"relation": "User"
}
]
}
],
"project-components": [
{
"id": "comp-1",
"type": "Button",
"name": "PrimaryButton",
"props": {
"variant": "contained",
"color": "primary"
},
"children": []
},
{
"id": "comp-2",
"type": "Card",
"name": "UserCard",
"props": {
"elevation": 2
},
"children": [
{
"id": "comp-3",
"type": "CardContent",
"name": "CardContent",
"props": {},
"children": []
}
]
}
],
"project-workflows": [
{
"id": "workflow-1",
"name": "User Registration Flow",
"description": "Complete user registration and onboarding workflow",
"nodes": [
{
"id": "node-1",
"type": "trigger",
"name": "Form Submit",
"position": { "x": 100, "y": 100 },
"data": {
"label": "Registration Form Submitted"
},
"config": {
"triggerType": "event"
}
},
{
"id": "node-2",
"type": "action",
"name": "Validate Input",
"position": { "x": 300, "y": 100 },
"data": {
"label": "Validate User Data"
}
},
{
"id": "node-3",
"type": "database",
"name": "Create User",
"position": { "x": 500, "y": 100 },
"data": {
"label": "Insert User Record"
},
"config": {
"databaseQuery": "INSERT INTO users"
}
}
],
"connections": [
{
"id": "conn-1",
"source": "node-1",
"target": "node-2"
},
{
"id": "conn-2",
"source": "node-2",
"target": "node-3"
}
],
"isActive": true,
"createdAt": 1704067200000,
"updatedAt": 1704067200000
}
],
"project-lambdas": [
{
"id": "lambda-1",
"name": "processUserData",
"description": "Process and transform user data",
"code": "export async function handler(event) {\n const { userId, data } = event;\n // Process user data\n return {\n statusCode: 200,\n body: JSON.stringify({ success: true })\n };\n}",
"language": "typescript",
"runtime": "nodejs20.x",
"handler": "index.handler",
"timeout": 30,
"memory": 256,
"environment": {
"NODE_ENV": "production"
},
"triggers": [
{
"id": "trigger-1",
"type": "http",
"config": {
"method": "POST",
"path": "/api/process-user"
}
}
],
"createdAt": 1704067200000,
"updatedAt": 1704067200000
}
],
"project-playwright-tests": [
{
"id": "test-1",
"name": "User Login Flow",
"description": "Test user authentication flow",
"pageUrl": "/login",
"steps": [
{
"id": "step-1",
"action": "navigate",
"value": "/login"
},
{
"id": "step-2",
"action": "fill",
"selector": "#email",
"value": "test@example.com"
},
{
"id": "step-3",
"action": "fill",
"selector": "#password",
"value": "password123"
},
{
"id": "step-4",
"action": "click",
"selector": "button[type='submit']"
},
{
"id": "step-5",
"action": "expect",
"selector": ".dashboard",
"assertion": "toBeVisible"
}
]
}
],
"project-storybook-stories": [
{
"id": "story-1",
"componentName": "Button",
"storyName": "Primary",
"args": {
"variant": "primary",
"children": "Click me",
"disabled": false
},
"description": "Primary button variant",
"category": "Components"
},
{
"id": "story-2",
"componentName": "Card",
"storyName": "Default",
"args": {
"title": "Card Title",
"description": "Card description",
"elevation": 2
},
"description": "Default card component",
"category": "Layout"
}
],
"project-unit-tests": [
{
"id": "test-1",
"name": "Button Component Tests",
"description": "Unit tests for Button component",
"testType": "component",
"targetFile": "/src/components/Button.tsx",
"testCases": [
{
"id": "case-1",
"description": "renders with correct text",
"assertions": [
"expect(screen.getByText('Click me')).toBeInTheDocument()"
]
},
{
"id": "case-2",
"description": "calls onClick handler when clicked",
"assertions": [
"expect(handleClick).toHaveBeenCalledTimes(1)"
]
}
]
}
],
"project-component-trees": [
{
"id": "tree-1",
"name": "Main App Layout",
"description": "Primary application layout tree",
"rootNodes": [
{
"id": "root-1",
"type": "Container",
"name": "AppContainer",
"props": {
"maxWidth": "xl"
},
"children": [
{
"id": "header-1",
"type": "AppBar",
"name": "Header",
"props": {
"position": "sticky"
},
"children": []
},
{
"id": "main-1",
"type": "Box",
"name": "MainContent",
"props": {
"sx": { "py": 4 }
},
"children": []
}
]
}
],
"createdAt": 1704067200000,
"updatedAt": 1704067200000
}
]
}

View File

@@ -0,0 +1,65 @@
import { useEffect, useState } from 'react'
import seedDataConfig from '@/config/seed-data.json'
export function useSeedData() {
const [isLoaded, setIsLoaded] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const loadSeedData = async () => {
if (isLoading || isLoaded) return
setIsLoading(true)
try {
const keys = await window.spark.kv.keys()
for (const [key, value] of Object.entries(seedDataConfig)) {
if (!keys.includes(key)) {
await window.spark.kv.set(key, value)
}
}
setIsLoaded(true)
} catch (error) {
console.error('Failed to load seed data:', error)
} finally {
setIsLoading(false)
}
}
const resetSeedData = async () => {
setIsLoading(true)
try {
for (const [key, value] of Object.entries(seedDataConfig)) {
await window.spark.kv.set(key, value)
}
setIsLoaded(true)
} catch (error) {
console.error('Failed to reset seed data:', error)
} finally {
setIsLoading(false)
}
}
const clearAllData = async () => {
setIsLoading(true)
try {
const keys = await window.spark.kv.keys()
for (const key of keys) {
await window.spark.kv.delete(key)
}
setIsLoaded(false)
} catch (error) {
console.error('Failed to clear data:', error)
} finally {
setIsLoading(false)
}
}
return {
isLoaded,
isLoading,
loadSeedData,
resetSeedData,
clearAllData,
}
}

View File

@@ -12,3 +12,4 @@ export * from './config/use-feature-flags'
export * from './ai/use-ai-generation'
export * from './data/use-seed-data'