From d12494ba71b103bfcb386b1d9a6955f2f485b7c4 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Wed, 14 May 2025 09:59:09 +0800 Subject: [PATCH 01/12] Added redirects tab --- .../src/app/views/ItemEdit/ItemEdit.js | 6 ++++ .../components/ItemEditHeader/index.tsx | 6 ++++ .../src/app/views/Redirects/index.tsx | 34 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 src/apps/content-editor/src/app/views/Redirects/index.tsx diff --git a/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js b/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js index 95df63989..cf059c155 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js +++ b/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js @@ -61,6 +61,7 @@ import { fetchItemPublishings, fetchItems, } from "../../../../../../shell/store/content"; +import { Redirects } from "../Redirects"; const selectItemHeadTags = createSelector( (state) => state.headTags, @@ -668,6 +669,11 @@ export default function ItemEdit() { path="/content/:modelZUID/:itemZUID/freestyle" render={() => } /> + } + /> diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/index.tsx index a1d0ffb48..c2a65d3da 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/index.tsx @@ -24,6 +24,7 @@ import { ContentCopyRounded, WebRounded, } from "@mui/icons-material"; +import { ShuffleVariant } from "@zesty-io/material"; import { useSelector } from "react-redux"; import { AppState } from "../../../../../../../../shell/store/types"; import { ItemEditHeaderActions } from "./ItemEditHeaderActions"; @@ -51,6 +52,11 @@ const tabs = [ icon: QueryStatsRounded, value: "meta", }, + { + label: "Redirects", + icon: ShuffleVariant, + value: "redirects", + }, { label: "Analytics", icon: BarChartRounded, diff --git a/src/apps/content-editor/src/app/views/Redirects/index.tsx b/src/apps/content-editor/src/app/views/Redirects/index.tsx new file mode 100644 index 000000000..228274c83 --- /dev/null +++ b/src/apps/content-editor/src/app/views/Redirects/index.tsx @@ -0,0 +1,34 @@ +import { Typography, Box, Stack } from "@mui/material"; +import { DataGridPro } from "@mui/x-data-grid-pro"; + +const COLUMNS = [ + { + field: "incomingPath", + headerName: "Incoming Path", + flex: 1, + }, + { + field: "httpCode", + headerName: "HTTP Code", + width: 120, + }, + { + field: "targetPath", + headerName: "Target Path", + flex: 1, + }, +] as const; + +export const Redirects = () => { + return ( + + + Incoming Redirects + + + Manage redirects that point to this content item + + + + ); +}; From 5d03b814908f3d158d0c9cf0f8d4453c426f4b5d Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Wed, 14 May 2025 11:39:35 +0800 Subject: [PATCH 02/12] Render table data --- .../src/app/views/Redirects/index.tsx | 59 +++++++++++++++++-- src/shell/services/instance.ts | 8 +++ src/shell/services/types.ts | 15 +++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/apps/content-editor/src/app/views/Redirects/index.tsx b/src/apps/content-editor/src/app/views/Redirects/index.tsx index 228274c83..3d9f0903f 100644 --- a/src/apps/content-editor/src/app/views/Redirects/index.tsx +++ b/src/apps/content-editor/src/app/views/Redirects/index.tsx @@ -1,5 +1,16 @@ +import { useMemo } from "react"; import { Typography, Box, Stack } from "@mui/material"; -import { DataGridPro } from "@mui/x-data-grid-pro"; +import { MoreHoriz } from "@mui/icons-material"; +import { + DataGridPro, + GridActionsCellItem, + GridRowParams, +} from "@mui/x-data-grid-pro"; + +import { useGetRedirectsQuery } from "../../../../../../shell/services/instance"; +import { useSelector } from "react-redux"; +import { AppState } from "../../../../../../shell/store/types"; +import { useParams } from "react-router"; const COLUMNS = [ { @@ -17,18 +28,58 @@ const COLUMNS = [ headerName: "Target Path", flex: 1, }, + { + field: "actions", + type: "actions", + width: 52, + getActions: (params: GridRowParams) => [ + } label="More" />, + ], + }, ] as const; export const Redirects = () => { + const { itemZUID } = useParams<{ + itemZUID: string; + }>(); + const { data: redirects, isLoading: isLoadingRedirects } = + useGetRedirectsQuery(); + const { web } = useSelector((state: AppState) => state.content[itemZUID]); + + const redirectsHere = useMemo(() => { + if (!redirects?.length || !web?.path) return []; + + return redirects.filter( + (redirect) => + redirect.targetType === "path" && redirect.target === web.path + ); + }, [redirects, web]); + + const rows = useMemo(() => { + if (!redirectsHere?.length) return []; + + return redirectsHere.map((redirect) => ({ + id: redirect.ZUID, + incomingPath: redirect.path, + httpCode: redirect.code, + targetPath: redirect.target, + })); + }, [redirectsHere]); + return ( - + Incoming Redirects Manage redirects that point to this content item - - + + ); }; diff --git a/src/shell/services/instance.ts b/src/shell/services/instance.ts index c90bdb24b..9ddb31b0b 100644 --- a/src/shell/services/instance.ts +++ b/src/shell/services/instance.ts @@ -23,6 +23,7 @@ import { Data, StyleCategory, GroupItem, + Redirects, } from "./types"; import { batchApiRequests } from "../../utility/batchApiRequests"; @@ -53,6 +54,7 @@ export const instanceApi = createApi({ "ContentItems", "ItemPublishings", "Groups", + "Redirects", ], endpoints: (builder) => ({ // https://www.zesty.io/docs/instances/api-reference/content/models/items/publishings/#Get-All-Item-Publishings @@ -716,6 +718,11 @@ export const instanceApi = createApi({ }, invalidatesTags: ["ContentModels", "WebViews", "ContentModelFields"], }), + getRedirects: builder.query({ + query: () => `/web/redirects`, + transformResponse: getResponseData, + providesTags: ["Redirects"], + }), }), }); @@ -770,4 +777,5 @@ export const { useGetGroupByZUIDQuery, useCreateGroupMutation, useCreateStarterBlockModelMutation, + useGetRedirectsQuery, } = instanceApi; diff --git a/src/shell/services/types.ts b/src/shell/services/types.ts index 3cf625867..0a0612a6f 100644 --- a/src/shell/services/types.ts +++ b/src/shell/services/types.ts @@ -592,3 +592,18 @@ export type GroupItem = { createdAt: string; updatedAt: string; }; + +export type RedirectsCodes = 301 | 302; +export type RedirectsTargetType = "page" | "external" | "path"; +export type Redirects = { + ZUID: string; + path: string; + targetType: RedirectsTargetType; + target: string; + code: RedirectsCodes; + query_string: string | null; + createdByUserZUID: string; + updatedByUserZUID: string; + createdAt: string; + updatedAt: string; +}; From f8036c9fc96115ee5d624e841cd0e0084ad81786 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Wed, 14 May 2025 12:23:12 +0800 Subject: [PATCH 03/12] Update cell render --- .../src/app/views/Redirects/index.tsx | 84 ++++++++++++------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/src/apps/content-editor/src/app/views/Redirects/index.tsx b/src/apps/content-editor/src/app/views/Redirects/index.tsx index 3d9f0903f..83193b7b4 100644 --- a/src/apps/content-editor/src/app/views/Redirects/index.tsx +++ b/src/apps/content-editor/src/app/views/Redirects/index.tsx @@ -1,44 +1,22 @@ import { useMemo } from "react"; -import { Typography, Box, Stack } from "@mui/material"; -import { MoreHoriz } from "@mui/icons-material"; +import { Typography, Box, Stack, Link } from "@mui/material"; +import { MoreHoriz, ArrowForwardRounded } from "@mui/icons-material"; import { DataGridPro, GridActionsCellItem, GridRowParams, + GridColDef, } from "@mui/x-data-grid-pro"; import { useGetRedirectsQuery } from "../../../../../../shell/services/instance"; import { useSelector } from "react-redux"; import { AppState } from "../../../../../../shell/store/types"; import { useParams } from "react-router"; - -const COLUMNS = [ - { - field: "incomingPath", - headerName: "Incoming Path", - flex: 1, - }, - { - field: "httpCode", - headerName: "HTTP Code", - width: 120, - }, - { - field: "targetPath", - headerName: "Target Path", - flex: 1, - }, - { - field: "actions", - type: "actions", - width: 52, - getActions: (params: GridRowParams) => [ - } label="More" />, - ], - }, -] as const; +import { Link as RouterLink } from "react-router-dom"; +import { useDomain } from "../../../../../../shell/hooks/use-domain"; export const Redirects = () => { + const domain = useDomain(); const { itemZUID } = useParams<{ itemZUID: string; }>(); @@ -55,6 +33,53 @@ export const Redirects = () => { ); }, [redirects, web]); + const columns: GridColDef[] = [ + { + field: "incomingPath", + headerName: "Incoming Path", + flex: 1, + }, + { + field: "httpCode", + headerName: "HTTP Code", + width: 120, + renderCell: (params) => { + return ( + + {params.row.httpCode} + + + ); + }, + }, + { + field: "targetPath", + headerName: "Target Path", + flex: 1, + renderCell: (params) => { + console.log(params); + return ( + + {params.row.targetPath} + + ); + }, + }, + { + field: "actions", + type: "actions", + width: 52, + getActions: (params: GridRowParams) => [ + } label="More" />, + ], + }, + ]; + const rows = useMemo(() => { if (!redirectsHere?.length) return []; @@ -75,9 +100,10 @@ export const Redirects = () => { Manage redirects that point to this content item From e7ed0264854d825f6b6ac4c16caf49110b23b28c Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Wed, 14 May 2025 13:22:51 +0800 Subject: [PATCH 04/12] Update action menu items --- .../src/app/views/Redirects/index.tsx | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/apps/content-editor/src/app/views/Redirects/index.tsx b/src/apps/content-editor/src/app/views/Redirects/index.tsx index 83193b7b4..ecd0ac9b1 100644 --- a/src/apps/content-editor/src/app/views/Redirects/index.tsx +++ b/src/apps/content-editor/src/app/views/Redirects/index.tsx @@ -1,6 +1,11 @@ -import { useMemo } from "react"; -import { Typography, Box, Stack, Link } from "@mui/material"; -import { MoreHoriz, ArrowForwardRounded } from "@mui/icons-material"; +import { useMemo, useState, MouseEvent } from "react"; +import { Typography, Box, Stack, Link, Menu, MenuItem } from "@mui/material"; +import { + MoreHoriz, + ArrowForwardRounded, + EditRounded, + DeleteRounded, +} from "@mui/icons-material"; import { DataGridPro, GridActionsCellItem, @@ -16,6 +21,7 @@ import { Link as RouterLink } from "react-router-dom"; import { useDomain } from "../../../../../../shell/hooks/use-domain"; export const Redirects = () => { + const [menuAnchorEl, setMenuAnchorEl] = useState(null); const domain = useDomain(); const { itemZUID } = useParams<{ itemZUID: string; @@ -75,7 +81,24 @@ export const Redirects = () => { type: "actions", width: 52, getActions: (params: GridRowParams) => [ - } label="More" />, + } + label="Edit Redirect" + onClick={() => console.log("Edit")} + showInMenu + sx={{ + width: 240, + }} + />, + } + label="Delete Redirect" + onClick={() => console.log("Delete")} + showInMenu + sx={{ + width: 240, + }} + />, ], }, ]; @@ -105,6 +128,9 @@ export const Redirects = () => { hideFooter disableRowSelectionOnClick loading={isLoadingRedirects} + slots={{ + moreActionsIcon: MoreHoriz, + }} /> ); From 81245c210a55897dd647b5756bb380867e01f279 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 15 May 2025 10:13:38 +0800 Subject: [PATCH 05/12] Update redirects tab icon --- src/shell/store/ui.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/shell/store/ui.ts b/src/shell/store/ui.ts index 2cfc59428..b39ed2f7c 100644 --- a/src/shell/store/ui.ts +++ b/src/shell/store/ui.ts @@ -16,9 +16,8 @@ import { RecentActorsRounded, SvgIconComponent, ExtensionRounded, - ShuffleRounded, } from "@mui/icons-material"; -import { Database, Block } from "@zesty-io/material"; +import { Database, Block, ShuffleVariant } from "@zesty-io/material"; import { capitalize, isEqual } from "lodash"; export type Tab = { @@ -139,7 +138,7 @@ export const { actions, reducer } = ui; const ICON_CONFIG: { [index: string]: SvgIconComponent } = Object.freeze({ launchpad: RocketLaunchRounded, - redirects: ShuffleRounded, + redirects: ShuffleVariant as SvgIconComponent, content: EditRounded, blocks: Block as SvgIconComponent, media: ImageRounded, From 7cb51bbcd6d9befd73251d9e786f65f94b159bac Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 15 May 2025 14:54:20 +0800 Subject: [PATCH 06/12] Added delete redirect flow --- .../views/Redirects/DeleteRedirectModal.tsx | 207 ++++++++++++++++++ .../src/app/views/Redirects/index.tsx | 82 ++++--- src/shell/services/instance.ts | 8 + 3 files changed, 269 insertions(+), 28 deletions(-) create mode 100644 src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx diff --git a/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx b/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx new file mode 100644 index 000000000..2c9b14038 --- /dev/null +++ b/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx @@ -0,0 +1,207 @@ +import { useEffect } from "react"; +import { + Dialog, + DialogContent, + DialogTitle, + Typography, + Box, + Stack, + Button, + DialogActions, + Link, +} from "@mui/material"; +import { DeleteRounded, Description, HiveRounded } from "@mui/icons-material"; +import { LoadingButton } from "@mui/lab"; +import { useDispatch } from "react-redux"; + +import { useDomain } from "../../../../../../shell/hooks/use-domain"; +import { useDeleteRedirectMutation } from "../../../../../../shell/services/instance"; +import { notify } from "../../../../../../shell/store/notifications"; +import { + Redirects, + RedirectsTargetType, +} from "../../../../../../shell/services/types"; + +const HTTP_CODE_OPTIONS = { + 301: "301 - Permanent Redirect", + 302: "302 - Temporary Redirect", +} as const; + +type DeleteRedirectModalProps = { + targetPath: string; + data: Redirects; + onClose: () => void; +}; +export const DeleteRedirectModal = ({ + targetPath, + data, + onClose, +}: DeleteRedirectModalProps) => { + const domain = useDomain(); + const dispatch = useDispatch(); + const [ + deleteRedirect, + { isLoading: isDeletingRedirect, isSuccess: isRedirectDeleted }, + ] = useDeleteRedirectMutation(); + + useEffect(() => { + if (isRedirectDeleted) { + onClose(); + dispatch( + notify({ + message: `Redirect Deleted: ${data.path}`, + kind: "error", + }) + ); + } + }, [isRedirectDeleted]); + + const onHandleDelete = () => { + deleteRedirect({ ZUID: data.ZUID }); + }; + + return ( + + + + + + + + + + Delete Redirect:  + + + {data.path} + + + + Deleting this redirect will remove it immediately from your site. + This action cannot be undone. + + + + + + + More details + + + + + HTTP Code + + + {HTTP_CODE_OPTIONS[data.code as keyof typeof HTTP_CODE_OPTIONS]} + + + + + Redirect Type + + + + + + Redirect Target + + + {targetPath} + + + + + + + + Delete Forever + + + + ); +}; + +const RedirectType = ({ type }: { type: RedirectsTargetType }) => { + if (type === "path") { + return ( + + + + Wildcard + + + ); + } + + if (type === "page") { + return ( + + + + Internal + + + ); + } + + return <>; +}; diff --git a/src/apps/content-editor/src/app/views/Redirects/index.tsx b/src/apps/content-editor/src/app/views/Redirects/index.tsx index ecd0ac9b1..ffd2fb28a 100644 --- a/src/apps/content-editor/src/app/views/Redirects/index.tsx +++ b/src/apps/content-editor/src/app/views/Redirects/index.tsx @@ -1,5 +1,5 @@ -import { useMemo, useState, MouseEvent } from "react"; -import { Typography, Box, Stack, Link, Menu, MenuItem } from "@mui/material"; +import { useMemo, useState } from "react"; +import { Typography, Stack, Link } from "@mui/material"; import { MoreHoriz, ArrowForwardRounded, @@ -16,12 +16,20 @@ import { import { useGetRedirectsQuery } from "../../../../../../shell/services/instance"; import { useSelector } from "react-redux"; import { AppState } from "../../../../../../shell/store/types"; -import { useParams } from "react-router"; -import { Link as RouterLink } from "react-router-dom"; +import { Redirect, useParams } from "react-router"; import { useDomain } from "../../../../../../shell/hooks/use-domain"; +import { DeleteRedirectModal } from "./DeleteRedirectModal"; +import { Redirects as RedirectsType } from "../../../../../../shell/services/types"; +export type RedirectRowType = { + httpCode: number; + incomingPath: string; + targetPath: string; + id: string; +}; export const Redirects = () => { - const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const [redirectToDelete, setRedirectToDelete] = + useState(null); const domain = useDomain(); const { itemZUID } = useParams<{ itemZUID: string; @@ -33,9 +41,11 @@ export const Redirects = () => { const redirectsHere = useMemo(() => { if (!redirects?.length || !web?.path) return []; + // We get wildcard and internal redirects return redirects.filter( (redirect) => - redirect.targetType === "path" && redirect.target === web.path + (redirect.targetType === "path" && redirect.target === web.path) || + (redirect.targetType === "page" && redirect.target === itemZUID) ); }, [redirects, web]); @@ -63,15 +73,14 @@ export const Redirects = () => { headerName: "Target Path", flex: 1, renderCell: (params) => { - console.log(params); return ( - {params.row.targetPath} + {web?.path} ); }, @@ -93,7 +102,15 @@ export const Redirects = () => { } label="Delete Redirect" - onClick={() => console.log("Delete")} + onClick={() => { + const matchedRedirect = redirectsHere.find( + (redirect) => redirect.ZUID === params.row.id + ); + + if (!matchedRedirect) return; + + setRedirectToDelete(matchedRedirect); + }} showInMenu sx={{ width: 240, @@ -115,23 +132,32 @@ export const Redirects = () => { }, [redirectsHere]); return ( - - - Incoming Redirects - - - Manage redirects that point to this content item - - - + <> + + + Incoming Redirects + + + Manage redirects that point to this content item + + + + {!!redirectToDelete && ( + setRedirectToDelete(null)} + /> + )} + ); }; diff --git a/src/shell/services/instance.ts b/src/shell/services/instance.ts index 9ddb31b0b..cea545d78 100644 --- a/src/shell/services/instance.ts +++ b/src/shell/services/instance.ts @@ -723,6 +723,13 @@ export const instanceApi = createApi({ transformResponse: getResponseData, providesTags: ["Redirects"], }), + deleteRedirect: builder.mutation({ + query: ({ ZUID }) => ({ + url: `/web/redirects/${ZUID}`, + method: "DELETE", + }), + invalidatesTags: ["Redirects"], + }), }), }); @@ -778,4 +785,5 @@ export const { useCreateGroupMutation, useCreateStarterBlockModelMutation, useGetRedirectsQuery, + useDeleteRedirectMutation, } = instanceApi; From bbdaa9c5e63129cc803dcd8e213e3663f4707dbe Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Fri, 16 May 2025 12:21:06 +0800 Subject: [PATCH 07/12] Use redirects app components for edit redirect --- .../src/app/views/ItemEdit/ItemEdit.js | 7 ++- .../views/Redirects/DeleteRedirectModal.tsx | 4 +- .../src/app/views/Redirects/index.tsx | 43 ++++++++++++++----- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js b/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js index 72b40325f..3dc7db533 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js +++ b/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js @@ -59,6 +59,7 @@ import { fetchItems, } from "../../../../../../shell/store/content"; import { Redirects } from "../Redirects"; +import RedirectsDialogContextProvider from "../../../../../seo/src/app/components/RedirectsDialogProvider"; const selectItemHeadTags = createSelector( (state) => state.headTags, @@ -664,7 +665,11 @@ export default function ItemEdit() { } + render={() => ( + + + + )} /> diff --git a/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx b/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx index 2c9b14038..0b1913015 100644 --- a/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx +++ b/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx @@ -56,7 +56,7 @@ export const DeleteRedirectModal = ({ } }, [isRedirectDeleted]); - const onHandleDelete = () => { + const handleDelete = () => { deleteRedirect({ ZUID: data.ZUID }); }; @@ -170,7 +170,7 @@ export const DeleteRedirectModal = ({ Delete Forever diff --git a/src/apps/content-editor/src/app/views/Redirects/index.tsx b/src/apps/content-editor/src/app/views/Redirects/index.tsx index ffd2fb28a..c0bc405fe 100644 --- a/src/apps/content-editor/src/app/views/Redirects/index.tsx +++ b/src/apps/content-editor/src/app/views/Redirects/index.tsx @@ -16,10 +16,11 @@ import { import { useGetRedirectsQuery } from "../../../../../../shell/services/instance"; import { useSelector } from "react-redux"; import { AppState } from "../../../../../../shell/store/types"; -import { Redirect, useParams } from "react-router"; +import { useParams } from "react-router"; import { useDomain } from "../../../../../../shell/hooks/use-domain"; import { DeleteRedirectModal } from "./DeleteRedirectModal"; import { Redirects as RedirectsType } from "../../../../../../shell/services/types"; +import { useRedirectsDialog } from "../../../../../seo/src/app/components/RedirectsDialogProvider"; export type RedirectRowType = { httpCode: number; @@ -31,6 +32,7 @@ export const Redirects = () => { const [redirectToDelete, setRedirectToDelete] = useState(null); const domain = useDomain(); + const { openDeleteDialog, openCreateForm } = useRedirectsDialog(); const { itemZUID } = useParams<{ itemZUID: string; }>(); @@ -49,6 +51,32 @@ export const Redirects = () => { ); }, [redirects, web]); + const handleAction = (zuid: string, action: "edit" | "delete") => { + const matchedRedirect = redirectsHere.find( + (redirect) => redirect.ZUID === zuid + ); + + if (!matchedRedirect) return; + + switch (action) { + case "edit": + openCreateForm({ + ZUID: matchedRedirect?.ZUID, + targetType: matchedRedirect?.targetType, + code: matchedRedirect?.code, + target: matchedRedirect?.target, + path: matchedRedirect?.path, + }); + break; + case "delete": + setRedirectToDelete(matchedRedirect); + break; + + default: + break; + } + }; + const columns: GridColDef[] = [ { field: "incomingPath", @@ -93,7 +121,7 @@ export const Redirects = () => { } label="Edit Redirect" - onClick={() => console.log("Edit")} + onClick={() => handleAction(params.row.id, "edit")} showInMenu sx={{ width: 240, @@ -102,15 +130,7 @@ export const Redirects = () => { } label="Delete Redirect" - onClick={() => { - const matchedRedirect = redirectsHere.find( - (redirect) => redirect.ZUID === params.row.id - ); - - if (!matchedRedirect) return; - - setRedirectToDelete(matchedRedirect); - }} + onClick={() => handleAction(params.row.id, "delete")} showInMenu sx={{ width: 240, @@ -151,6 +171,7 @@ export const Redirects = () => { }} /> + {/* TODO: Use george's delete flow */} {!!redirectToDelete && ( Date: Mon, 19 May 2025 06:10:50 +0800 Subject: [PATCH 08/12] Clean up --- src/apps/content-editor/src/app/views/Redirects/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/apps/content-editor/src/app/views/Redirects/index.tsx b/src/apps/content-editor/src/app/views/Redirects/index.tsx index c0bc405fe..035c078e0 100644 --- a/src/apps/content-editor/src/app/views/Redirects/index.tsx +++ b/src/apps/content-editor/src/app/views/Redirects/index.tsx @@ -32,7 +32,7 @@ export const Redirects = () => { const [redirectToDelete, setRedirectToDelete] = useState(null); const domain = useDomain(); - const { openDeleteDialog, openCreateForm } = useRedirectsDialog(); + const { openCreateForm } = useRedirectsDialog(); const { itemZUID } = useParams<{ itemZUID: string; }>(); @@ -171,7 +171,6 @@ export const Redirects = () => { }} /> - {/* TODO: Use george's delete flow */} {!!redirectToDelete && ( Date: Mon, 19 May 2025 14:56:01 +0800 Subject: [PATCH 09/12] Add content item redirects tab tests --- cypress/e2e/content/redirects.spec.js | 62 +++++++++++++++++++ .../views/Redirects/DeleteRedirectModal.tsx | 1 + .../src/app/views/Redirects/index.tsx | 2 + 3 files changed, 65 insertions(+) create mode 100644 cypress/e2e/content/redirects.spec.js diff --git a/cypress/e2e/content/redirects.spec.js b/cypress/e2e/content/redirects.spec.js new file mode 100644 index 000000000..2db307802 --- /dev/null +++ b/cypress/e2e/content/redirects.spec.js @@ -0,0 +1,62 @@ +const NOW = new Date().getTime(); + +describe("Content item redirects", () => { + before(() => { + // Create a new redirect + cy.waitOn("/v1/web/redirects", () => { + cy.visit("/redirects"); + }); + + cy.intercept("/v1/web/redirects").as("redirects"); + cy.getBySelector("RedirectActionCreateButton").click(); + cy.getBySelector("RedirectsFieldPath") + .find("input") + .type(`/test-redirect/${NOW}`); + cy.getBySelector("RedirectsSearchFieldInputField").find("input").click(); + cy.wait(2000); + cy.getBySelector("RedirectsSearchFieldInputField") + .find("input") + .type("all field types{downArrow}{enter}"); + cy.getBySelector("RedirectsCreateButton").click(); + cy.wait("@redirects"); + }); + + it("should show redirects for a content item", () => { + cy.waitOn("/v1/content/models*", () => { + cy.visit("/content/6-556370-8sh47g/7-b939a4-457q19/redirects"); + }); + + cy.contains(`/test-redirect/${NOW}`, { timeout: 10000 }).should("exist"); + }); + + it("should be able to edit a redirect", () => { + cy.intercept("/v1/web/redirects").as("redirects"); + cy.contains(`/test-redirect/${NOW}`) + .parent() + .within(() => { + cy.get(".MuiDataGrid-actionsCell button").click(); + }); + cy.getBySelector("EditRedirect").click(); + cy.getBySelector("RedirectsFieldPath").find("input").type(`/updated`); + cy.getBySelector("RedirectsCreateButton").click({ timeout: 10000 }); + + cy.wait("@redirects"); + + cy.contains(`/test-redirect/${NOW}/updated`, { timeout: 10000 }).should( + "exist" + ); + }); + + it("should be able to delete a redirect", () => { + cy.intercept("/v1/web/redirects").as("redirects"); + cy.get(".MuiDataGrid-actionsCell button").last().click(); + cy.getBySelector("DeleteRedirect").click(); + cy.getBySelector("ConfirmDeleteRedirect").click(); + + cy.wait("@redirects").then(() => { + cy.get(".MuiDataGrid-cell") + .contains(`/test-redirect/${NOW}/updated`, { timeout: 10000 }) + .should("not.exist"); + }); + }); +}); diff --git a/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx b/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx index 0b1913015..977379004 100644 --- a/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx +++ b/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx @@ -168,6 +168,7 @@ export const DeleteRedirectModal = ({ Cancel { width: 52, getActions: (params: GridRowParams) => [ } label="Edit Redirect" onClick={() => handleAction(params.row.id, "edit")} @@ -128,6 +129,7 @@ export const Redirects = () => { }} />, } label="Delete Redirect" onClick={() => handleAction(params.row.id, "delete")} From bb137bddc337d8a41c02f6382fc2fd8f5fbf62c3 Mon Sep 17 00:00:00 2001 From: Nar -- <28705606+finnar-bin@users.noreply.github.com> Date: Mon, 19 May 2025 15:05:32 +0800 Subject: [PATCH 10/12] Remove unused imports Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js b/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js index 3dc7db533..f0b8e3728 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js +++ b/src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js @@ -56,7 +56,6 @@ import { FieldError } from "../../components/Editor/FieldError"; import { AIGeneratorProvider } from "../../../../../../shell/components/withAi/AIGeneratorProvider"; import { fetchItemPublishings, - fetchItems, } from "../../../../../../shell/store/content"; import { Redirects } from "../Redirects"; import RedirectsDialogContextProvider from "../../../../../seo/src/app/components/RedirectsDialogProvider"; From efcdd0603c4c22c11ffc8a96aac6c0c53f0c0b9b Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Wed, 21 May 2025 09:12:56 +0800 Subject: [PATCH 11/12] Simplified more actions click handler --- .../views/Redirects/DeleteRedirectModal.tsx | 25 +++++---- .../src/app/views/Redirects/index.tsx | 56 ++++++++----------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx b/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx index 977379004..efda39453 100644 --- a/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx +++ b/src/apps/content-editor/src/app/views/Redirects/DeleteRedirectModal.tsx @@ -17,10 +17,7 @@ import { useDispatch } from "react-redux"; import { useDomain } from "../../../../../../shell/hooks/use-domain"; import { useDeleteRedirectMutation } from "../../../../../../shell/services/instance"; import { notify } from "../../../../../../shell/store/notifications"; -import { - Redirects, - RedirectsTargetType, -} from "../../../../../../shell/services/types"; +import { RedirectsTargetType } from "../../../../../../shell/services/types"; const HTTP_CODE_OPTIONS = { 301: "301 - Permanent Redirect", @@ -29,12 +26,18 @@ const HTTP_CODE_OPTIONS = { type DeleteRedirectModalProps = { targetPath: string; - data: Redirects; + incomingPath: string; + ZUID: string; + httpCode: number; + targetType: RedirectsTargetType; onClose: () => void; }; export const DeleteRedirectModal = ({ targetPath, - data, + incomingPath, + ZUID, + httpCode, + targetType, onClose, }: DeleteRedirectModalProps) => { const domain = useDomain(); @@ -49,7 +52,7 @@ export const DeleteRedirectModal = ({ onClose(); dispatch( notify({ - message: `Redirect Deleted: ${data.path}`, + message: `Redirect Deleted: ${incomingPath}`, kind: "error", }) ); @@ -57,7 +60,7 @@ export const DeleteRedirectModal = ({ }, [isRedirectDeleted]); const handleDelete = () => { - deleteRedirect({ ZUID: data.ZUID }); + deleteRedirect({ ZUID }); }; return ( @@ -93,7 +96,7 @@ export const DeleteRedirectModal = ({ Delete Redirect:  - {data.path} + {incomingPath} @@ -117,7 +120,7 @@ export const DeleteRedirectModal = ({ HTTP Code - {HTTP_CODE_OPTIONS[data.code as keyof typeof HTTP_CODE_OPTIONS]} + {HTTP_CODE_OPTIONS[httpCode as keyof typeof HTTP_CODE_OPTIONS]} Redirect Type - + { - const [redirectToDelete, setRedirectToDelete] = - useState(null); + const [redirectToDelete, setRedirectToDelete] = useState(null); const domain = useDomain(); const { openCreateForm } = useRedirectsDialog(); const { itemZUID } = useParams<{ @@ -51,32 +57,6 @@ export const Redirects = () => { ); }, [redirects, web]); - const handleAction = (zuid: string, action: "edit" | "delete") => { - const matchedRedirect = redirectsHere.find( - (redirect) => redirect.ZUID === zuid - ); - - if (!matchedRedirect) return; - - switch (action) { - case "edit": - openCreateForm({ - ZUID: matchedRedirect?.ZUID, - targetType: matchedRedirect?.targetType, - code: matchedRedirect?.code, - target: matchedRedirect?.target, - path: matchedRedirect?.path, - }); - break; - case "delete": - setRedirectToDelete(matchedRedirect); - break; - - default: - break; - } - }; - const columns: GridColDef[] = [ { field: "incomingPath", @@ -122,7 +102,15 @@ export const Redirects = () => { data-cy="EditRedirect" icon={} label="Edit Redirect" - onClick={() => handleAction(params.row.id, "edit")} + onClick={() => { + openCreateForm({ + ZUID: params.row?.id, + targetType: params.row?.targetType, + code: params.row?.httpCode, + target: params.row?.targetPath, + path: params.row?.incomingPath, + }); + }} showInMenu sx={{ width: 240, @@ -132,7 +120,7 @@ export const Redirects = () => { data-cy="DeleteRedirect" icon={} label="Delete Redirect" - onClick={() => handleAction(params.row.id, "delete")} + onClick={() => setRedirectToDelete(params.row)} showInMenu sx={{ width: 240, @@ -150,6 +138,7 @@ export const Redirects = () => { incomingPath: redirect.path, httpCode: redirect.code, targetPath: redirect.target, + targetType: redirect.targetType, })); }, [redirectsHere]); @@ -175,7 +164,10 @@ export const Redirects = () => { {!!redirectToDelete && ( setRedirectToDelete(null)} /> From 0301784ab1a326113957024748d5010ae558865b Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Tue, 27 May 2025 06:42:37 +0800 Subject: [PATCH 12/12] Fix failing test --- cypress/e2e/content/redirects.spec.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/content/redirects.spec.js b/cypress/e2e/content/redirects.spec.js index 2db307802..12dbac569 100644 --- a/cypress/e2e/content/redirects.spec.js +++ b/cypress/e2e/content/redirects.spec.js @@ -54,9 +54,10 @@ describe("Content item redirects", () => { cy.getBySelector("ConfirmDeleteRedirect").click(); cy.wait("@redirects").then(() => { - cy.get(".MuiDataGrid-cell") - .contains(`/test-redirect/${NOW}/updated`, { timeout: 10000 }) - .should("not.exist"); + cy.getBySelector("toast").should( + "contain.text", + `Redirect Deleted: /test-redirect/${NOW}/updated` + ); }); }); });