diff --git a/apps/builder/app/builder/features/pages/page-settings.tsx b/apps/builder/app/builder/features/pages/page-settings.tsx index d0c51da0a7f9..8c868b08a322 100644 --- a/apps/builder/app/builder/features/pages/page-settings.tsx +++ b/apps/builder/app/builder/features/pages/page-settings.tsx @@ -366,13 +366,11 @@ const StatusField = ({ onChange: (value: undefined | string) => void; }) => { const id = useId(); - const { allowDynamicData } = useStore($userPlanFeatures); const { variableValues, scope, aliases } = useStore($pageRootScope); return ( - {allowDynamicData === false && PRO} @@ -801,7 +799,7 @@ const FormFields = ({ {allowDynamicData === false && ( - Dynamic routing, redirect and status code are a part of the CMS + Dynamic routing and redirect are a part of the CMS functionality. diff --git a/apps/builder/app/builder/features/topbar/publish.tsx b/apps/builder/app/builder/features/topbar/publish.tsx index 1b8debca6db2..4b2527d328f3 100644 --- a/apps/builder/app/builder/features/topbar/publish.tsx +++ b/apps/builder/app/builder/features/topbar/publish.tsx @@ -263,12 +263,10 @@ const $usedProFeatures = computed( pageId: page.id, instanceSelector: [page.rootInstanceId], }; - if (isPathnamePattern(page.path)) { + // allow catch all for 404 pages on free plan + if (isPathnamePattern(page.path) && page.path !== "/*") { features.set("Dynamic path", { awareness, view: "pageSettings" }); } - if (page.meta.status && page.meta.status !== `200`) { - features.set("Page status code", { awareness, view: "pageSettings" }); - } if (page.meta.redirect && page.meta.redirect !== `""`) { features.set("Redirect", { awareness, view: "pageSettings" }); } diff --git a/packages/project-build/package.json b/packages/project-build/package.json index 975a14bd91d1..c6639494aef8 100644 --- a/packages/project-build/package.json +++ b/packages/project-build/package.json @@ -24,6 +24,7 @@ "@webstudio-is/authorization-token": "workspace:*", "@webstudio-is/postrest": "workspace:*", "@webstudio-is/sdk": "workspace:*", + "@webstudio-is/template": "workspace:*", "@webstudio-is/trpc-interface": "workspace:*", "nanoid": "^5.1.5", "zod": "^3.24.2" diff --git a/packages/project-build/src/db/build.ts b/packages/project-build/src/db/build.ts index 33037b0c3f09..15fa7b521ef9 100644 --- a/packages/project-build/src/db/build.ts +++ b/packages/project-build/src/db/build.ts @@ -1,6 +1,5 @@ /* eslint no-console: ["error", { allow: ["time", "timeEnd"] }] */ -import { nanoid } from "nanoid"; import type { Database } from "@webstudio-is/postrest/index.server"; import { AuthorizationError, @@ -8,26 +7,25 @@ import { type AppContext, } from "@webstudio-is/trpc-interface/index.server"; import { db as authDb } from "@webstudio-is/authorization-token/index.server"; -import { - type Deployment, - type Resource, - type StyleSource, - type Prop, - type DataSource, - type Instance, - type Breakpoint, - type StyleSourceSelection, - type StyleDecl, +import type { + Deployment, + Resource, + StyleSource, + Prop, + DataSource, + Instance, + Breakpoint, + StyleSourceSelection, + StyleDecl, Pages, - initialBreakpoints, - elementComponent, } from "@webstudio-is/sdk"; import type { Build, CompactBuild } from "../types"; import { parseDeployment } from "./deployment"; -import { serializePages } from "./pages"; -import { createDefaultPages } from "../shared/pages-utils"; import type { MarketplaceProduct } from "../shared//marketplace"; import { breakCyclesMutable } from "../shared/graph-utils"; +import { createPages } from "../template"; +import { serializeStyles } from "./styles"; +import { serializeStyleSourceSelections } from "./style-source-selections"; const parseCompactData = (serialized: string) => JSON.parse(serialized) as Item[]; @@ -222,35 +220,6 @@ export const loadApprovedProdBuildByProjectId = async ( return parseCompactBuild(build.data[0]); }; -const createNewPageInstances = (): Build["instances"] => { - const instanceId = nanoid(); - return [ - [ - instanceId, - { - type: "instance", - id: instanceId, - component: elementComponent, - tag: "body", - children: [], - }, - ], - ]; -}; - -const createInitialBreakpoints = (): [Breakpoint["id"], Breakpoint][] => { - return initialBreakpoints.map((breakpoint) => { - const id = nanoid(); - return [ - id, - { - ...breakpoint, - id, - }, - ]; - }); -}; - /* * We create "dev" build in two cases: * 1. when we create a new project @@ -263,16 +232,21 @@ export const createBuild = async ( }, context: AppContext ): Promise => { - const newInstances = createNewPageInstances(); - const [rootInstanceId] = newInstances[0]; - const defaultPages = createDefaultPages({ rootInstanceId }); - + const data = createPages(); const newBuild = await context.postgrest.client.from("Build").insert({ id: crypto.randomUUID(), projectId: props.projectId, - pages: serializePages(defaultPages), - breakpoints: serializeData(new Map(createInitialBreakpoints())), - instances: serializeData(new Map(newInstances)), + pages: serializeConfig(data.pages), + breakpoints: serializeData(data.breakpoints), + styles: serializeStyles(data.styles), + styleSources: serializeData(data.styleSources), + styleSourceSelections: serializeStyleSourceSelections( + data.styleSourceSelections + ), + props: serializeData(data.props), + dataSources: serializeData(data.dataSources), + resources: serializeData(data.resources), + instances: serializeData(data.instances), }); if (newBuild.error) { throw newBuild.error; diff --git a/packages/project-build/src/template.tsx b/packages/project-build/src/template.tsx new file mode 100644 index 000000000000..d89fa514c440 --- /dev/null +++ b/packages/project-build/src/template.tsx @@ -0,0 +1,129 @@ +import { nanoid } from "nanoid"; +import { + initialBreakpoints, + type Pages, + type WebstudioData, +} from "@webstudio-is/sdk"; +import { coreTemplates } from "@webstudio-is/sdk/core-templates"; +import { css, renderData, ws } from "@webstudio-is/template"; +import { createRootFolder } from "./shared/pages-utils"; + +export const createPages = (): WebstudioData => { + const breakpoints = initialBreakpoints.map((breakpoint) => ({ + ...breakpoint, + id: nanoid(), + })); + const homePageId = nanoid(); + const homeBodyId = nanoid(); + const notFoundPageId = nanoid(); + const notFoundBodyId = nanoid(); + + const data = renderData( + <> + {/* home page body */} + + {/* not found page body */} + + + + 404 + + 404 + + + 404 + + + + + PAGE NOT FOUND + + + {coreTemplates.builtWithWebstudio.template} + + , + nanoid, + breakpoints + ); + + const pages: Pages = { + homePage: { + id: homePageId, + name: "Home", + path: "", + title: `"Home"`, + meta: {}, + rootInstanceId: homeBodyId, + }, + pages: [ + { + id: notFoundPageId, + name: "404", + path: "/*", + title: `"Page not found"`, + meta: { + status: `404`, + excludePageFromSearch: "false", + }, + rootInstanceId: notFoundBodyId, + }, + ], + folders: [createRootFolder([homePageId, notFoundPageId])], + }; + + return { ...data, pages }; +}; + +createPages(); diff --git a/packages/template/src/jsx.ts b/packages/template/src/jsx.ts index 9a259be41de8..a55e6af1c8b7 100644 --- a/packages/template/src/jsx.ts +++ b/packages/template/src/jsx.ts @@ -122,11 +122,12 @@ const getElementChildren = (element: JSX.Element): JSX.Element[] => { export const renderTemplate = ( root: JSX.Element, - generateId?: () => string + generateId?: () => string, + initialBreakpoints: Breakpoint[] = [] ): WebstudioFragment => { const instances: Instance[] = []; const props: Prop[] = []; - const breakpoints: Breakpoint[] = []; + const breakpoints = Array.from(initialBreakpoints); const styleSources: StyleSource[] = []; const styleSourceSelections: StyleSourceSelection[] = []; const styles: StyleDecl[] = []; @@ -417,7 +418,8 @@ export const renderTemplate = ( export const renderData = ( root: JSX.Element, - generateId?: () => string + generateId?: () => string, + initialBreakpoints: Breakpoint[] = [] ): Omit => { const { instances, @@ -429,7 +431,7 @@ export const renderData = ( dataSources, resources, assets, - } = renderTemplate(root, generateId); + } = renderTemplate(root, generateId, initialBreakpoints); return { instances: new Map(instances.map((item) => [item.id, item])), props: new Map(props.map((item) => [item.id, item])), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45ade6760091..f62dae6c0cf2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1817,6 +1817,9 @@ importers: '@webstudio-is/sdk': specifier: workspace:* version: link:../sdk + '@webstudio-is/template': + specifier: workspace:* + version: link:../template '@webstudio-is/trpc-interface': specifier: workspace:* version: link:../trpc-interface