Added limit to the description size, when adding/editing a recommendation (#18380)

closes https://github.com/TryGhost/Product/issues/3960

- recommendation description is limited to 200 characters, so that it renders nicely in Portal
This commit is contained in:
Sag 2023-09-27 15:04:25 +02:00 committed by GitHub
parent 5e85d2f58f
commit 488ef87a7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 38 additions and 18 deletions

View File

@ -9,8 +9,7 @@ import useRouting from '../../../../hooks/useRouting';
import {AlreadyExistsError} from '../../../../utils/errors';
import {EditOrAddRecommendation, RecommendationResponseType, useGetRecommendationByUrl} from '../../../../api/recommendations';
import {RoutingModalProps} from '../../../providers/RoutingProvider';
import {showToast} from '../../../../admin-x-ds/global/Toast';
import {toast} from 'react-hot-toast';
import {dismissAllToasts, showToast} from '../../../../admin-x-ds/global/Toast';
import {trimSearchAndHash} from '../../../../utils/url';
import {useExternalGhostSite} from '../../../../api/external-ghost-site';
import {useGetOembed} from '../../../../api/oembed';
@ -138,7 +137,7 @@ const AddRecommendationModal: React.FC<RoutingModalProps & AddRecommendationModa
return;
}
toast.remove();
dismissAllToasts();
try {
await handleSave({force: true});
} catch (e) {

View File

@ -20,7 +20,7 @@ const AddRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({r
const {mutateAsync: addRecommendation} = useAddRecommendation();
const handleError = useHandleError();
const {formState, updateForm, handleSave, saveState, errors} = useForm({
const {formState, updateForm, handleSave, saveState, errors, clearError} = useForm({
initialState: {
...recommendation
},
@ -39,6 +39,10 @@ const AddRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({r
if (!formState.title) {
newErrors.title = 'Title is required';
}
if (formState.reason && formState.reason.length > 200) {
newErrors.reason = 'Description cannot be longer than 200 characters';
}
return newErrors;
}
});
@ -115,7 +119,7 @@ const AddRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({r
}
}}
>
<RecommendationReasonForm errors={errors} formState={formState} showURL={false} updateForm={updateForm}/>
<RecommendationReasonForm clearError={clearError} errors={errors} formState={formState} showURL={false} updateForm={updateForm}/>
</Modal>;
};

View File

@ -8,8 +8,7 @@ import useHandleError from '../../../../utils/api/handleError';
import useRouting from '../../../../hooks/useRouting';
import {Recommendation, useDeleteRecommendation, useEditRecommendation} from '../../../../api/recommendations';
import {RoutingModalProps} from '../../../providers/RoutingProvider';
import {showToast} from '../../../../admin-x-ds/global/Toast';
import {toast} from 'react-hot-toast';
import {dismissAllToasts, showToast} from '../../../../admin-x-ds/global/Toast';
interface EditRecommendationModalProps {
recommendation: Recommendation,
@ -23,7 +22,7 @@ const EditRecommendationModal: React.FC<RoutingModalProps & EditRecommendationMo
const {mutateAsync: deleteRecommendation} = useDeleteRecommendation();
const handleError = useHandleError();
const {formState, updateForm, handleSave, saveState, errors} = useForm({
const {formState, updateForm, handleSave, saveState, errors, clearError} = useForm({
initialState: {
...recommendation
},
@ -35,9 +34,15 @@ const EditRecommendationModal: React.FC<RoutingModalProps & EditRecommendationMo
onSaveError: handleError,
onValidate: () => {
const newErrors: Record<string, string> = {};
if (!formState.title) {
newErrors.title = 'Title is required';
}
if (formState.reason && formState.reason.length > 200) {
newErrors.reason = 'Description cannot be longer than 200 characters';
}
return newErrors;
}
});
@ -103,10 +108,10 @@ const EditRecommendationModal: React.FC<RoutingModalProps & EditRecommendationMo
return;
}
toast.remove();
if (await handleSave({force: true})) {
// Already handled
} else {
dismissAllToasts();
try {
await handleSave({force: true});
} catch (e) {
showToast({
type: 'pageError',
message: 'One or more fields have errors, please double check that you\'ve filled all mandatory fields.'
@ -114,7 +119,7 @@ const EditRecommendationModal: React.FC<RoutingModalProps & EditRecommendationMo
}
}}
>
<RecommendationReasonForm errors={errors} formState={formState} showURL={true} updateForm={updateForm as any}/>
<RecommendationReasonForm clearError={clearError} errors={errors} formState={formState} showURL={true} updateForm={updateForm as any}/>
</Modal>;
};

View File

@ -13,10 +13,14 @@ interface Props<T extends EditOrAddRecommendation> {
showURL?: boolean,
formState: T,
errors: ErrorMessages,
updateForm: (fn: (state: T) => T) => void
updateForm: (fn: (state: T) => T) => void,
clearError?: (key: keyof ErrorMessages) => void
}
const RecommendationReasonForm: React.FC<Props<EditOrAddRecommendation | Recommendation>> = ({showURL, formState, updateForm, errors}) => {
const RecommendationReasonForm: React.FC<Props<EditOrAddRecommendation | Recommendation>> = ({showURL, formState, updateForm, errors, clearError}) => {
const [reasonLength, setReasonLength] = React.useState(formState?.reason?.length || 0);
const reasonLengthColor = reasonLength > 200 ? 'text-red' : 'text-green';
return <Form
marginBottom={false}
marginTop
@ -53,15 +57,23 @@ const RecommendationReasonForm: React.FC<Props<EditOrAddRecommendation | Recomme
hint={errors.title}
title="Title"
value={formState.title ?? ''}
onChange={e => updateForm(state => ({...state, title: e.target.value}))}
onChange={(e) => {
clearError?.('title');
updateForm(state => ({...state, title: e.target.value}));
}}
/>
<TextArea
clearBg={true}
hint='Optional, try to keep it under 156 characters'
error={Boolean(errors.reason)}
hint={errors.reason || <>Max. <strong>200</strong> characters. You&apos;ve used <strong className={reasonLengthColor}>{reasonLength}</strong></>}
rows={3}
title="Short description"
value={formState.reason ?? ''}
onChange={e => updateForm(state => ({...state, reason: e.target.value}))}
onChange={(e) => {
clearError?.('reason');
setReasonLength(e.target.value.length);
updateForm(state => ({...state, reason: e.target.value}));
}}
/>
</Form>;
};