Add Puck site builder with preview and draft functionality

Frontend:
- Add comprehensive Puck component library (Layout, Content, Booking, Contact)
- Add Services component with usePublicServices hook integration
- Add 150+ icons to IconList component organized by category
- Add preview modal with viewport toggles (desktop/tablet/mobile)
- Add draft save/discard functionality with localStorage persistence
- Add draft status indicator in PageEditor toolbar
- Fix useSites hooks to use correct API URLs (/pages/{id}/)

Backend:
- Add SiteConfig model for theme, header, footer configuration
- Add Page SEO fields (meta_title, meta_description, og_image, etc.)
- Add puck_data validation for component structure
- Add create_missing_sites management command
- Fix PageViewSet to use EntitlementService for permissions
- Add comprehensive tests for site builder functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-13 01:32:11 -05:00
parent 41caccd31a
commit 29bcb27e76
40 changed files with 6626 additions and 45 deletions

View File

@@ -25,7 +25,7 @@ export const usePage = (pageId: string) => {
return useQuery({
queryKey: ['page', pageId],
queryFn: async () => {
const response = await api.get(`/sites/me/pages/${pageId}/`);
const response = await api.get(`/pages/${pageId}/`);
return response.data;
},
enabled: !!pageId,
@@ -36,7 +36,7 @@ export const useUpdatePage = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, data }: { id: string; data: any }) => {
const response = await api.patch(`/sites/me/pages/${id}/`, data);
const response = await api.patch(`/pages/${id}/`, data);
return response.data;
},
onSuccess: (data, variables) => {
@@ -63,7 +63,7 @@ export const useDeletePage = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (id: string) => {
await api.delete(`/sites/me/pages/${id}/`);
await api.delete(`/pages/${id}/`);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['pages'] });
@@ -81,3 +81,41 @@ export const usePublicPage = () => {
retry: false,
});
};
export const useSiteConfig = () => {
return useQuery({
queryKey: ['siteConfig'],
queryFn: async () => {
const response = await api.get('/sites/me/config/');
return response.data;
},
});
};
export const useUpdateSiteConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (data: {
theme?: Record<string, unknown>;
header?: Record<string, unknown>;
footer?: Record<string, unknown>;
}) => {
const response = await api.patch('/sites/me/config/', data);
return response.data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['siteConfig'] });
},
});
};
export const usePublicSiteConfig = () => {
return useQuery({
queryKey: ['publicSiteConfig'],
queryFn: async () => {
const response = await api.get('/public/site-config/');
return response.data;
},
retry: false,
});
};