Refactor Site Builder Integration and Update Docker Configuration

- Merged the site builder functionality into the main app, enhancing the SiteBuilderWizard component with new steps and improved UI.
- Updated the Docker Compose configuration by removing the separate site builder service and integrating its functionality into the igny8_sites service.
- Enhanced Vite configuration to support code-splitting for builder routes, optimizing loading times.
- Updated package dependencies to include new libraries for state management and form handling.
This commit is contained in:
IGNY8 VPS (Salman)
2025-11-18 10:35:30 +00:00
parent 8508af37c7
commit 3ea519483d
19 changed files with 1637 additions and 91 deletions

116
sites/MIGRATION_SUMMARY.md Normal file
View File

@@ -0,0 +1,116 @@
# Site Builder → Sites Container Migration Summary
## Overview
Successfully merged Site Builder container into Sites container. Sites is now the primary container hosting both:
- **Public Site Renderer** (no auth required)
- **Site Builder** (auth required)
## Structure Changes
### New Directory Structure
```
sites/src/
├── builder/ # Site Builder (from site-builder/src/)
│ ├── pages/ # Wizard, Preview, Dashboard
│ ├── components/ # Builder-specific components
│ ├── state/ # Zustand stores
│ ├── api/ # API client with conditional auth
│ ├── types/ # TypeScript types
│ └── App.css # Builder styles
├── renderer/ # Sites Renderer (existing)
│ ├── pages/ # SiteRenderer component
│ ├── loaders/ # Site definition loaders
│ ├── utils/ # Layout renderer utilities
│ └── types/ # Renderer types
├── shared/ # Shared components
│ └── ProtectedRoute.tsx
├── App.tsx # Unified router
└── main.tsx # Entry point
```
## Routing Structure
### Public Routes (No Auth)
- `/:siteId/*` - Site renderer (public sites)
- `/` - Root page
### Builder Routes (Auth Required)
- `/builder` - Wizard page
- `/builder/preview` - Preview canvas
- `/builder/dashboard` - Blueprint history
## Key Changes
### 1. Unified Router (`App.tsx`)
- Single router handles both builder and renderer routes
- Builder routes wrapped in `ProtectedRoute` component
- Builder routes wrapped in `BuilderLayout` component
- Code-splitting for builder routes (lazy loading)
### 2. Authentication
- `ProtectedRoute` component checks for JWT token in localStorage
- Builder API client conditionally includes auth headers
- Public renderer routes have no authentication
### 3. API Clients
- Builder API (`builder/api/builder.api.ts`): Includes auth token if available
- Renderer API (`renderer/loaders/loadSiteDefinition.ts`): No auth required
### 4. Build Configuration
- Updated `vite.config.ts` with code-splitting
- Builder routes split into separate chunk
- Supports both `sites.igny8.com` and `builder.igny8.com` domains
### 5. Docker Configuration
- Removed `igny8_site_builder` service from docker-compose
- Updated `igny8_sites` service comment
- Single container now handles both functions
## Backend (No Changes)
- Backend Django views remain unchanged
- API endpoints: `/api/v1/site-builder/` (same as before)
- Database tables: No changes
- Authentication: Same JWT-based auth
## Migration Checklist
✅ Created merged directory structure
✅ Moved site-builder files to sites/src/builder/
✅ Created unified App.tsx with routing
✅ Created ProtectedRoute component
✅ Created BuilderLayout component
✅ Merged package.json dependencies
✅ Updated vite.config.ts with code-splitting
✅ Updated main.tsx entry point
✅ Updated docker-compose.app.yml (removed site_builder service)
✅ Updated API clients with conditional authentication
✅ Updated index.css with builder styles
## Next Steps
1. **Test Builder Functionality**
- Navigate to `/builder` (should require auth)
- Test wizard flow
- Test preview
- Test dashboard
2. **Test Renderer Functionality**
- Navigate to `/:siteId/*` (should work without auth)
- Test site loading from filesystem
- Test site loading from API
3. **Build & Deploy**
- Build Docker image: `docker build -t igny8-sites-dev:latest -f Dockerfile.dev .`
- Update docker-compose: `docker compose -f docker-compose.app.yml up -d igny8_sites`
4. **Cleanup** (Optional)
- Remove `/site-builder` directory after verification
- Update documentation
## Notes
- **No Backward Compatibility**: Old site-builder container is removed
- **Backend Unchanged**: All Django views and models remain the same
- **Code-Splitting**: Builder code is lazy-loaded, so public sites don't load builder code
- **Route Conflicts**: Builder routes are namespaced under `/builder/*` to avoid conflicts

