mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-15 03:01:37 -05:00
Added edit recommendations modal (#17889)
refs https://github.com/TryGhost/Product/issues/3794
This commit is contained in:
parent
f56c36ca24
commit
3a946de06d
7 changed files with 135 additions and 28 deletions
|
@ -47,7 +47,7 @@ export const useDeleteRecommendation = createMutation<RecommendationDeleteRespon
|
|||
}
|
||||
});
|
||||
|
||||
export const useEditRecommendation = createMutation<RecommendationEditResponseType, Recommendation>({
|
||||
export const useEditRecommendation = createMutation<RecommendationEditResponseType, Partial<Recommendation> & {id: string}>({
|
||||
method: 'PUT',
|
||||
path: recommendation => `/recommendations/${recommendation.id}/`,
|
||||
body: recommendation => ({recommendations: [recommendation]}),
|
||||
|
|
|
@ -58,7 +58,8 @@ export const modalRoutes = {
|
|||
showUser: 'users/show/:slug',
|
||||
showNewsletter: 'newsletters/show/:id',
|
||||
showTier: 'tiers/show/:id',
|
||||
showIntegration: 'integrations/show/:id'
|
||||
showIntegration: 'integrations/show/:id',
|
||||
editRecommendation: 'recommendations/:id'
|
||||
};
|
||||
|
||||
function getHashPath(urlPath: string | undefined) {
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import Button from '../../../admin-x-ds/global/Button';
|
||||
import EditRecommendationModal from './recommendations/EditRecommendationModal';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import React, {useState} from 'react';
|
||||
import RecommendationList from './recommendations/RecommendationList';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import TabView from '../../../admin-x-ds/global/TabView';
|
||||
import useDetailModalRoute from '../../../hooks/useDetailModalRoute';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {modalRoutes} from '../../providers/RoutingProvider';
|
||||
import {useBrowseRecommendations} from '../../../api/recommendations';
|
||||
|
||||
const Recommendations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
|
@ -14,7 +18,13 @@ const Recommendations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
} = useSettingGroup();
|
||||
const {data: {recommendations} = {}} = useBrowseRecommendations();
|
||||
const [selectedTab, setSelectedTab] = useState('your-recommendations');
|
||||
|
||||
|
||||
useDetailModalRoute({
|
||||
route: modalRoutes.editRecommendation,
|
||||
items: recommendations || [],
|
||||
showModal: recommendation => NiceModal.show(EditRecommendationModal, {recommendation})
|
||||
});
|
||||
|
||||
const {updateRoute} = useRouting();
|
||||
const openAddNewRecommendationModal = () => {
|
||||
updateRoute('recommendations/add');
|
||||
|
@ -38,7 +48,7 @@ const Recommendations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
contents: (<RecommendationList recommendations={[]} />)
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
return (
|
||||
<SettingGroup
|
||||
customButtons={buttons}
|
||||
|
@ -55,4 +65,4 @@ const Recommendations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Recommendations;
|
||||
export default Recommendations;
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import AddRecommendationModal from './AddRecommendationModal';
|
||||
import Avatar from '../../../../admin-x-ds/global/Avatar';
|
||||
import Form from '../../../../admin-x-ds/global/form/Form';
|
||||
import Modal from '../../../../admin-x-ds/global/modal/Modal';
|
||||
import NiceModal, {useModal} from '@ebay/nice-modal-react';
|
||||
import React from 'react';
|
||||
import TextArea from '../../../../admin-x-ds/global/form/TextArea';
|
||||
import RecommendationReasonForm from './RecommendationReasonForm';
|
||||
import useForm from '../../../../hooks/useForm';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import {EditOrAddRecommendation, useAddRecommendation} from '../../../../api/recommendations';
|
||||
|
@ -88,25 +86,7 @@ const AddRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({r
|
|||
}
|
||||
}}
|
||||
>
|
||||
<Form
|
||||
marginBottom={false}
|
||||
marginTop
|
||||
>
|
||||
<div className='mb-4 flex items-center gap-3 rounded-sm border border-grey-300 p-3'>
|
||||
{(recommendation.favicon || recommendation.featured_image) && <Avatar image={recommendation.favicon ?? recommendation.featured_image!} labelColor='white' />}
|
||||
<div className={`flex grow flex-col`}>
|
||||
<span className='mb-0.5 font-medium'>{recommendation.title}</span>
|
||||
<span className='text-xs leading-snug text-grey-700'>{recommendation.url}</span>
|
||||
</div>
|
||||
</div>
|
||||
<TextArea
|
||||
clearBg={true}
|
||||
rows={3}
|
||||
title="Reason for recommending"
|
||||
value={formState.reason ?? ''}
|
||||
onChange={e => updateForm(state => ({...state, reason: e.target.value}))}
|
||||
/>
|
||||
</Form>
|
||||
<RecommendationReasonForm formState={formState} updateForm={updateForm} />
|
||||
</Modal>;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import Modal from '../../../../admin-x-ds/global/modal/Modal';
|
||||
import NiceModal, {useModal} from '@ebay/nice-modal-react';
|
||||
import React from 'react';
|
||||
import RecommendationReasonForm from './RecommendationReasonForm';
|
||||
import useForm from '../../../../hooks/useForm';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import {Recommendation, useEditRecommendation} from '../../../../api/recommendations';
|
||||
import {showToast} from '../../../../admin-x-ds/global/Toast';
|
||||
import {toast} from 'react-hot-toast';
|
||||
|
||||
interface AddRecommendationModalProps {
|
||||
recommendation: Recommendation,
|
||||
animate?: boolean
|
||||
}
|
||||
|
||||
const EditRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({recommendation, animate}) => {
|
||||
const modal = useModal();
|
||||
const {updateRoute} = useRouting();
|
||||
const {mutateAsync: editRecommendation} = useEditRecommendation();
|
||||
|
||||
const {formState, updateForm, handleSave, saveState} = useForm({
|
||||
initialState: {
|
||||
...recommendation
|
||||
},
|
||||
onSave: async () => {
|
||||
await editRecommendation(formState);
|
||||
modal.remove();
|
||||
updateRoute('recommendations');
|
||||
},
|
||||
onValidate: () => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
return newErrors;
|
||||
}
|
||||
});
|
||||
|
||||
let okLabel = 'Save';
|
||||
|
||||
if (saveState === 'saving') {
|
||||
okLabel = 'Saving...';
|
||||
} else if (saveState === 'saved') {
|
||||
okLabel = 'Saved';
|
||||
}
|
||||
|
||||
return <Modal
|
||||
afterClose={() => {
|
||||
// Closed without saving: reset route
|
||||
updateRoute('recommendations');
|
||||
}}
|
||||
animate={animate ?? true}
|
||||
cancelLabel={'Cancel'}
|
||||
okColor='black'
|
||||
okLabel={okLabel}
|
||||
size='sm'
|
||||
testId='edit-recommendation-modal'
|
||||
title={'Edit recommendation'}
|
||||
onOk={async () => {
|
||||
if (saveState === 'saving') {
|
||||
// Already saving
|
||||
return;
|
||||
}
|
||||
|
||||
toast.remove();
|
||||
if (await handleSave({force: true})) {
|
||||
// Already handled
|
||||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RecommendationReasonForm formState={formState} updateForm={updateForm as any} />
|
||||
</Modal>;
|
||||
};
|
||||
|
||||
export default NiceModal.create(EditRecommendationModalConfirm);
|
|
@ -7,13 +7,16 @@ import React from 'react';
|
|||
import Table from '../../../../admin-x-ds/global/Table';
|
||||
import TableCell from '../../../../admin-x-ds/global/TableCell';
|
||||
import TableRow from '../../../../admin-x-ds/global/TableRow';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import {Recommendation, useDeleteRecommendation} from '../../../../api/recommendations';
|
||||
import {modalRoutes} from '../../../providers/RoutingProvider';
|
||||
|
||||
interface RecommendationListProps {
|
||||
recommendations: Recommendation[]
|
||||
}
|
||||
|
||||
const RecommendationItem: React.FC<{recommendation: Recommendation}> = ({recommendation}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
const {mutateAsync: deleteRecommendation} = useDeleteRecommendation();
|
||||
|
||||
const action = (
|
||||
|
@ -32,7 +35,9 @@ const RecommendationItem: React.FC<{recommendation: Recommendation}> = ({recomme
|
|||
}} />
|
||||
);
|
||||
|
||||
const showDetails = () => {};
|
||||
const showDetails = () => {
|
||||
updateRoute({route: modalRoutes.editRecommendation, params: {id: recommendation.id}});
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow action={action} hideActions>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import Avatar from '../../../../admin-x-ds/global/Avatar';
|
||||
import Form from '../../../../admin-x-ds/global/form/Form';
|
||||
import React from 'react';
|
||||
import TextArea from '../../../../admin-x-ds/global/form/TextArea';
|
||||
import {EditOrAddRecommendation, Recommendation} from '../../../../api/recommendations';
|
||||
|
||||
interface Props<T extends EditOrAddRecommendation> {
|
||||
formState: T,
|
||||
updateForm: (fn: (state: T) => T) => void
|
||||
}
|
||||
|
||||
const RecommendationReasonForm: React.FC<Props<EditOrAddRecommendation | Recommendation>> = ({formState, updateForm}) => {
|
||||
return <Form
|
||||
marginBottom={false}
|
||||
marginTop
|
||||
>
|
||||
<div className='mb-4 flex items-center gap-3 rounded-sm border border-grey-300 p-3'>
|
||||
{(formState.favicon || formState.featured_image) && <Avatar image={formState.favicon ?? formState.featured_image!} labelColor='white' />}
|
||||
<div className={`flex grow flex-col`}>
|
||||
<span className='mb-0.5 font-medium'>{formState.title}</span>
|
||||
<span className='text-xs leading-snug text-grey-700'>{formState.url}</span>
|
||||
</div>
|
||||
</div>
|
||||
<TextArea
|
||||
clearBg={true}
|
||||
rows={3}
|
||||
title="Reason for recommending"
|
||||
value={formState.reason ?? ''}
|
||||
onChange={e => updateForm(state => ({...state, reason: e.target.value}))}
|
||||
/>
|
||||
</Form>;
|
||||
};
|
||||
|
||||
export default RecommendationReasonForm;
|
Loading…
Add table
Reference in a new issue