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:
251
frontend/src/store/builderStore.ts
Normal file
251
frontend/src/store/builderStore.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
import { create } from "zustand";
|
||||
import { useSiteStore } from "./siteStore";
|
||||
import { useSectorStore } from "./sectorStore";
|
||||
import { useSiteDefinitionStore } from "./siteDefinitionStore";
|
||||
import { siteBuilderApi } from "../services/siteBuilder.api";
|
||||
import type {
|
||||
BuilderFormData,
|
||||
PageBlueprint,
|
||||
SiteBlueprint,
|
||||
StylePreferences,
|
||||
} from "../types/siteBuilder";
|
||||
|
||||
const defaultStyle: StylePreferences = {
|
||||
palette: "Vibrant modern palette with rich accent color",
|
||||
typography: "Sans-serif display for headings, humanist body font",
|
||||
personality: "Confident, energetic, optimistic",
|
||||
heroImagery: "Real people interacting with the product/service",
|
||||
};
|
||||
|
||||
const buildDefaultForm = (): BuilderFormData => {
|
||||
const site = useSiteStore.getState().activeSite;
|
||||
const sector = useSectorStore.getState().activeSector;
|
||||
|
||||
return {
|
||||
siteId: site?.id ?? null,
|
||||
sectorId: sector?.id ?? null,
|
||||
siteName: site?.name ?? "",
|
||||
businessType: "",
|
||||
industry: "",
|
||||
targetAudience: "",
|
||||
hostingType: "igny8_sites",
|
||||
businessBrief: "",
|
||||
objectives: ["Launch a conversion-focused marketing site"],
|
||||
style: defaultStyle,
|
||||
};
|
||||
};
|
||||
|
||||
interface BuilderState {
|
||||
form: BuilderFormData;
|
||||
currentStep: number;
|
||||
isSubmitting: boolean;
|
||||
isGenerating: boolean;
|
||||
error?: string;
|
||||
activeBlueprint?: SiteBlueprint;
|
||||
pages: PageBlueprint[];
|
||||
selectedPageIds: number[];
|
||||
generationProgress?: {
|
||||
pagesQueued: number;
|
||||
taskIds: number[];
|
||||
celeryTaskId?: string;
|
||||
};
|
||||
// Actions
|
||||
setField: <K extends keyof BuilderFormData>(
|
||||
key: K,
|
||||
value: BuilderFormData[K],
|
||||
) => void;
|
||||
updateStyle: (partial: Partial<StylePreferences>) => void;
|
||||
addObjective: (value: string) => void;
|
||||
removeObjective: (index: number) => void;
|
||||
setStep: (step: number) => void;
|
||||
nextStep: () => void;
|
||||
previousStep: () => void;
|
||||
reset: () => void;
|
||||
syncContextFromStores: () => void;
|
||||
submitWizard: () => Promise<void>;
|
||||
refreshPages: (blueprintId: number) => Promise<void>;
|
||||
togglePageSelection: (pageId: number) => void;
|
||||
selectAllPages: () => void;
|
||||
clearPageSelection: () => void;
|
||||
generateAllPages: (blueprintId: number, force?: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useBuilderStore = create<BuilderState>((set, get) => ({
|
||||
form: buildDefaultForm(),
|
||||
currentStep: 0,
|
||||
isSubmitting: false,
|
||||
isGenerating: false,
|
||||
pages: [],
|
||||
selectedPageIds: [],
|
||||
|
||||
setField: (key, value) =>
|
||||
set((state) => ({
|
||||
form: { ...state.form, [key]: value },
|
||||
})),
|
||||
|
||||
updateStyle: (partial) =>
|
||||
set((state) => ({
|
||||
form: { ...state.form, style: { ...state.form.style, ...partial } },
|
||||
})),
|
||||
|
||||
addObjective: (value) =>
|
||||
set((state) => ({
|
||||
form: { ...state.form, objectives: [...state.form.objectives, value] },
|
||||
})),
|
||||
|
||||
removeObjective: (index) =>
|
||||
set((state) => ({
|
||||
form: {
|
||||
...state.form,
|
||||
objectives: state.form.objectives.filter((_, idx) => idx !== index),
|
||||
},
|
||||
})),
|
||||
|
||||
setStep: (step) => set({ currentStep: step }),
|
||||
|
||||
nextStep: () =>
|
||||
set((state) => ({
|
||||
currentStep: Math.min(state.currentStep + 1, 3),
|
||||
})),
|
||||
|
||||
previousStep: () =>
|
||||
set((state) => ({
|
||||
currentStep: Math.max(state.currentStep - 1, 0),
|
||||
})),
|
||||
|
||||
reset: () =>
|
||||
set({
|
||||
form: buildDefaultForm(),
|
||||
currentStep: 0,
|
||||
isSubmitting: false,
|
||||
error: undefined,
|
||||
activeBlueprint: undefined,
|
||||
pages: [],
|
||||
selectedPageIds: [],
|
||||
generationProgress: undefined,
|
||||
}),
|
||||
|
||||
syncContextFromStores: () => {
|
||||
const site = useSiteStore.getState().activeSite;
|
||||
const sector = useSectorStore.getState().activeSector;
|
||||
set((state) => ({
|
||||
form: {
|
||||
...state.form,
|
||||
siteId: site?.id ?? state.form.siteId,
|
||||
siteName: site?.name ?? state.form.siteName,
|
||||
sectorId: sector?.id ?? state.form.sectorId,
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
submitWizard: async () => {
|
||||
const { form } = get();
|
||||
if (!form.siteId || !form.sectorId) {
|
||||
set({
|
||||
error:
|
||||
"Select an active site and sector before running the Site Builder wizard.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
set({ isSubmitting: true, error: undefined });
|
||||
try {
|
||||
const payload = {
|
||||
name: form.siteName || `Site Blueprint (${form.industry || "New"})`,
|
||||
description: form.businessType
|
||||
? `${form.businessType} for ${form.targetAudience}`
|
||||
: undefined,
|
||||
site_id: form.siteId,
|
||||
sector_id: form.sectorId,
|
||||
hosting_type: form.hostingType,
|
||||
config_json: {
|
||||
business_type: form.businessType,
|
||||
industry: form.industry,
|
||||
target_audience: form.targetAudience,
|
||||
},
|
||||
};
|
||||
|
||||
const blueprint = await siteBuilderApi.createBlueprint(payload);
|
||||
set({ activeBlueprint: blueprint });
|
||||
|
||||
const generation = await siteBuilderApi.generateStructure(
|
||||
blueprint.id,
|
||||
{
|
||||
business_brief: form.businessBrief,
|
||||
objectives: form.objectives,
|
||||
style: form.style,
|
||||
metadata: { targetAudience: form.targetAudience },
|
||||
},
|
||||
);
|
||||
|
||||
if (generation?.structure) {
|
||||
useSiteDefinitionStore.getState().setStructure(generation.structure);
|
||||
}
|
||||
|
||||
await get().refreshPages(blueprint.id);
|
||||
} catch (error: any) {
|
||||
set({
|
||||
error: error?.message || "Unexpected error while running wizard",
|
||||
});
|
||||
} finally {
|
||||
set({ isSubmitting: false });
|
||||
}
|
||||
},
|
||||
|
||||
refreshPages: async (blueprintId: number) => {
|
||||
try {
|
||||
const pages = await siteBuilderApi.listPages(blueprintId);
|
||||
set({ pages });
|
||||
useSiteDefinitionStore.getState().setPages(pages);
|
||||
} catch (error: any) {
|
||||
set({
|
||||
error: error?.message || "Unable to load generated pages",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
togglePageSelection: (pageId: number) =>
|
||||
set((state) => {
|
||||
const isSelected = state.selectedPageIds.includes(pageId);
|
||||
return {
|
||||
selectedPageIds: isSelected
|
||||
? state.selectedPageIds.filter((id) => id !== pageId)
|
||||
: [...state.selectedPageIds, pageId],
|
||||
};
|
||||
}),
|
||||
|
||||
selectAllPages: () =>
|
||||
set((state) => ({
|
||||
selectedPageIds: state.pages.map((page) => page.id),
|
||||
})),
|
||||
|
||||
clearPageSelection: () => set({ selectedPageIds: [] }),
|
||||
|
||||
generateAllPages: async (blueprintId: number, force = false) => {
|
||||
const { selectedPageIds } = get();
|
||||
set({ isGenerating: true, error: undefined, generationProgress: undefined });
|
||||
try {
|
||||
const result = await siteBuilderApi.generateAllPages(blueprintId, {
|
||||
pageIds: selectedPageIds.length > 0 ? selectedPageIds : undefined,
|
||||
force,
|
||||
});
|
||||
|
||||
set({
|
||||
generationProgress: {
|
||||
pagesQueued: result.pages_queued,
|
||||
taskIds: result.task_ids,
|
||||
celeryTaskId: result.celery_task_id,
|
||||
},
|
||||
});
|
||||
|
||||
await get().refreshPages(blueprintId);
|
||||
} catch (error: any) {
|
||||
set({
|
||||
error: error?.message || "Failed to queue page generation",
|
||||
});
|
||||
} finally {
|
||||
set({ isGenerating: false });
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
30
frontend/src/store/siteDefinitionStore.ts
Normal file
30
frontend/src/store/siteDefinitionStore.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { create } from "zustand";
|
||||
import type {
|
||||
PageBlueprint,
|
||||
SiteStructure,
|
||||
} from "../types/siteBuilder";
|
||||
|
||||
interface SiteDefinitionState {
|
||||
structure?: SiteStructure;
|
||||
pages: PageBlueprint[];
|
||||
selectedSlug?: string;
|
||||
setStructure: (structure: SiteStructure) => void;
|
||||
setPages: (pages: PageBlueprint[]) => void;
|
||||
selectPage: (slug: string) => void;
|
||||
}
|
||||
|
||||
export const useSiteDefinitionStore = create<SiteDefinitionState>((set) => ({
|
||||
pages: [],
|
||||
setStructure: (structure) =>
|
||||
set({
|
||||
structure,
|
||||
selectedSlug: structure.pages?.[0]?.slug,
|
||||
}),
|
||||
setPages: (pages) =>
|
||||
set((state) => ({
|
||||
pages,
|
||||
selectedSlug: state.selectedSlug ?? pages[0]?.slug,
|
||||
})),
|
||||
selectPage: (slug) => set({ selectedSlug: slug }),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user