import { create } from "zustand"; import { useSiteStore } from "./siteStore"; import { useSiteDefinitionStore } from "./siteDefinitionStore"; import { siteBuilderApi } from "../services/siteBuilder.api"; import type { BuilderFormData, PageBlueprint, SiteBlueprint, StylePreferences, SiteStructure, SiteBuilderMetadata, SiteBuilderMetadataOption, } 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; return { siteId: site?.id ?? null, sectorIds: site?.selected_sectors ?? [], siteName: site?.name ?? "", businessTypeId: null, businessType: "", customBusinessType: "", industry: site?.industry_name ?? "", targetAudienceIds: [], targetAudience: "", customTargetAudience: "", hostingType: "igny8_sites", businessBrief: "", objectives: ["Launch a conversion-focused marketing site"], brandPersonalityIds: [], customBrandPersonality: "", heroImageryDirectionId: null, customHeroImageryDirection: "", style: { ...defaultStyle }, }; }; interface BuilderState { form: BuilderFormData; currentStep: number; isSubmitting: boolean; isGenerating: boolean; isLoadingBlueprint: boolean; metadata?: SiteBuilderMetadata; isMetadataLoading: boolean; metadataError?: string; error?: string; activeBlueprint?: SiteBlueprint; pages: PageBlueprint[]; selectedPageIds: number[]; generationProgress?: { pagesQueued: number; taskIds: number[]; celeryTaskId?: string; }; // Actions setField: ( key: K, value: BuilderFormData[K], ) => void; updateStyle: (partial: Partial) => void; addObjective: (value: string) => void; removeObjective: (index: number) => void; setStep: (step: number) => void; nextStep: () => void; previousStep: () => void; reset: () => void; syncContextFromStores: () => void; submitWizard: () => Promise; refreshPages: (blueprintId: number) => Promise; togglePageSelection: (pageId: number) => void; selectAllPages: () => void; clearPageSelection: () => void; loadBlueprint: (blueprintId: number) => Promise; generateAllPages: (blueprintId: number, force?: boolean) => Promise; loadMetadata: () => Promise; } export const useBuilderStore = create((set, get) => ({ form: buildDefaultForm(), currentStep: 0, isSubmitting: false, isGenerating: false, isLoadingBlueprint: false, metadata: undefined, isMetadataLoading: false, metadataError: undefined, 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; set((state) => ({ form: { ...state.form, siteId: site?.id ?? state.form.siteId, siteName: site?.name ?? state.form.siteName, sectorIds: site?.selected_sectors ?? state.form.sectorIds, industry: site?.industry_name ?? state.form.industry, }, })); }, submitWizard: async () => { const { form, metadata } = get(); if (!form.siteId) { set({ error: "Select an active site before running the Site Builder wizard.", }); return; } if (!form.sectorIds?.length) { set({ error: "This site has no sectors configured. Add sectors in Sites → All Sites before running the wizard.", }); return; } const findOptionName = ( options: SiteBuilderMetadataOption[] | undefined, id: number | null | undefined, ) => options?.find((option) => option.id === id)?.name; const businessTypeName = findOptionName(metadata?.business_types, form.businessTypeId) || form.customBusinessType?.trim() || form.businessType || "General business"; const selectedAudienceOptions = metadata?.audience_profiles?.filter((option) => form.targetAudienceIds.includes(option.id), ) ?? []; const audienceNames = selectedAudienceOptions.map((option) => option.name); if (form.customTargetAudience?.trim()) { audienceNames.push(form.customTargetAudience.trim()); } const targetAudienceSummary = audienceNames.join(", "); const selectedBrandPersonalities = metadata?.brand_personalities?.filter((option) => form.brandPersonalityIds.includes(option.id), ) ?? []; const brandPersonalityNames = selectedBrandPersonalities.map( (option) => option.name, ); if (form.customBrandPersonality?.trim()) { brandPersonalityNames.push(form.customBrandPersonality.trim()); } const personalityDescription = brandPersonalityNames.length > 0 ? brandPersonalityNames.join(", ") : form.style.personality; const heroImageryName = findOptionName( metadata?.hero_imagery_directions, form.heroImageryDirectionId, ) || form.customHeroImageryDirection?.trim() || form.style.heroImagery; const preparedForm: BuilderFormData = { ...form, businessType: businessTypeName, targetAudience: targetAudienceSummary, }; const stylePreferences: StylePreferences = { ...preparedForm.style, personality: personalityDescription, heroImagery: heroImageryName, }; set({ form: { ...preparedForm, style: stylePreferences }, isSubmitting: true, error: undefined, }); try { let lastBlueprint: SiteBlueprint | undefined; let lastStructure: SiteStructure | undefined; for (const sectorId of preparedForm.sectorIds) { const payload = { name: preparedForm.siteName || `Site Blueprint (${preparedForm.industry || "New"})`, description: targetAudienceSummary ? `${businessTypeName} • ${targetAudienceSummary}` : businessTypeName, site_id: preparedForm.siteId!, sector_id: sectorId, hosting_type: preparedForm.hostingType, config_json: { business_type_id: preparedForm.businessTypeId, business_type: businessTypeName, custom_business_type: preparedForm.customBusinessType, industry: preparedForm.industry, target_audience_ids: preparedForm.targetAudienceIds, target_audience: audienceNames, custom_target_audience: preparedForm.customTargetAudience, brand_personality_ids: preparedForm.brandPersonalityIds, brand_personality: brandPersonalityNames, custom_brand_personality: preparedForm.customBrandPersonality, hero_imagery_direction_id: preparedForm.heroImageryDirectionId, hero_imagery_direction: heroImageryName, custom_hero_imagery_direction: preparedForm.customHeroImageryDirection, sector_id: sectorId, }, }; const blueprint = await siteBuilderApi.createBlueprint(payload); lastBlueprint = blueprint; const generation = await siteBuilderApi.generateStructure( blueprint.id, { business_brief: preparedForm.businessBrief, objectives: preparedForm.objectives, style: stylePreferences, metadata: { targetAudience: audienceNames, brandPersonality: brandPersonalityNames, sectorId, }, }, ); if (generation?.structure) { lastStructure = generation.structure; } } if (lastBlueprint) { set({ activeBlueprint: lastBlueprint }); if (lastStructure) { useSiteDefinitionStore.getState().setStructure(lastStructure); } await get().refreshPages(lastBlueprint.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: [] }), loadBlueprint: async (blueprintId: number) => { set({ isLoadingBlueprint: true, error: undefined }); try { const [blueprint, pages] = await Promise.all([ siteBuilderApi.getBlueprint(blueprintId), siteBuilderApi.listPages(blueprintId), ]); set({ activeBlueprint: blueprint, pages, selectedPageIds: [], }); if (blueprint.structure_json) { useSiteDefinitionStore.getState().setStructure(blueprint.structure_json); } else { useSiteDefinitionStore.getState().setStructure({ site: undefined, pages: pages.map((page) => ({ slug: page.slug, title: page.title, type: page.type, blocks: page.blocks_json, })), }); } useSiteDefinitionStore.getState().setPages(pages); } catch (error: any) { set({ error: error?.message || "Unable to load blueprint", }); } finally { set({ isLoadingBlueprint: false }); } }, 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 }); } }, loadMetadata: async () => { if (get().metadata || get().isMetadataLoading) return; set({ isMetadataLoading: true, metadataError: undefined }); try { const metadata = await siteBuilderApi.getMetadata(); set({ metadata, isMetadataLoading: false }); } catch (error: any) { set({ metadataError: error?.message || "Unable to load Site Builder metadata", isMetadataLoading: false, }); } }, }));