diff --git a/packages/slice-machine/components/Forms/RenameSliceModal/RenameSliceModal.tsx b/packages/slice-machine/components/Forms/RenameSliceModal/RenameSliceModal.tsx index f9639c52fa..3843cf9baa 100644 --- a/packages/slice-machine/components/Forms/RenameSliceModal/RenameSliceModal.tsx +++ b/packages/slice-machine/components/Forms/RenameSliceModal/RenameSliceModal.tsx @@ -1,50 +1,62 @@ import { Box } from "theme-ui"; -import useSliceMachineActions from "@src/modules/useSliceMachineActions"; -import ModalFormCard from "../../ModalFormCard"; -import { InputBox } from "../components/InputBox"; import { useSelector } from "react-redux"; + +import useSliceMachineActions from "@src/modules/useSliceMachineActions"; import { SliceMachineStoreType } from "@src/redux/type"; -import { isModalOpen } from "@src/modules/modal"; -import { ModalKeysEnum } from "@src/modules/modal/types"; import { getLibraries, getRemoteSlices } from "@src/modules/slices"; +import { ComponentUI } from "@lib/models/common/ComponentUI"; +import { renameSlice } from "@src/features/slices/actions/renameSlice"; + +import { InputBox } from "../components/InputBox"; +import ModalFormCard from "../../ModalFormCard"; import { SliceModalValues } from "../formsTypes"; import { validateSliceModalValues } from "../formsValidator"; interface RenameSliceModalProps { - sliceName: string; - sliceId: string; - libName: string; + isOpen: boolean; + slice?: ComponentUI; + onClose: () => void; } export const RenameSliceModal: React.FC = ({ - sliceName, - sliceId, - libName, + slice, + isOpen, + onClose, }) => { - const { renameSlice, closeModals } = useSliceMachineActions(); - const { isRenameSliceModalOpen, localLibs, remoteLibs } = useSelector( + const { renameSliceSuccess } = useSliceMachineActions(); + const { localLibs, remoteLibs } = useSelector( (store: SliceMachineStoreType) => ({ - isRenameSliceModalOpen: isModalOpen(store, ModalKeysEnum.RENAME_SLICE), localLibs: getLibraries(store), remoteLibs: getRemoteSlices(store), }), ); + const initialSliceName = slice?.model.name ?? ""; + + const handleOnSubmit = async (values: SliceModalValues) => { + if (slice) { + await renameSlice({ + slice, + newSliceName: values.sliceName, + onSuccess: (renamedSlice) => { + renameSliceSuccess(renamedSlice.from, renamedSlice.model); + }, + }); - const handleOnSubmit = (values: SliceModalValues) => { - renameSlice(libName, sliceId, values.sliceName); + onClose(); + } }; return ( dataCy="rename-slice-modal" - isOpen={isRenameSliceModalOpen} + isOpen={isOpen} widthInPx="530px" - formId={`rename-slice-modal-${sliceId}`} - close={closeModals} + formId={`rename-slice-modal-${slice?.model.id ?? ""}`} + close={onClose} buttonLabel="Rename" - onSubmit={handleOnSubmit} + onSubmit={(values) => void handleOnSubmit(values)} initialValues={{ - sliceName: sliceName, + sliceName: initialSliceName, }} content={{ title: "Rename a slice", diff --git a/packages/slice-machine/pages/slices.tsx b/packages/slice-machine/pages/slices.tsx index dc1fc9a290..7fdd7776ed 100644 --- a/packages/slice-machine/pages/slices.tsx +++ b/packages/slice-machine/pages/slices.tsx @@ -24,7 +24,6 @@ import ScreenshotChangesModal from "@components/ScreenshotChangesModal"; import { RenameSliceModal } from "@components/Forms/RenameSliceModal"; import { DeleteSliceModal } from "@components/DeleteSliceModal"; import { SliceMachineStoreType } from "@src/redux/type"; -import useSliceMachineActions from "@src/modules/useSliceMachineActions"; import { getLibraries, getRemoteSlices } from "@src/modules/slices"; import { useScreenshotChangesModal } from "@src/hooks/useScreenshotChangesModal"; import { SharedSliceCard } from "@src/features/slices/sliceCards/SharedSliceCard"; @@ -33,8 +32,6 @@ import { SliceToastMessage } from "@components/ToasterContainer"; const SlicesIndex: React.FunctionComponent = () => { const router = useRouter(); - const { openRenameSliceModal } = useSliceMachineActions(); - const { modalPayload, onOpenModal } = useScreenshotChangesModal(); const { sliceFilterFn, defaultVariationSelector } = modalPayload; @@ -47,6 +44,7 @@ const SlicesIndex: React.FunctionComponent = () => { ); const [isCreateSliceModalOpen, setIsCreateSliceModalOpen] = useState(false); const [isDeleteSliceModalOpen, setIsDeleteSliceModalOpen] = useState(false); + const [isRenameSliceModalOpen, setIsRenameSliceModalOpen] = useState(false); const localLibraries: LibraryUI[] = libraries.filter( (library) => library.isLocal, @@ -69,6 +67,10 @@ const SlicesIndex: React.FunctionComponent = () => { setIsDeleteSliceModalOpen(true); }; + const openRenameSliceModal = () => { + setIsRenameSliceModalOpen(true); + }; + return ( <> @@ -241,9 +243,11 @@ const SlicesIndex: React.FunctionComponent = () => { /> )} { + setIsRenameSliceModalOpen(false); + }} data-cy="rename-slice-modal" /> { return { slice: model ? Slices.toSM(model) : undefined, errors }; }; -export const renameSlice = async ( - slice: SliceSM, - libName: string, -): ReturnType => { - return await managerClient.slices.renameSlice({ - libraryID: libName, - model: Slices.fromSM(slice), - }); -}; - export const renameSliceVariation = async ( slice: ComponentUI, variation: VariationSM, diff --git a/packages/slice-machine/src/domain/slice.ts b/packages/slice-machine/src/domain/slice.ts index 3a3c55d13d..2da18a9558 100644 --- a/packages/slice-machine/src/domain/slice.ts +++ b/packages/slice-machine/src/domain/slice.ts @@ -271,3 +271,13 @@ export function buildEmptySliceModel(sliceName: string): SharedSlice { ], }; } + +export function rename(slice: ComponentUI, newSliceName: string): ComponentUI { + return { + ...slice, + model: { + ...slice.model, + name: newSliceName, + }, + }; +} diff --git a/packages/slice-machine/src/features/slices/actions/renameSlice.ts b/packages/slice-machine/src/features/slices/actions/renameSlice.ts new file mode 100644 index 0000000000..305af32225 --- /dev/null +++ b/packages/slice-machine/src/features/slices/actions/renameSlice.ts @@ -0,0 +1,37 @@ +import { toast } from "react-toastify"; + +import { managerClient } from "@src/managerClient"; +import { ComponentUI } from "@lib/models/common/ComponentUI"; +import { rename } from "@src/domain/slice"; +import { Slices } from "@lib/models/common/Slice"; + +type DeleteSliceArgs = { + newSliceName: string; + slice: ComponentUI; + onSuccess: (renamedSlice: ComponentUI) => void; +}; + +export async function renameSlice(args: DeleteSliceArgs) { + const { slice, newSliceName, onSuccess } = args; + + try { + const renamedSlice = rename(slice, newSliceName); + + const { errors } = await managerClient.slices.renameSlice({ + libraryID: slice.from, + model: Slices.fromSM(renamedSlice.model), + }); + + if (errors.length > 0) { + throw errors; + } + + onSuccess(renamedSlice); + + toast.success("Slice name updated"); + } catch (e) { + const errorMessage = `An unexpected error happened while renaming “${slice.model.name}”.`; + console.error(errorMessage, e); + toast.error(errorMessage); + } +} diff --git a/packages/slice-machine/src/modules/loading/types.ts b/packages/slice-machine/src/modules/loading/types.ts index 3c8a8cbff5..4bd430aca9 100644 --- a/packages/slice-machine/src/modules/loading/types.ts +++ b/packages/slice-machine/src/modules/loading/types.ts @@ -5,7 +5,6 @@ export enum LoadingKeysEnum { REVIEW = "REVIEW", CHECK_SIMULATOR = "CHECK_SIMULATOR", CHECK_SIMULATOR_IFRAME = "CHECK_SIMULATOR_IFRAME", - RENAME_SLICE = "RENAME_SLICE", GENERATE_SLICE_CUSTOM_SCREENSHOT = "GENERATE_SLICE_CUSTOM_SCREENSHOT", SIMULATOR_SAVE_MOCK = "SIMULATOR_SAVE_MOCK", CHANGELOG = "CHANGELOG", diff --git a/packages/slice-machine/src/modules/modal/types.ts b/packages/slice-machine/src/modules/modal/types.ts index 1fc97697ef..abeafa2842 100644 --- a/packages/slice-machine/src/modules/modal/types.ts +++ b/packages/slice-machine/src/modules/modal/types.ts @@ -1,6 +1,5 @@ export enum ModalKeysEnum { LOGIN = "LOGIN", - RENAME_SLICE = "RENAME_SLICE", SCREENSHOT_PREVIEW = "SCREENSHOT_PREVIEW", SCREENSHOTS = "SCREENSHOTS", SIMULATOR_SETUP = "SIMULATOR_SETUP", diff --git a/packages/slice-machine/src/modules/slices/index.ts b/packages/slice-machine/src/modules/slices/index.ts index 7223da8827..a5cad0d92e 100644 --- a/packages/slice-machine/src/modules/slices/index.ts +++ b/packages/slice-machine/src/modules/slices/index.ts @@ -1,47 +1,25 @@ -import { - ActionType, - createAction, - createAsyncAction, - getType, -} from "typesafe-actions"; -import { call, fork, put, select, takeLatest } from "redux-saga/effects"; +import { ActionType, createAction, getType } from "typesafe-actions"; import { Reducer } from "redux"; import { LocalOrRemoteSlice } from "@lib/models/common/ModelData"; import { normalizeFrontendSlices } from "@lib/models/common/normalizers/slices"; import { SliceSM } from "@lib/models/common/Slice"; import { LibraryUI } from "@models/common/LibraryUI"; -import { withLoader } from "@src/modules/loading"; -import { LoadingKeysEnum } from "@src/modules/loading/types"; -import { renameSlice, SaveSliceMockRequest } from "@src/apiClient"; -import { modalCloseCreator } from "@src/modules/modal"; +import { SaveSliceMockRequest } from "@src/apiClient"; import { refreshStateCreator } from "@src/modules/environment"; import { SliceMachineStoreType } from "@src/redux/type"; -import { openToasterCreator, ToasterType } from "@src/modules/toaster"; import { SlicesStoreType } from "./types"; import { ComponentUI, ScreenshotUI } from "@lib/models/common/ComponentUI"; -import { selectSliceById } from "./selector"; // Action Creators export const sliceCreateSuccess = createAction("SLICES/CREATE_SUCCESS")<{ libraries: Readonly; }>(); -export const renameSliceCreator = createAsyncAction( - "SLICES/RENAME.REQUEST", - "SLICES/RENAME.RESPONSE", - "SLICES/RENAME.FAILURE", -)< - { - libName: string; - sliceId: string; - newSliceName: string; - }, - { - libName: string; - renamedSlice: SliceSM; - } ->(); +export const sliceRenameSuccess = createAction("SLICES/RENAME_SUCCESS")<{ + libName: string; + renamedSlice: SliceSM; +}>(); export const sliceDeleteSuccess = createAction("SLICES/DELETE_SUCCESS")<{ sliceId: string; @@ -62,7 +40,7 @@ export const updateSliceMock = type SlicesActions = | ActionType | ActionType - | ActionType + | ActionType | ActionType | ActionType | ActionType @@ -108,7 +86,7 @@ export const slicesReducer: Reducer = ( ...state, libraries: action.payload.libraries, }; - case getType(renameSliceCreator.success): { + case getType(sliceRenameSuccess): { const { libName, renamedSlice } = action.payload; const newLibs = state.libraries.map((library) => { if (library.name !== libName) return library; @@ -214,68 +192,3 @@ export const slicesReducer: Reducer = ( return state; } }; - -// Sagas - -export function* renameSliceSaga({ - payload, -}: ReturnType) { - const { libName, sliceId, newSliceName } = payload; - try { - const slice = (yield select((store: SliceMachineStoreType) => - selectSliceById(store, libName, sliceId), - )) as ReturnType; - if (!slice) { - throw new Error( - `Slice "${payload.sliceId} in the "${payload.libName}" library not found.`, - ); - } - - const renamedSlice = renameSliceModel({ - slice: slice.model, - newName: newSliceName, - }); - - yield call(renameSlice, renamedSlice, libName); - yield put(renameSliceCreator.success({ libName, renamedSlice })); - - yield put(modalCloseCreator()); - yield put( - openToasterCreator({ - content: "Slice name updated", - type: ToasterType.SUCCESS, - }), - ); - } catch (e) { - yield put( - openToasterCreator({ - content: "Internal Error: Slice name not saved", - type: ToasterType.ERROR, - }), - ); - } -} - -function* watchRenameSlice() { - yield takeLatest( - getType(renameSliceCreator.request), - withLoader(renameSliceSaga, LoadingKeysEnum.RENAME_SLICE), - ); -} - -// Saga Exports -export function* watchSliceSagas() { - yield fork(watchRenameSlice); -} - -type RenameSliceModelArgs = { - slice: SliceSM; - newName: string; -}; - -export function renameSliceModel(args: RenameSliceModelArgs): SliceSM { - return { - ...args.slice, - name: args.newName, - }; -} diff --git a/packages/slice-machine/src/modules/slices/selector.ts b/packages/slice-machine/src/modules/slices/selector.ts index d4d59cdbe5..0f2ac90b41 100644 --- a/packages/slice-machine/src/modules/slices/selector.ts +++ b/packages/slice-machine/src/modules/slices/selector.ts @@ -2,20 +2,6 @@ import { SliceMachineStoreType } from "@src/redux/type"; import { getLibraries } from "./index"; -export const selectSliceById = ( - store: SliceMachineStoreType, - libraryName: string, - sliceId: string, -) => { - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - const libraries = getLibraries(store) || []; - - const library = libraries.find((library) => library.name === libraryName); - const slice = library?.components.find((c) => c.model.id === sliceId); - - return slice; -}; - export const selectCurrentSlice = ( store: SliceMachineStoreType, lib: string, diff --git a/packages/slice-machine/src/modules/useSliceMachineActions.ts b/packages/slice-machine/src/modules/useSliceMachineActions.ts index e05314d0fc..9f33381f64 100644 --- a/packages/slice-machine/src/modules/useSliceMachineActions.ts +++ b/packages/slice-machine/src/modules/useSliceMachineActions.ts @@ -28,8 +28,8 @@ import { sliceCreateSuccess, sliceDeleteSuccess, sliceGenerateCustomScreenshotSuccess, - renameSliceCreator, sliceUpdateSuccess, + sliceRenameSuccess, } from "./slices"; import { UserContextStoreType, UserReviewType } from "./userContext/types"; import { GenericToastTypes, openToasterCreator } from "./toaster"; @@ -39,6 +39,7 @@ import { ComponentUI, ScreenshotUI } from "../../lib/models/common/ComponentUI"; import { saveSliceMockCreator } from "./simulator"; import { SaveSliceMockRequest } from "@src/apiClient"; import { LibraryUI } from "@lib/models/common/LibraryUI"; +import { SliceSM } from "@lib/models/common/Slice"; const useSliceMachineActions = () => { const dispatch = useDispatch(); @@ -61,8 +62,6 @@ const useSliceMachineActions = () => { dispatch(modalOpenCreator({ modalKey: ModalKeysEnum.LOGIN })); const openScreenshotsModal = () => dispatch(modalOpenCreator({ modalKey: ModalKeysEnum.SCREENSHOTS })); - const openRenameSliceModal = () => - dispatch(modalOpenCreator({ modalKey: ModalKeysEnum.RENAME_SLICE })); const openScreenshotPreviewModal = () => dispatch(modalOpenCreator({ modalKey: ModalKeysEnum.SCREENSHOT_PREVIEW })); const openSimulatorSetupModal = () => @@ -99,19 +98,6 @@ const useSliceMachineActions = () => { dispatch(hasSeenTutorialsToolTipCreator()); const setSeenChangesToolTip = () => dispatch(hasSeenChangesToolTipCreator()); - const renameSlice = ( - libName: string, - sliceId: string, - newSliceName: string, - ) => - dispatch( - renameSliceCreator.request({ - sliceId, - newSliceName, - libName, - }), - ); - // Toaster store const openToaster = ( content: string | React.ReactNode, @@ -206,6 +192,13 @@ const useSliceMachineActions = () => { libName, }), ); + const renameSliceSuccess = (libName: string, renamedSlice: SliceSM) => + dispatch( + sliceRenameSuccess({ + renamedSlice, + libName, + }), + ); /** * Changes module @@ -230,7 +223,7 @@ const useSliceMachineActions = () => { saveSliceSuccess, saveSliceCustomScreenshotSuccess, createSliceSuccess, - renameSlice, + renameSliceSuccess, deleteSliceSuccess, sendAReview, skipReview, @@ -240,7 +233,6 @@ const useSliceMachineActions = () => { setSeenChangesToolTip, openScreenshotPreviewModal, openSimulatorSetupModal, - openRenameSliceModal, closeModals, openToaster, saveSliceMock, diff --git a/packages/slice-machine/src/redux/saga.ts b/packages/slice-machine/src/redux/saga.ts index 74cbcb3360..6dfe59f91c 100644 --- a/packages/slice-machine/src/redux/saga.ts +++ b/packages/slice-machine/src/redux/saga.ts @@ -1,14 +1,12 @@ import { fork } from "redux-saga/effects"; import { watchSimulatorSagas } from "@src/modules/simulator"; -import { watchSliceSagas } from "@src/modules/slices"; import { watchToasterSagas } from "@src/modules/toaster"; import { watchChangelogSagas } from "@src/modules/environment"; // Single entry point to start all Sagas at once export default function* rootSaga() { yield fork(watchSimulatorSagas); - yield fork(watchSliceSagas); yield fork(watchToasterSagas); yield fork(watchChangelogSagas); }