View File

@@ -17,27 +17,29 @@
"lucide-react": "^0.554.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.9.6"
"react-hook-form": "^7.66.0",
"react-router-dom": "^7.9.6",
"zustand": "^5.0.8"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.5.1",
"@types/node": "^24.10.0",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^5.1.0",
"@vitest/ui": "^1.0.4",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"jsdom": "^23.0.1",
"jsdom": "^25.0.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.3",
"vite": "^7.2.2",
"vitest": "^1.0.4"
"vitest": "^2.1.5"
}
}

View File

@@ -1,16 +1,52 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import SiteRenderer from './pages/SiteRenderer';
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import ProtectedRoute from './shared/ProtectedRoute';
import BuilderLayout from './builder/components/layout/BuilderLayout';
// Lazy load builder pages (code-split to avoid loading in public sites)
const WizardPage = lazy(() => import('./builder/pages/wizard/WizardPage'));
const PreviewCanvas = lazy(() => import('./builder/pages/preview/PreviewCanvas'));
const SiteDashboard = lazy(() => import('./builder/pages/dashboard/SiteDashboard'));
// Renderer pages (load immediately for public sites)
const SiteRenderer = lazy(() => import('./renderer/pages/SiteRenderer'));
// Loading component
const LoadingFallback = () => (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}>
<div>Loading...</div>
</div>
);
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/:siteId/*" element={<SiteRenderer />} />
<Route path="/" element={<div>IGNY8 Sites Renderer</div>} />
</Routes>
<Suspense fallback={<LoadingFallback />}>
<Routes>
{/* Public Site Renderer Routes (No Auth) */}
<Route path="/:siteId/*" element={<SiteRenderer />} />
<Route path="/" element={<div>IGNY8 Sites Renderer</div>} />
{/* Builder Routes (Auth Required) */}
<Route
path="/builder/*"
element={
<ProtectedRoute>
<BuilderLayout>
<Routes>
<Route path="/" element={<WizardPage />} />
<Route path="preview" element={<PreviewCanvas />} />
<Route path="dashboard" element={<SiteDashboard />} />
<Route path="*" element={<Navigate to="/builder" replace />} />
</Routes>
</BuilderLayout>
</ProtectedRoute>
}
/>
</Routes>
</Suspense>
</BrowserRouter>
);
}
export default App;

View File

@@ -1,29 +1,28 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-family: 'Inter', 'Inter var', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color: #0f172a;
background-color: #f5f7fb;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
background: #f5f7fb;
}
#root {
width: 100%;
margin: 0 auto;
text-align: center;
button {
font-family: inherit;
}
a {
color: inherit;
}

View File

@@ -1,11 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
);

View File

@@ -0,0 +1,44 @@
import { useEffect, useState } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
/**
* ProtectedRoute component that checks for authentication token.
* Redirects to login if not authenticated.
*/
interface ProtectedRouteProps {
children: React.ReactNode;
}
export default function ProtectedRoute({ children }: ProtectedRouteProps) {
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
const location = useLocation();
useEffect(() => {
// Check for JWT token in localStorage
const token = localStorage.getItem('auth-storage');
if (token) {
try {
const authData = JSON.parse(token);
setIsAuthenticated(!!authData?.state?.token);
} catch {
setIsAuthenticated(false);
}
} else {
setIsAuthenticated(false);
}
}, []);
if (isAuthenticated === null) {
// Still checking authentication
return <div>Checking authentication...</div>;
}
if (!isAuthenticated) {
// Redirect to login (or main app login page)
// In production, this might redirect to app.igny8.com/login
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <>{children}</>;
}

View File

@@ -23,10 +23,22 @@ export default defineConfig({
server: {
host: '0.0.0.0',
port: 5176,
allowedHosts: ['sites.igny8.com'],
allowedHosts: ['sites.igny8.com', 'builder.igny8.com'],
fs: {
allow: [path.resolve(__dirname, '..'), sharedComponentsPath],
},
},
build: {
rollupOptions: {
output: {
manualChunks: {
// Code-split builder routes to avoid loading in public sites
'builder': ['./src/builder/pages/wizard/WizardPage', './src/builder/pages/preview/PreviewCanvas', './src/builder/pages/dashboard/SiteDashboard'],
// Vendor chunks
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
'vendor-ui': ['lucide-react', 'zustand'],
},
},
},
},
});