Skip to content

Commit

Permalink
[Content] SEO tab VQA updates (#2959)
Browse files Browse the repository at this point in the history
Resolves the ff:
-
#2933 (comment)
-
#2933 (comment)
  • Loading branch information
finnar-bin authored Sep 20, 2024
1 parent ca3b436 commit ea55995
Show file tree
Hide file tree
Showing 27 changed files with 412 additions and 268 deletions.
6 changes: 0 additions & 6 deletions src/apps/content-editor/src/app/components/Editor/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,8 @@ export default memo(function Editor({
onSave,
itemZUID,
modelZUID,
saveClicked,
onUpdateFieldErrors,
fieldErrors,
hasErrors,
}) {
const dispatch = useDispatch();
const isNewItem = itemZUID.slice(0, 3) === "new";
Expand Down Expand Up @@ -347,10 +345,6 @@ export default memo(function Editor({
return (
<ThemeProvider theme={theme}>
<div className={styles.Fields}>
{saveClicked && hasErrors && (
<FieldError errors={fieldErrors} fields={activeFields} />
)}

{activeFields.length ? (
activeFields.map((field) => {
return (
Expand Down
15 changes: 13 additions & 2 deletions src/apps/content-editor/src/app/components/Editor/FieldError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import { Error } from "./Field/FieldShell";
import { ContentModelField } from "../../../../../../shell/services/types";
import pluralizeWord from "../../../../../../utility/pluralizeWord";

const SEO_FIELD_LABELS = {
metaDescription: "Meta Description",
metaTitle: "Meta Title",
metaKeywords: "Meta Keywords",
metaLinkText: "Navigation Title",
parentZUID: "Page Parent",
pathPart: "URL Path Part",
};

type FieldErrorProps = {
errors: Record<string, Error>;
fields: ContentModelField[];
Expand Down Expand Up @@ -74,10 +83,12 @@ export const FieldError = ({ errors, fields }: FieldErrorProps) => {
const fieldData = fields?.find((field) => field.name === name);

return {
label: fieldData?.label,
label:
fieldData?.label ||
SEO_FIELD_LABELS[name as keyof typeof SEO_FIELD_LABELS],
errorMessages,
sort: fieldData?.sort,
ZUID: fieldData?.ZUID,
ZUID: fieldData?.ZUID || name,
};
});

Expand Down
44 changes: 36 additions & 8 deletions src/apps/content-editor/src/app/views/ItemCreate/ItemCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
import { SchedulePublish } from "../../../../../../shell/components/SchedulePublish";
import { Meta } from "../ItemEdit/Meta";
import { SocialMediaPreview } from "../ItemEdit/Meta/SocialMediaPreview";
import { FieldError } from "../../components/Editor/FieldError";

export type ActionAfterSave =
| ""
Expand All @@ -55,7 +56,7 @@ const selectSortedModelFields = createSelector(
.sort((a, b) => a.sort - b.sort)
);

type FieldError = {
type FieldErrors = {
[key: string]: Error;
};

Expand All @@ -78,9 +79,10 @@ export const ItemCreate = () => {
const [newItemZUID, setNewItemZUID] = useState();
const [isScheduleDialogOpen, setIsScheduleDialogOpen] = useState(false);
const [willRedirect, setWillRedirect] = useState(true);
const [fieldErrors, setFieldErrors] = useState<FieldError>({});
const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});
const [saveClicked, setSaveClicked] = useState(false);
const [hasSEOErrors, setHasSEOErrors] = useState(false);
// const [hasSEOErrors, setHasSEOErrors] = useState(false);
const [SEOErrors, setSEOErrors] = useState<FieldErrors>({});
const metaRef = useRef(null);

const [
Expand Down Expand Up @@ -141,6 +143,27 @@ export const ItemCreate = () => {
return hasErrors;
}, [fieldErrors]);

const hasSEOErrors = useMemo(() => {
const hasErrors = Object.values(SEOErrors)
?.map((error) => {
return Object.values(error) ?? [];
})
?.flat()
.some((error) => !!error);

return hasErrors;
}, [SEOErrors]);

const activeFields = useMemo(() => {
if (fields?.length) {
return fields.filter(
(field) => !field.deletedAt && !["og_image"].includes(field.name)
);
}

return [];
}, [fields]);

const loadItemFields = async (modelZUID: string) => {
setLoading(true);
try {
Expand Down Expand Up @@ -360,9 +383,14 @@ export const ItemCreate = () => {
gap={4}
>
<Box width="60%" height="100%">
{saveClicked && (hasErrors || hasSEOErrors) && (
<FieldError
errors={{ ...fieldErrors, ...SEOErrors }}
fields={activeFields}
/>
)}
<Editor
// @ts-ignore no types
hasErrors={hasErrors}
itemZUID={itemZUID}
item={item}
items={content}
Expand All @@ -375,10 +403,9 @@ export const ItemCreate = () => {
loading={loading}
saving={saving}
isDirty={item?.dirty}
saveClicked={saveClicked}
fieldErrors={fieldErrors}
// @ts-ignore untyped component
onUpdateFieldErrors={(errors: FieldError) => {
onUpdateFieldErrors={(errors: FieldErrors) => {
setFieldErrors(errors);
}}
/>
Expand All @@ -389,11 +416,12 @@ export const ItemCreate = () => {
}}
/>
<Meta
onUpdateSEOErrors={(hasErrors) => {
setHasSEOErrors(hasErrors);
onUpdateSEOErrors={(errors: FieldErrors) => {
setSEOErrors(errors);
}}
isSaving={saving}
ref={metaRef}
errors={SEOErrors}
/>
</Box>
<ThemeProvider theme={theme}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Actions } from "./Actions";
import { useLocalStorage } from "react-use";
import { useContext } from "react";
import { DuoModeContext } from "../../../../../../../shell/contexts/duoModeContext";
import { FieldError } from "../../../components/Editor/FieldError";

export default function Content(props) {
const [showSidebar, setShowSidebar] = useLocalStorage(
Expand Down Expand Up @@ -71,6 +72,12 @@ export default function Content(props) {
flex="0 1 auto"
>
<Box width="100%">
{props.saveClicked && props.hasErrors && (
<FieldError
errors={props.fieldErrors}
fields={props.activeFields}
/>
)}
<Editor
// active={this.state.makeActive}
// scrolled={() => this.setState({ makeActive: "" })}
Expand Down
48 changes: 44 additions & 4 deletions src/apps/content-editor/src/app/views/ItemEdit/ItemEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { DuoModeContext } from "../../../../../../shell/contexts/duoModeContext"
import { useLocalStorage } from "react-use";
import { FreestyleWrapper } from "./FreestyleWrapper";
import { Meta } from "./Meta";
import { FieldError } from "../../components/Editor/FieldError";

const selectItemHeadTags = createSelector(
(state) => state.headTags,
Expand Down Expand Up @@ -96,7 +97,8 @@ export default function ItemEdit() {
const [notFound, setNotFound] = useState("");
const [saveClicked, setSaveClicked] = useState(false);
const [fieldErrors, setFieldErrors] = useState({});
const [hasSEOErrors, setHasSEOErrors] = useState(false);
const [SEOErrors, setSEOErrors] = useState({});
// const [hasSEOErrors, setHasSEOErrors] = useState(false);
const [headerTitle, setHeaderTitle] = useState("");
const { data: fields, isLoading: isLoadingFields } =
useGetContentModelFieldsQuery(modelZUID);
Expand Down Expand Up @@ -163,6 +165,12 @@ export default function ItemEdit() {
}
}, [loading]);

useEffect(() => {
setSaveClicked(false);
setFieldErrors({});
setSEOErrors({});
}, [location.pathname]);

const hasErrors = useMemo(() => {
const hasErrors = Object.values(fieldErrors)
?.map((error) => {
Expand All @@ -178,6 +186,27 @@ export default function ItemEdit() {
return hasErrors;
}, [fieldErrors]);

const hasSEOErrors = useMemo(() => {
const hasErrors = Object.values(SEOErrors)
?.map((error) => {
return Object.values(error) ?? [];
})
?.flat()
.some((error) => !!error);

return hasErrors;
}, [SEOErrors]);

const activeFields = useMemo(() => {
if (fields?.length) {
return fields.filter(
(field) => !field.deletedAt && !["og_image"].includes(field.name)
);
}

return [];
}, [fields]);

async function lockItem() {
setCheckingLock(true);
try {
Expand Down Expand Up @@ -369,12 +398,12 @@ export default function ItemEdit() {
dispatch(fetchAuditTrailDrafting(itemZUID));
} catch (err) {
console.error(err);
throw new Error(err);
// we need to set the item to dirty again because the save failed
dispatch({
type: "MARK_ITEM_DIRTY",
itemZUID,
});
throw new Error(err);
} finally {
if (isMounted.current) {
setSaving(false);
Expand Down Expand Up @@ -478,10 +507,20 @@ export default function ItemEdit() {
render={() => (
<Meta
ref={metaRef}
onUpdateSEOErrors={(hasErrors) => {
setHasSEOErrors(hasErrors);
onUpdateSEOErrors={(errors) => {
setSEOErrors(errors);
}}
isSaving={saving}
errors={SEOErrors}
errorComponent={
saveClicked &&
hasSEOErrors && (
<FieldError
errors={{ ...fieldErrors, ...SEOErrors }}
fields={activeFields}
/>
)
}
/>
)}
/>
Expand Down Expand Up @@ -556,6 +595,7 @@ export default function ItemEdit() {
}}
fieldErrors={fieldErrors}
hasErrors={hasErrors}
activeFields={activeFields}
/>
</ItemLockContext.Provider>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { Box, Stack, Typography, Chip } from "@mui/material";
import { Check, Add, Remove } from "@mui/icons-material";
import { Check, AddRounded, RemoveRounded } from "@mui/icons-material";
import { useMemo, useState } from "react";
import { useSelector } from "react-redux";
import { useParams } from "react-router";

import {
stripDashesAndSlashes,
stripDoubleSpace,
stripPunctuation,
} from "./index";
import { AppState } from "../../../../../../../../shell/store/types";
import { cleanContent } from "./index";
import { DYNAMIC_META_FIELD_NAMES } from "../index";

type MatchedWordsProps = {
uniqueNonCommonWordsArray: string[];
Expand All @@ -29,21 +26,21 @@ export const MatchedWords = ({
"metaTitle",
"metaKeywords",
"pathPart",
...DYNAMIC_META_FIELD_NAMES,
];

if (
item?.web &&
Object.values(item.web)?.length &&
item?.data &&
Object.values(item.data)?.length &&
uniqueNonCommonWordsArray?.length
) {
const metaWords = Object.entries(item.web)?.reduce(
const metaWords = Object.entries({ ...item.web, ...item.data })?.reduce(
(accu: string[], [fieldName, value]) => {
if (textMetaFieldNames.includes(fieldName) && !!value) {
const cleanedValue = stripDoubleSpace(
stripPunctuation(
stripDashesAndSlashes(value.trim().toLowerCase())
)
);
// Replace all new line characters with a space and remove all special characters
const cleanedValue = cleanContent(value);

accu = [...accu, ...cleanedValue?.split(" ")];

Expand All @@ -63,14 +60,19 @@ export const MatchedWords = ({
}

return [];
}, [uniqueNonCommonWordsArray, item?.web]);
}, [uniqueNonCommonWordsArray, item?.web, item?.data]);

return (
<Box mt={1.5} mb={2}>
<Typography variant="h6" color="text.secondary" fontWeight={700} mb={1}>
Content and Meta Matched Words
</Typography>
<Stack direction="row" gap={1} flexWrap="wrap">
{!contentAndMetaWordMatches?.length && (
<Typography variant="body2" color="text.secondary">
No Matching Words
</Typography>
)}
{contentAndMetaWordMatches
?.slice(0, showAll ? undefined : 9)
?.map((word) => (
Expand All @@ -87,7 +89,13 @@ export const MatchedWords = ({
label={`See ${showAll ? "Less" : "More"}`}
size="small"
variant="outlined"
icon={showAll ? <Remove color="action" /> : <Add color="action" />}
icon={
showAll ? (
<RemoveRounded color="action" />
) : (
<AddRounded color="action" />
)
}
onClick={() => setShowAll(!showAll)}
/>
)}
Expand Down
Loading

0 comments on commit ea55995

Please sign in to comment.