diff --git a/apps/builder/app/builder/features/ai/ai-command-bar.tsx b/apps/builder/app/builder/features/ai/ai-command-bar.tsx index 090cb17aac56..48e82eb19612 100644 --- a/apps/builder/app/builder/features/ai/ai-command-bar.tsx +++ b/apps/builder/app/builder/features/ai/ai-command-bar.tsx @@ -1,4 +1,3 @@ -/* eslint-disable import/no-internal-modules */ import { formatDistance } from "date-fns/formatDistance"; import { AutogrowTextArea, diff --git a/apps/builder/app/builder/features/inspector/inspector.tsx b/apps/builder/app/builder/features/inspector/inspector.tsx index c39c9284f33b..f11ea17b8778 100644 --- a/apps/builder/app/builder/features/inspector/inspector.tsx +++ b/apps/builder/app/builder/features/inspector/inspector.tsx @@ -25,6 +25,7 @@ import { $registeredComponentMetas, $dragAndDropState, $inspectorLastInputTime, + $selectedPage, } from "~/shared/nano-states"; import { NavigatorTree } from "~/builder/shared/navigator-tree"; import type { Settings } from "~/builder/shared/client-settings"; @@ -82,6 +83,7 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => { const [tab, setTab] = useState("style"); const isDragging = useStore($isDragging); const metas = useStore($registeredComponentMetas); + const selectedPage = useStore($selectedPage); if (navigatorLayout === "docked" && isDragging) { return ; @@ -101,7 +103,9 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => { } const meta = metas.get(selectedInstance.component); - const isStyleTabVisible = meta?.stylable ?? true; + const documentType = selectedPage?.meta.documentType ?? "html"; + + const isStyleTabVisible = documentType === "html" && (meta?.stylable ?? true); const availableTabs = [ isStyleTabVisible ? "style" : undefined, diff --git a/apps/builder/app/builder/features/project-settings/section-general.tsx b/apps/builder/app/builder/features/project-settings/section-general.tsx index f489cdb84ff8..3a18d7d79555 100644 --- a/apps/builder/app/builder/features/project-settings/section-general.tsx +++ b/apps/builder/app/builder/features/project-settings/section-general.tsx @@ -1,3 +1,5 @@ +import { z } from "zod"; +import { useId, useState } from "react"; import { useStore } from "@nanostores/react"; import { Grid, @@ -10,16 +12,21 @@ import { CheckboxAndLabel, Checkbox, css, + Flex, + Tooltip, + InputErrorsTooltip, + ProBadge, } from "@webstudio-is/design-system"; +import { InfoCircleIcon } from "@webstudio-is/icons"; import { ImageControl } from "./image-control"; -import { $assets, $imageLoader, $pages } from "~/shared/nano-states"; import { Image } from "@webstudio-is/image"; -import { useIds } from "~/shared/form-utils"; import type { ProjectMeta, CompilerSettings } from "@webstudio-is/sdk"; -import { useState } from "react"; +import { $assets, $imageLoader, $pages } from "~/shared/nano-states"; +import { useIds } from "~/shared/form-utils"; import { serverSyncStore } from "~/shared/sync"; import { sectionSpacing } from "./utils"; import { CodeEditor } from "~/builder/shared/code-editor"; +import { $userPlanFeatures } from "~/builder/shared/nano-states"; const imgStyle = css({ width: 72, @@ -32,15 +39,25 @@ const imgStyle = css({ const defaultMetaSettings: ProjectMeta = { siteName: "", + contactEmail: "", faviconAssetId: "", code: "", }; +const Email = z.string().email(); + export const SectionGeneral = () => { + const { allowContactEmail } = useStore($userPlanFeatures); const [meta, setMeta] = useState( () => $pages.get()?.meta ?? defaultMetaSettings ); - const ids = useIds(["siteName"]); + const siteNameId = useId(); + const contactEmailId = useId(); + const contactEmailError = + (meta.contactEmail ?? "").trim().length === 0 || + Email.safeParse(meta.contactEmail).success + ? undefined + : "Contact email is invalid."; const assets = useStore($assets); const asset = assets.get(meta.faviconAssetId ?? ""); const favIconUrl = asset ? `${asset.name}` : undefined; @@ -65,21 +82,59 @@ export const SectionGeneral = () => { }; return ( - <> + + + General + + - General - + + + + + + { handleSave("siteName")(event.target.value); }} - placeholder="Current Site Name" - autoFocus /> + + + + + + + {allowContactEmail === false && Pro} + + + { + handleSave("contactEmail")(event.target.value); + }} + /> + + + @@ -118,7 +173,7 @@ export const SectionGeneral = () => { - + ); }; diff --git a/apps/builder/app/builder/features/project-settings/section-marketplace.tsx b/apps/builder/app/builder/features/project-settings/section-marketplace.tsx index 617c972fa1f7..50016b975d3c 100644 --- a/apps/builder/app/builder/features/project-settings/section-marketplace.tsx +++ b/apps/builder/app/builder/features/project-settings/section-marketplace.tsx @@ -167,9 +167,11 @@ export const SectionMarketplace = () => { }; return ( - <> + + + Marketplace + - Marketplace { )} - + ); }; diff --git a/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx b/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx index 49ee8484a6c4..ac79cfbdf43e 100644 --- a/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx +++ b/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx @@ -1,7 +1,9 @@ import { useState } from "react"; import { useStore } from "@nanostores/react"; +import { matchSorter } from "match-sorter"; import type { Instance } from "@webstudio-is/sdk"; import { theme, Combobox, Separator, Flex } from "@webstudio-is/design-system"; +import { descendantComponent } from "@webstudio-is/react-sdk"; import { $propValuesByInstanceSelector, $propsIndex, @@ -17,7 +19,6 @@ import { } from "./use-props-logic"; import { Row } from "../shared"; import { serverSyncStore } from "~/shared/sync"; -import { matchSorter } from "match-sorter"; const itemToString = (item: NameAndLabel | null) => item?.label || item?.name || ""; @@ -222,11 +223,16 @@ export const PropsSectionContainer = ({ } return ( - +
+ +
); }; diff --git a/apps/builder/app/builder/features/settings-panel/props-section/use-props-logic.ts b/apps/builder/app/builder/features/settings-panel/props-section/use-props-logic.ts index 65c7f1f420b0..f3cc73c4c2da 100644 --- a/apps/builder/app/builder/features/settings-panel/props-section/use-props-logic.ts +++ b/apps/builder/app/builder/features/settings-panel/props-section/use-props-logic.ts @@ -151,11 +151,12 @@ export const usePropsLogic = ({ const instanceMeta = useStore($registeredComponentMetas).get( instance.component ); - const meta = useStore($registeredComponentPropsMetas).get(instance.component); - - if (meta === undefined) { - throw new Error(`Could not get meta for component "${instance.component}"`); - } + const meta = useStore($registeredComponentPropsMetas).get( + instance.component + ) ?? { + props: {}, + initialProps: [], + }; const savedProps = props; diff --git a/apps/builder/app/builder/features/sidebar-left/panels/components/components.tsx b/apps/builder/app/builder/features/sidebar-left/panels/components/components.tsx index 09dde21755c2..86670b119953 100644 --- a/apps/builder/app/builder/features/sidebar-left/panels/components/components.tsx +++ b/apps/builder/app/builder/features/sidebar-left/panels/components/components.tsx @@ -22,13 +22,17 @@ import { useDraggable, } from "./use-draggable"; import { MetaIcon } from "~/builder/shared/meta-icon"; -import { $registeredComponentMetas } from "~/shared/nano-states"; +import { $registeredComponentMetas, $selectedPage } from "~/shared/nano-states"; import { getMetaMaps } from "./get-meta-maps"; import { getInstanceLabel } from "~/shared/instance-utils"; import { isFeatureEnabled } from "@webstudio-is/feature-flags"; export const TabContent = ({ publish, onSetActiveTab }: TabContentProps) => { const metaByComponentName = useStore($registeredComponentMetas); + const selectedPage = useStore($selectedPage); + + const documentType = selectedPage?.meta.documentType ?? "html"; + const { metaByCategory, componentNamesByMeta } = useMemo( () => getMetaMaps(metaByComponentName), [metaByComponentName] @@ -51,9 +55,18 @@ export const TabContent = ({ publish, onSetActiveTab }: TabContentProps) => { return false; } + // Only xml category is allowed for xml document type + if (documentType === "xml") { + return category === "xml"; + } + // Hide xml category for non-xml document types + if (category === "xml") { + return false; + } + if ( - isFeatureEnabled("xmlElement") === false && - category === "xml" + isFeatureEnabled("internalComponents") === false && + category === "internal" ) { return false; } @@ -80,6 +93,12 @@ export const TabContent = ({ publish, onSetActiveTab }: TabContentProps) => { ) { return; } + if ( + isFeatureEnabled("cms") === false && + component === "ContentEmbed" + ) { + return; + } return ( “{values.name}” is the home page @@ -664,6 +669,7 @@ const FormFields = ({ css={{ overflowWrap: "anywhere", wordBreak: "break-all", + my: 2, }} color="subtle" > @@ -671,6 +677,20 @@ const FormFields = ({ to set it as your home page + ) : values.documentType === "xml" ? ( + <> + + + XML pages cannot be set as the home page + + ) : ( <> )} + + {isFeatureEnabled("xmlElement") && ( + + +