diff --git a/apps/designer/app/designer/features/sidebar-left/panels/pages/pages.tsx b/apps/designer/app/designer/features/sidebar-left/panels/pages/pages.tsx index 4f3feb9dbb..0702357926 100644 --- a/apps/designer/app/designer/features/sidebar-left/panels/pages/pages.tsx +++ b/apps/designer/app/designer/features/sidebar-left/panels/pages/pages.tsx @@ -102,7 +102,7 @@ const Pages = ({ actions={ <> {onNewPage && ( - + )} diff --git a/apps/designer/app/designer/features/sidebar-left/panels/pages/settings.tsx b/apps/designer/app/designer/features/sidebar-left/panels/pages/settings.tsx index 8da1ac629a..08ac6ba4d9 100644 --- a/apps/designer/app/designer/features/sidebar-left/panels/pages/settings.tsx +++ b/apps/designer/app/designer/features/sidebar-left/panels/pages/settings.tsx @@ -23,7 +23,7 @@ const Group = styled(Flex, { defaultVariants: { direction: "column" }, }); -export type PutPageData = +export type CreatePageData = | { ok: true; page: Page } | { errors: string | ZodError["formErrors"] }; @@ -67,7 +67,7 @@ export const NewPageSettings = ({ const nameFieldId = useId(); const pathFieldId = useId(); - const fetcher = useFetcher(); + const fetcher = useFetcher(); const [fieldErrors, setFieldErrors] = useState< ZodError["formErrors"]["fieldErrors"] @@ -107,7 +107,7 @@ export const NewPageSettings = ({ actions={ <> {onClose && ( - + )} diff --git a/apps/designer/app/routes/rest/pages.$projectId.ts b/apps/designer/app/routes/rest/pages.$projectId.ts index de3913590d..6d60c423d0 100644 --- a/apps/designer/app/routes/rest/pages.$projectId.ts +++ b/apps/designer/app/routes/rest/pages.$projectId.ts @@ -3,7 +3,7 @@ import { db } from "@webstudio-is/project/index.server"; import { type Pages, utils } from "@webstudio-is/project"; import { zfd } from "zod-form-data"; import { z } from "zod"; -import { PutPageData } from "~/designer/features/sidebar-left/panels/pages/settings"; +import { CreatePageData } from "~/designer/features/sidebar-left/panels/pages/settings"; import { sentryException } from "~/shared/sentry"; const nonEmptyString = z @@ -33,7 +33,7 @@ const CreatePageInput = zfd.formData({ const handlePUT = async ( projectId: string, request: Request -): Promise => { +): Promise => { const result = CreatePageInput.safeParse(await request.formData()); if (result.success === false) { return { errors: result.error.formErrors }; @@ -72,7 +72,7 @@ export const action: ActionFunction = async ({ request, params }) => { } if (request.method.toLowerCase() === "put") { - return handlePUT(params.projectId, request); + await handlePUT(params.projectId, request); } throw new Error(`Method ${request.method} is not supported`); diff --git a/packages/project/src/db/types.ts b/packages/project/src/db/types.ts index 3983b91e4d..8ac459a815 100644 --- a/packages/project/src/db/types.ts +++ b/packages/project/src/db/types.ts @@ -9,21 +9,50 @@ export type Project = Omit & { assets?: Array; }; -export const PageSchema = z.object({ +const commonPageFields = { id: z.string(), - name: z.string(), - path: z.string(), + name: z.string().refine((val) => val !== "", "Can't be empty"), title: z.string(), meta: z.record(z.string(), z.string()), treeId: z.string(), +} as const; + +const HomePageSchema = z.object({ + ...commonPageFields, + path: z + .string() + .refine((path) => path === "", "Home page path must be empty"), +}); + +const PageSchema = z.object({ + ...commonPageFields, + path: z + .string() + .refine((path) => path !== "" && path !== "/", "Can't be empty") + .refine((path) => path.startsWith("/"), "Must start with a /") + .refine((path) => path.endsWith("/") === false, "Can't end with a /") + .refine( + (path) => path.includes("//") === false, + "Can't contain repeating /" + ) + .refine( + (path) => /^[-_a-z0-9\\/]*$/.test(path), + "Only a-z, 0-9, -, _ and / are allowed" + ), }); export type Page = z.infer; export const PagesSchema: z.ZodType<{ homePage: Page; pages: Array }> = z.object({ - homePage: PageSchema, - pages: z.array(PageSchema), + homePage: HomePageSchema, + pages: z + .array(PageSchema) + .refine( + (array) => + new Set(array.map((page) => page.path)).size === array.length, + "All paths must be unique" + ), }); export type Pages = z.infer;