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:
116
sites/MIGRATION_SUMMARY.md
Normal file
116
sites/MIGRATION_SUMMARY.md
Normal 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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
44
sites/src/shared/ProtectedRoute.tsx
Normal file
44
sites/src/shared/ProtectedRoute.tsx
Normal 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}</>;
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user