From d8c6687feec5b281b9b70102a4bf757083f240e6 Mon Sep 17 00:00:00 2001 From: Xavier Rutayisire Date: Mon, 29 Jan 2024 17:28:49 +0100 Subject: [PATCH] refactor(custom types): Remove createCustomTypeSaga --- .../CreateCustomTypeModal.tsx | 99 ++++++---- .../RenameCustomTypeModal.tsx | 4 +- .../components/Navigation/ChangesListItem.tsx | 46 ++--- .../src/domain/__tests__/customType.test.ts | 179 ++++++++++++++++++ .../slice-machine/src/domain/customType.ts | 112 ++++++++++- .../customTypes/PageTypeOnboarding.tsx | 47 ++--- .../__tests__/createCustomType.test.ts | 159 ---------------- .../actions/convertCustomToPageType.ts | 24 ++- .../customTypes/actions/createCustomType.ts | 69 +++++++ .../customTypes/actions/deleteCustomType.ts | 16 +- .../customTypes/actions/renameCustomType.ts | 14 +- .../CustomTypesBuilderPage.tsx | 4 +- .../customTypesTable/CustomTypesTable.tsx | 16 +- .../customTypesTable/CustomTypesTablePage.tsx | 35 ++-- .../customTypesTable/createCustomType.ts | 107 ----------- .../src/modules/availableCustomTypes/index.ts | 154 ++------------- .../src/modules/loading/types.ts | 3 - .../slice-machine/src/modules/modal/types.ts | 1 - .../src/modules/useSliceMachineActions.ts | 148 +++++++-------- packages/slice-machine/src/redux/saga.ts | 2 - .../components/CreateCustomTypeModal.test.tsx | 73 ------- .../availableCustomTypes/index.test.ts | 160 +--------------- 22 files changed, 618 insertions(+), 854 deletions(-) delete mode 100644 packages/slice-machine/src/features/customTypes/__tests__/createCustomType.test.ts create mode 100644 packages/slice-machine/src/features/customTypes/actions/createCustomType.ts delete mode 100644 packages/slice-machine/src/features/customTypes/customTypesTable/createCustomType.ts delete mode 100644 packages/slice-machine/test/components/CreateCustomTypeModal.test.tsx diff --git a/packages/slice-machine/components/Forms/CreateCustomTypeModal/CreateCustomTypeModal.tsx b/packages/slice-machine/components/Forms/CreateCustomTypeModal/CreateCustomTypeModal.tsx index 2a24db6528..64a3be4d18 100644 --- a/packages/slice-machine/components/Forms/CreateCustomTypeModal/CreateCustomTypeModal.tsx +++ b/packages/slice-machine/components/Forms/CreateCustomTypeModal/CreateCustomTypeModal.tsx @@ -2,25 +2,28 @@ import { SetStateAction, useState } from "react"; import { Box } from "theme-ui"; import { FormikErrors } from "formik"; import { useSelector } from "react-redux"; +import { useRouter } from "next/router"; import ModalFormCard from "@components/ModalFormCard"; -import { InputBox } from "../components/InputBox"; -import { SelectRepeatable } from "../components/SelectRepeatable"; import useSliceMachineActions from "@src/modules/useSliceMachineActions"; import { SliceMachineStoreType } from "@src/redux/type"; import { selectAllCustomTypeIds, selectAllCustomTypeLabels, } from "@src/modules/availableCustomTypes"; -import { isModalOpen } from "@src/modules/modal"; -import { ModalKeysEnum } from "@src/modules/modal/types"; -import { isLoading } from "@src/modules/loading"; -import { LoadingKeysEnum } from "@src/modules/loading/types"; -import { telemetry } from "@src/apiClient"; import { slugify } from "@lib/utils/str"; import { API_ID_REGEX } from "@lib/consts"; import type { CustomTypeFormat } from "@slicemachine/manager"; import { CUSTOM_TYPES_MESSAGES } from "@src/features/customTypes/customTypesMessages"; +import { + CustomTypeOrigin, + createCustomType, +} from "@src/features/customTypes/actions/createCustomType"; +import { CUSTOM_TYPES_CONFIG } from "@src/features/customTypes/customTypesConfig"; +import { getFormat } from "@src/domain/customType"; + +import { InputBox } from "../components/InputBox"; +import { SelectRepeatable } from "../components/SelectRepeatable"; interface FormValues { id: string; @@ -30,46 +33,64 @@ interface FormValues { type CreateCustomTypeModalProps = { format: CustomTypeFormat; - origin?: "onboarding" | "table"; + isCreating: boolean; + isOpen: boolean; + origin?: CustomTypeOrigin; + onCreateChange: (isCreating: boolean) => void; + onOpenChange: (isOpen: boolean) => void; }; export const CreateCustomTypeModal: React.FC = ({ format, + isCreating, + isOpen, origin = "table", + onCreateChange, + onOpenChange, }) => { - const { createCustomType, closeModals } = useSliceMachineActions(); - - const { - customTypeIds, - isCreateCustomTypeModalOpen, - isCreatingCustomType, - customTypeLabels, - } = useSelector((store: SliceMachineStoreType) => ({ - customTypeIds: selectAllCustomTypeIds(store), - customTypeLabels: selectAllCustomTypeLabels(store), - isCreateCustomTypeModalOpen: isModalOpen( - store, - ModalKeysEnum.CREATE_CUSTOM_TYPE, - ), - isCreatingCustomType: isLoading(store, LoadingKeysEnum.CREATE_CUSTOM_TYPE), - })); + const { createCustomTypeSuccess } = useSliceMachineActions(); + + const { customTypeIds, customTypeLabels } = useSelector( + (store: SliceMachineStoreType) => ({ + customTypeIds: selectAllCustomTypeIds(store), + customTypeLabels: selectAllCustomTypeLabels(store), + }), + ); const customTypesMessages = CUSTOM_TYPES_MESSAGES[format]; const [isIdFieldPristine, setIsIdFieldPristine] = useState(true); + const router = useRouter(); - const createCustomTypeAndTrack = ({ id, label, repeatable }: FormValues) => { - const name = label || id; + const onSubmit = async ({ id, label, repeatable }: FormValues) => { + onCreateChange(true); - void telemetry.track({ - event: "custom-type:created", - id, - name, + await createCustomType({ format, - type: repeatable ? "repeatable" : "single", + id, + label, origin, + repeatable, + onSuccess: async (newCustomType) => { + createCustomTypeSuccess(newCustomType); + + const format = getFormat(newCustomType); + const customTypesConfig = CUSTOM_TYPES_CONFIG[format]; + + setIsIdFieldPristine(true); + + await router.push({ + pathname: customTypesConfig.getBuilderPagePathname(id), + query: + newCustomType.format === "page" + ? { + newPageType: true, + } + : undefined, + }); + }, }); - createCustomType(id, name, repeatable, format); - closeModals(); - setIsIdFieldPristine(true); + + onCreateChange(false); + onOpenChange(false); }; const handleLabelChange = ( @@ -106,16 +127,18 @@ export const CreateCustomTypeModal: React.FC = ({ return ( { - closeModals(); + onOpenChange(false); setIsIdFieldPristine(true); }} - onSubmit={createCustomTypeAndTrack} - isLoading={isCreatingCustomType} + onSubmit={(values) => { + void onSubmit(values); + }} + isLoading={isCreating} initialValues={{ repeatable: true, id: "", diff --git a/packages/slice-machine/components/Forms/RenameCustomTypeModal/RenameCustomTypeModal.tsx b/packages/slice-machine/components/Forms/RenameCustomTypeModal/RenameCustomTypeModal.tsx index bd94408416..d59ec2322c 100644 --- a/packages/slice-machine/components/Forms/RenameCustomTypeModal/RenameCustomTypeModal.tsx +++ b/packages/slice-machine/components/Forms/RenameCustomTypeModal/RenameCustomTypeModal.tsx @@ -31,7 +31,7 @@ export const RenameCustomTypeModal: React.FC = ({ }) => { const customTypeName = customType?.label ?? ""; const customTypeId = customType?.id ?? ""; - const { renameAvailableCustomTypeSuccess } = useSliceMachineActions(); + const { renameCustomTypeSuccess } = useSliceMachineActions(); const [isRenaming, setIsRenaming] = useState(false); @@ -46,7 +46,7 @@ export const RenameCustomTypeModal: React.FC = ({ await renameCustomType({ model: customType, newLabel: values.customTypeName, - onSuccess: renameAvailableCustomTypeSuccess, + onSuccess: renameCustomTypeSuccess, }); } setIsRenaming(false); diff --git a/packages/slice-machine/components/Navigation/ChangesListItem.tsx b/packages/slice-machine/components/Navigation/ChangesListItem.tsx index e285dc1292..6c00f9ff8e 100644 --- a/packages/slice-machine/components/Navigation/ChangesListItem.tsx +++ b/packages/slice-machine/components/Navigation/ChangesListItem.tsx @@ -1,13 +1,9 @@ -import { type FC, useState } from "react"; +import { type FC } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { SliceMachineStoreType } from "@src/redux/type"; -import { useSelector } from "react-redux"; import { SideNavLink, SideNavListItem } from "@src/components/SideNav"; import { RadarIcon } from "@src/icons/RadarIcon"; -import { isLoading } from "@src/modules/loading"; -import { LoadingKeysEnum } from "@src/modules/loading/types"; import { HoverCard, HoverCardCloseButton, @@ -15,11 +11,8 @@ import { HoverCardMedia, HoverCardTitle, } from "@src/components/HoverCard"; -import { - userHasSeenChangesToolTip, - userHasSeenSimulatorToolTip, -} from "@src/modules/userContext"; import useSliceMachineActions from "@src/modules/useSliceMachineActions"; + import { ChangesRightElement } from "./ChangesRightElement"; export const ChangesListItem: FC = () => { @@ -63,30 +56,19 @@ export const ChangesListItem: FC = () => { ); }; +// TODO(DT-1925): Reactivate this feature const useOpenChangesHoverCard = () => { - const { - hasSeenChangesToolTip, - hasSeenSimulatorToolTip, - isSavingCustomType, - isSavingSlice, - } = useSelector((store: SliceMachineStoreType) => ({ - hasSeenChangesToolTip: userHasSeenChangesToolTip(store), - hasSeenSimulatorToolTip: userHasSeenSimulatorToolTip(store), - isSavingCustomType: isLoading(store, LoadingKeysEnum.SAVE_CUSTOM_TYPE), - isSavingSlice: isLoading(store, LoadingKeysEnum.SAVE_SLICE), - })); - - const isSaving = isSavingCustomType || isSavingSlice; - const [prevIsSaving, setPrevIsSaving] = useState(isSaving); + // const { hasSeenChangesToolTip, hasSeenSimulatorToolTip } = useSelector( + // (store: SliceMachineStoreType) => ({ + // hasSeenChangesToolTip: userHasSeenChangesToolTip(store), + // hasSeenSimulatorToolTip: userHasSeenSimulatorToolTip(store), + // }), + // ); - if (!prevIsSaving && isSaving) { - setPrevIsSaving(isSaving); - } + // return ( + // !hasSeenChangesToolTip && + // hasSeenSimulatorToolTip + // ); - return ( - !hasSeenChangesToolTip && - hasSeenSimulatorToolTip && - !isSaving && - prevIsSaving - ); + return false; }; diff --git a/packages/slice-machine/src/domain/__tests__/customType.test.ts b/packages/slice-machine/src/domain/__tests__/customType.test.ts index ac7fe2807a..031ea1a91f 100644 --- a/packages/slice-machine/src/domain/__tests__/customType.test.ts +++ b/packages/slice-machine/src/domain/__tests__/customType.test.ts @@ -782,4 +782,183 @@ describe("CustomTypeModel test suite", () => { }, }); }); + + it("should create a custom type with repeatable true", () => { + expect( + CustomTypeModel.create({ + id: "id", + label: "lama", + repeatable: true, + format: "custom", + }), + ).toMatchInlineSnapshot(` + { + "format": "custom", + "id": "id", + "json": { + "Main": { + "uid": { + "config": { + "label": "UID", + }, + "type": "UID", + }, + }, + }, + "label": "lama", + "repeatable": true, + "status": true, + } + `); + }); + + it("should create a custom type with repeatable false", () => { + const result = CustomTypeModel.create({ + id: "id", + label: "lama", + repeatable: false, + format: "custom", + }); + + expect(result.id).toBe("id"); + expect(result.label).toBe("lama"); + expect(result.format).toBe("custom"); + expect(result.json).toHaveProperty("Main"); + expect(result).toMatchInlineSnapshot(` + { + "format": "custom", + "id": "id", + "json": { + "Main": {}, + }, + "label": "lama", + "repeatable": false, + "status": true, + } + `); + }); + + it("should create a page type with a slice zone", () => { + const result = CustomTypeModel.create({ + id: "🥪", + label: "label", + repeatable: true, + format: "page", + }); + + expect(result.format).toBe("page"); + expect(result.json).toHaveProperty("Main"); + expect(result.json.Main).toHaveProperty("slices"); + expect(result.json).toHaveProperty("SEO & Metadata"); + expect(result).toMatchInlineSnapshot(` + { + "format": "page", + "id": "🥪", + "json": { + "Main": { + "slices": { + "config": { + "choices": {}, + }, + "fieldset": "Slice Zone", + "type": "Slices", + }, + "uid": { + "config": { + "label": "UID", + }, + "type": "UID", + }, + }, + "SEO & Metadata": { + "meta_description": { + "config": { + "label": "Meta Description", + "placeholder": "A brief summary of the page", + }, + "type": "Text", + }, + "meta_image": { + "config": { + "constraint": { + "height": 1260, + "width": 2400, + }, + "label": "Meta Image", + "thumbnails": [], + }, + "type": "Image", + }, + "meta_title": { + "config": { + "label": "Meta Title", + "placeholder": "A title of the page used for social media and search engines", + }, + "type": "Text", + }, + }, + }, + "label": "label", + "repeatable": true, + "status": true, + } + `); + }); +}); + +it("when non repeatable page type is should contain Main with a slice-zone, no uid, and a SEO tab", () => { + const result = CustomTypeModel.create({ + id: "foo", + label: "bar", + repeatable: false, + format: "page", + }); + + expect(result).toMatchInlineSnapshot(` + { + "format": "page", + "id": "foo", + "json": { + "Main": { + "slices": { + "config": { + "choices": {}, + }, + "fieldset": "Slice Zone", + "type": "Slices", + }, + }, + "SEO & Metadata": { + "meta_description": { + "config": { + "label": "Meta Description", + "placeholder": "A brief summary of the page", + }, + "type": "Text", + }, + "meta_image": { + "config": { + "constraint": { + "height": 1260, + "width": 2400, + }, + "label": "Meta Image", + "thumbnails": [], + }, + "type": "Image", + }, + "meta_title": { + "config": { + "label": "Meta Title", + "placeholder": "A title of the page used for social media and search engines", + }, + "type": "Text", + }, + }, + }, + "label": "bar", + "repeatable": false, + "status": true, + } + `); }); diff --git a/packages/slice-machine/src/domain/customType.ts b/packages/slice-machine/src/domain/customType.ts index a0ee62cfca..8684a61d84 100644 --- a/packages/slice-machine/src/domain/customType.ts +++ b/packages/slice-machine/src/domain/customType.ts @@ -104,8 +104,15 @@ type GetGroupFieldArgs = { groupFieldId: string; }; -export function getFormat(custom: CustomType): CustomTypeFormat { - return custom.format ?? "custom"; +type CreateArgs = { + format: CustomTypeFormat; + id: string; + label: string; + repeatable: boolean; +}; + +export function getFormat(customType: CustomType): CustomTypeFormat { + return customType.format ?? "custom"; } export function getSectionEntries( @@ -633,3 +640,104 @@ export function updateGroupFields( }, }; } + +export function create(args: CreateArgs) { + const { id, label, repeatable, format } = args; + const mainTab = makeMainTab(repeatable, format); + + return { + format, + id, + json: mainTab, + label, + repeatable, + status: true, + }; +} + +function makeMainTab( + repeatable: boolean, + format: CustomTypeFormat, +): CustomType["json"] { + if (repeatable === false && format === "page") { + return { ...DEFAULT_MAIN_WITH_SLICE_ZONE, ...DEFAULT_SEO_TAB }; + } + + if (repeatable === false) { + return DEFAULT_MAIN; + } + + if (format === "page") { + return { + ...DEFAULT_MAIN_WITH_UID_AND_SLICE_ZONE, + ...DEFAULT_SEO_TAB, + }; + } + + return DEFAULT_MAIN_WITH_UID; +} + +const DEFAULT_MAIN: CustomType["json"] = { + Main: {}, +}; + +const DEFAULT_MAIN_WITH_SLICE_ZONE: CustomType["json"] = { + Main: { + slices: { + config: { + choices: {}, + }, + fieldset: "Slice Zone", + type: "Slices", + }, + }, +}; + +const DEFAULT_MAIN_WITH_UID: CustomType["json"] = { + Main: { + uid: { + config: { + label: "UID", + }, + type: "UID", + }, + }, +}; + +const DEFAULT_MAIN_WITH_UID_AND_SLICE_ZONE: CustomType["json"] = { + Main: { + ...DEFAULT_MAIN_WITH_UID.Main, + ...DEFAULT_MAIN_WITH_SLICE_ZONE.Main, + }, +}; + +const DEFAULT_SEO_TAB: CustomType["json"] = { + "SEO & Metadata": { + meta_description: { + config: { + label: "Meta Description", + placeholder: "A brief summary of the page", + }, + type: "Text", + }, + meta_image: { + config: { + constraint: { + height: 1260, + width: 2400, + }, + label: "Meta Image", + thumbnails: [], + }, + type: "Image", + }, + meta_title: { + config: { + label: "Meta Title", + placeholder: + "A title of the page used for social media and search engines", + }, + type: "Text", + }, + }, +}; diff --git a/packages/slice-machine/src/features/customTypes/PageTypeOnboarding.tsx b/packages/slice-machine/src/features/customTypes/PageTypeOnboarding.tsx index 19b86a1988..4b7b1f31ef 100644 --- a/packages/slice-machine/src/features/customTypes/PageTypeOnboarding.tsx +++ b/packages/slice-machine/src/features/customTypes/PageTypeOnboarding.tsx @@ -1,35 +1,15 @@ -import { useEffect, useState } from "react"; -import { useSelector } from "react-redux"; +import { useState } from "react"; import { Box, Button, Text } from "@prismicio/editor-ui"; import { cache } from "@prismicio/editor-support/Suspense"; import { CreateCustomTypeModal } from "@components/Forms/CreateCustomTypeModal"; -import { SliceMachineStoreType } from "@src/redux/type"; -import useSliceMachineActions from "@src/modules/useSliceMachineActions"; -import { LoadingKeysEnum } from "@src/modules/loading/types"; -import { isLoading } from "@src/modules/loading"; import { BlankPageIcon } from "@src/icons/BlankPageIcon"; import { getIsEmptyProject } from "@src/hooks/useIsEmptyProject"; export function PageTypeOnboarding() { - const { openCreateCustomTypeModal } = useSliceMachineActions(); - const { isCreatingCustomType } = useSelector( - (store: SliceMachineStoreType) => ({ - isCreatingCustomType: isLoading( - store, - LoadingKeysEnum.CREATE_CUSTOM_TYPE, - ), - }), - ); - // State to ensure button loader is still visible while redirecting - const [isButtonLoading, setIsButtonLoading] = useState(false); - - useEffect(() => { - if (isCreatingCustomType) { - setIsButtonLoading(true); - cache.clear(getIsEmptyProject, []); - } - }, [isCreatingCustomType]); + const [isCreatingCustomType, setIsCreatingCustomType] = useState(false); + const [isCreateCustomTypeModalOpen, setIsCreateCustomTypeModalOpen] = + useState(false); return ( - - + { + setIsCreatingCustomType(isCreating); + cache.clear(getIsEmptyProject, []); + }} + onOpenChange={setIsCreateCustomTypeModalOpen} + /> ); diff --git a/packages/slice-machine/src/features/customTypes/__tests__/createCustomType.test.ts b/packages/slice-machine/src/features/customTypes/__tests__/createCustomType.test.ts deleted file mode 100644 index 684d080076..0000000000 --- a/packages/slice-machine/src/features/customTypes/__tests__/createCustomType.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { describe, expect } from "vitest"; -import { createCustomType } from "@src/features/customTypes/customTypesTable/createCustomType"; - -describe("createCustomType test suite", () => { - it("should create a custom type with repeatable true", () => { - expect(createCustomType("id", "lama", true, "custom")) - .toMatchInlineSnapshot(` - { - "format": "custom", - "id": "id", - "json": { - "Main": { - "uid": { - "config": { - "label": "UID", - }, - "type": "UID", - }, - }, - }, - "label": "lama", - "repeatable": true, - "status": true, - } - `); - }); - - it("should create a custom type with repeatable false", () => { - const result = createCustomType("id", "lama", false, "custom"); - expect(result.id).toBe("id"); - expect(result.label).toBe("lama"); - expect(result.format).toBe("custom"); - expect(result.json).toHaveProperty("Main"); - expect(result).toMatchInlineSnapshot(` - { - "format": "custom", - "id": "id", - "json": { - "Main": {}, - }, - "label": "lama", - "repeatable": false, - "status": true, - } - `); - }); - - it("should create a page type with a slice zone", () => { - const result = createCustomType("🥪", "label", true, "page"); - expect(result.format).toBe("page"); - expect(result.json).toHaveProperty("Main"); - expect(result.json.Main).toHaveProperty("slices"); - expect(result.json).toHaveProperty("SEO & Metadata"); - expect(result).toMatchInlineSnapshot(` - { - "format": "page", - "id": "🥪", - "json": { - "Main": { - "slices": { - "config": { - "choices": {}, - }, - "fieldset": "Slice Zone", - "type": "Slices", - }, - "uid": { - "config": { - "label": "UID", - }, - "type": "UID", - }, - }, - "SEO & Metadata": { - "meta_description": { - "config": { - "label": "Meta Description", - "placeholder": "A brief summary of the page", - }, - "type": "Text", - }, - "meta_image": { - "config": { - "constraint": { - "height": 1260, - "width": 2400, - }, - "label": "Meta Image", - "thumbnails": [], - }, - "type": "Image", - }, - "meta_title": { - "config": { - "label": "Meta Title", - "placeholder": "A title of the page used for social media and search engines", - }, - "type": "Text", - }, - }, - }, - "label": "label", - "repeatable": true, - "status": true, - } - `); - }); -}); - -it("when non repeatable page type is should contain Main with a slice-zone, no uid, and a SEO tab", () => { - const result = createCustomType("foo", "bar", false, "page"); - expect(result).toMatchInlineSnapshot(` - { - "format": "page", - "id": "foo", - "json": { - "Main": { - "slices": { - "config": { - "choices": {}, - }, - "fieldset": "Slice Zone", - "type": "Slices", - }, - }, - "SEO & Metadata": { - "meta_description": { - "config": { - "label": "Meta Description", - "placeholder": "A brief summary of the page", - }, - "type": "Text", - }, - "meta_image": { - "config": { - "constraint": { - "height": 1260, - "width": 2400, - }, - "label": "Meta Image", - "thumbnails": [], - }, - "type": "Image", - }, - "meta_title": { - "config": { - "label": "Meta Title", - "placeholder": "A title of the page used for social media and search engines", - }, - "type": "Text", - }, - }, - }, - "label": "bar", - "repeatable": false, - "status": true, - } - `); -}); diff --git a/packages/slice-machine/src/features/customTypes/actions/convertCustomToPageType.ts b/packages/slice-machine/src/features/customTypes/actions/convertCustomToPageType.ts index 880eddeee5..941871e81e 100644 --- a/packages/slice-machine/src/features/customTypes/actions/convertCustomToPageType.ts +++ b/packages/slice-machine/src/features/customTypes/actions/convertCustomToPageType.ts @@ -9,17 +9,20 @@ import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages"; export async function convertCustomToPageType( customType: CustomType, - saveCustomType: (customType: CustomType) => void, + onSuccess: (newCustomType: CustomType) => void, ) { const customTypesMessages = CUSTOM_TYPES_MESSAGES[customType.format as CustomTypeFormat]; try { const newCustomType = convertToPageType(customType); - await updateCustomType(newCustomType); + const { errors } = await updateCustomType(newCustomType); - // Update the custom type in the redux store - saveCustomType(newCustomType); + if (errors.length > 0) { + throw errors; + } + + onSuccess(newCustomType); toast.success( `${customTypesMessages.name({ @@ -28,11 +31,12 @@ export async function convertCustomToPageType( })} converted to page type`, ); } catch (e) { - toast.error( - `Internal Error: ${customTypesMessages.name({ - start: true, - plural: false, - })} not converted to page type`, - ); + const errorMessage = `Internal Error: ${customTypesMessages.name({ + start: true, + plural: false, + })} not converted to page type`; + + console.error(errorMessage, e); + toast.error(errorMessage); } } diff --git a/packages/slice-machine/src/features/customTypes/actions/createCustomType.ts b/packages/slice-machine/src/features/customTypes/actions/createCustomType.ts new file mode 100644 index 0000000000..491e411990 --- /dev/null +++ b/packages/slice-machine/src/features/customTypes/actions/createCustomType.ts @@ -0,0 +1,69 @@ +import { toast } from "react-toastify"; +import { CustomType } from "@prismicio/types-internal/lib/customtypes"; + +import { ToastMessageWithPath } from "@components/ToasterContainer"; +import { CustomTypeFormat } from "@slicemachine/manager"; +import { telemetry, updateCustomType } from "@src/apiClient"; +import { create } from "@src/domain/customType"; + +import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages"; + +type DeleteCustomTypeArgs = { + format: CustomTypeFormat; + id: string; + label: string; + origin: CustomTypeOrigin; + repeatable: boolean; + onSuccess: (newCustomType: CustomType) => Promise; +}; + +export type CustomTypeOrigin = "onboarding" | "table"; + +export async function createCustomType(args: DeleteCustomTypeArgs) { + const { id, label, repeatable, format, onSuccess } = args; + const customTypesMessages = CUSTOM_TYPES_MESSAGES[format]; + + const newCustomType: CustomType = create({ + id, + label, + repeatable, + format, + }); + + try { + const { errors } = await updateCustomType(newCustomType); + + if (errors.length > 0) { + throw errors; + } + + void telemetry.track({ + event: "custom-type:created", + id: newCustomType.id, + name: label, + format: format, + type: repeatable ? "repeatable" : "single", + origin: "table", + }); + + await onSuccess(newCustomType); + + toast.success( + ToastMessageWithPath({ + message: `${customTypesMessages.name({ + start: true, + plural: false, + })} saved successfully at `, + path: `./customtypes/${newCustomType.id}/index.json`, + }), + ); + } catch (e) { + const errorMessage = `Internal Error: ${customTypesMessages.name({ + start: true, + plural: false, + })} not saved`; + + console.error(errorMessage, e); + toast.error(errorMessage); + } +} diff --git a/packages/slice-machine/src/features/customTypes/actions/deleteCustomType.ts b/packages/slice-machine/src/features/customTypes/actions/deleteCustomType.ts index ef71a52689..4a84ba0545 100644 --- a/packages/slice-machine/src/features/customTypes/actions/deleteCustomType.ts +++ b/packages/slice-machine/src/features/customTypes/actions/deleteCustomType.ts @@ -1,8 +1,10 @@ +import { toast } from "react-toastify"; import { CustomType } from "@prismicio/types-internal/lib/customtypes"; + import { managerClient } from "@src/managerClient"; +import { CustomTypeFormat } from "@slicemachine/manager"; + import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages"; -import { CustomTypeFormat } from "@slicemachine/manager/*"; -import { toast } from "react-toastify"; type DeleteCustomTypeArgs = { customType: CustomType; @@ -17,7 +19,13 @@ export async function deleteCustomType({ CUSTOM_TYPES_MESSAGES[customType.format as CustomTypeFormat]; try { - await managerClient.customTypes.deleteCustomType({ id: customType.id }); + const { errors } = await managerClient.customTypes.deleteCustomType({ + id: customType.id, + }); + + if (errors.length > 0) { + throw errors; + } onSuccess(); @@ -32,6 +40,8 @@ export async function deleteCustomType({ start: true, plural: false, })} could not be deleted`; + console.error(errorMessage, e); + toast.error(errorMessage); } } diff --git a/packages/slice-machine/src/features/customTypes/actions/renameCustomType.ts b/packages/slice-machine/src/features/customTypes/actions/renameCustomType.ts index 49320c55a1..54680621b8 100644 --- a/packages/slice-machine/src/features/customTypes/actions/renameCustomType.ts +++ b/packages/slice-machine/src/features/customTypes/actions/renameCustomType.ts @@ -1,8 +1,10 @@ +import { toast } from "react-toastify"; import { CustomType } from "@prismicio/types-internal/lib/customtypes"; + import { managerClient } from "@src/managerClient"; -import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages"; import { CustomTypeFormat } from "@slicemachine/manager"; -import { toast } from "react-toastify"; + +import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages"; type RenameCustomTypeArgs = { model: CustomType; @@ -23,10 +25,14 @@ export async function renameCustomType({ label: newLabel, }; - await managerClient.customTypes.renameCustomType({ + const { errors } = await managerClient.customTypes.renameCustomType({ model: renamedCustomType, }); + if (errors.length > 0) { + throw errors; + } + onSuccess(renamedCustomType); toast.success( @@ -40,8 +46,8 @@ export async function renameCustomType({ start: true, plural: false, })} could not be renamed`; - console.error(errorMessage, e); + console.error(errorMessage, e); toast.error(errorMessage); } } diff --git a/packages/slice-machine/src/features/customTypes/customTypesBuilder/CustomTypesBuilderPage.tsx b/packages/slice-machine/src/features/customTypes/customTypesBuilder/CustomTypesBuilderPage.tsx index 17bc94a1d8..5a763bfb35 100644 --- a/packages/slice-machine/src/features/customTypes/customTypesBuilder/CustomTypesBuilderPage.tsx +++ b/packages/slice-machine/src/features/customTypes/customTypesBuilder/CustomTypesBuilderPage.tsx @@ -39,7 +39,9 @@ export const CustomTypesBuilderPage: FC = () => { ); useEffect(() => { - // TODO(DT-1801): When creating a custom type, don't redirect to the builder page until the custom type is created + // This is a fallback in case the user try to directly access a custom type + // that don't exist + // TODO(DT-1801): Implement a 404 page if (!selectedCustomType || !hasLocal(selectedCustomType)) { void router.replace("/"); } diff --git a/packages/slice-machine/src/features/customTypes/customTypesTable/CustomTypesTable.tsx b/packages/slice-machine/src/features/customTypes/customTypesTable/CustomTypesTable.tsx index 1750dab2a7..5d8f56916b 100644 --- a/packages/slice-machine/src/features/customTypes/customTypesTable/CustomTypesTable.tsx +++ b/packages/slice-machine/src/features/customTypes/customTypesTable/CustomTypesTable.tsx @@ -11,16 +11,9 @@ import { } from "@src/components/Table"; import { ReusableIcon } from "@src/icons/ReusableIcon"; import { UniqueIcon } from "@src/icons/UniqueIcon"; -import useSliceMachineActions from "@src/modules/useSliceMachineActions"; import { type CustomType } from "@prismicio/types-internal/lib/customtypes"; import { type CustomTypeFormat } from "@slicemachine/manager"; import { CUSTOM_TYPES_MESSAGES } from "@src/features/customTypes/customTypesMessages"; -import { CUSTOM_TYPES_CONFIG } from "../customTypesConfig"; -import { - useCustomTypes, - useCustomTypesAutoRevalidation, -} from "./useCustomTypes"; - import { BlankSlate, BlankSlateImage, @@ -29,18 +22,25 @@ import { BlankSlateActions, BlankSlateContent, } from "@src/components/BlankSlate"; + +import { CUSTOM_TYPES_CONFIG } from "../customTypesConfig"; import { EditDropdown } from "../EditDropdown"; +import { + useCustomTypes, + useCustomTypesAutoRevalidation, +} from "./useCustomTypes"; type CustomTypesTableProps = { format: CustomTypeFormat; isCreatingCustomType: boolean; + openCreateCustomTypeModal: () => void; }; export const CustomTypesTable: FC = ({ format, isCreatingCustomType, + openCreateCustomTypeModal, }) => { - const { openCreateCustomTypeModal } = useSliceMachineActions(); const router = useRouter(); const { customTypes, updateCustomTypes } = useCustomTypes(format); const sortedCustomTypes = customTypes.sort( diff --git a/packages/slice-machine/src/features/customTypes/customTypesTable/CustomTypesTablePage.tsx b/packages/slice-machine/src/features/customTypes/customTypesTable/CustomTypesTablePage.tsx index d470d1bfc9..40cc578c7f 100644 --- a/packages/slice-machine/src/features/customTypes/customTypesTable/CustomTypesTablePage.tsx +++ b/packages/slice-machine/src/features/customTypes/customTypesTable/CustomTypesTablePage.tsx @@ -1,4 +1,4 @@ -import { type FC, Suspense } from "react"; +import { type FC, Suspense, useState } from "react"; import { Button, ErrorBoundary, @@ -7,7 +7,6 @@ import { DefaultErrorMessage, } from "@prismicio/editor-ui"; import Head from "next/head"; -import { useSelector } from "react-redux"; import { AppLayout, @@ -16,13 +15,10 @@ import { AppLayoutContent, AppLayoutHeader, } from "@components/AppLayout"; -import useSliceMachineActions from "@src/modules/useSliceMachineActions"; -import { isLoading } from "@src/modules/loading"; -import { type SliceMachineStoreType } from "@src/redux/type"; -import { LoadingKeysEnum } from "@src/modules/loading/types"; import { CreateCustomTypeModal } from "@components/Forms/CreateCustomTypeModal"; import { CUSTOM_TYPES_MESSAGES } from "@src/features/customTypes/customTypesMessages"; import type { CustomTypeFormat } from "@slicemachine/manager"; + import { CustomTypesTable } from "./CustomTypesTable"; type CustomTypesTablePageProps = { @@ -33,15 +29,9 @@ export const CustomTypesTablePage: FC = ({ format, }) => { const customTypesMessages = CUSTOM_TYPES_MESSAGES[format]; - const { openCreateCustomTypeModal } = useSliceMachineActions(); - const { isCreatingCustomType } = useSelector( - (store: SliceMachineStoreType) => ({ - isCreatingCustomType: isLoading( - store, - LoadingKeysEnum.CREATE_CUSTOM_TYPE, - ), - }), - ); + const [isCreatingCustomType, setIsCreatingCustomType] = useState(false); + const [isCreateCustomTypeModalOpen, setIsCreateCustomTypeModalOpen] = + useState(false); return ( <> @@ -101,7 +91,9 @@ export const CustomTypesTablePage: FC = ({