From 9e0d4d44c5ed752135d18fcf73ea3cd8166bbb67 Mon Sep 17 00:00:00 2001 From: SurabhiKeesara Date: Wed, 6 Dec 2023 23:58:35 -0500 Subject: [PATCH] Move delete functionality to clean branch --- src/api/protectedApiClient.ts | 10 +++ src/api/test/protectedApiClient.test.ts | 27 +++++++ src/components/careEntry/index.tsx | 25 ++----- src/components/confirmationModal/index.tsx | 50 +++++++++++++ src/components/themedComponents/index.tsx | 8 ++ src/components/treePage/siteImageCarousel.tsx | 74 +++++++++++++++++-- src/containers/treePage/ducks/types.ts | 1 + .../treePage/test/selectors.test.ts | 5 ++ 8 files changed, 176 insertions(+), 24 deletions(-) create mode 100644 src/components/confirmationModal/index.tsx diff --git a/src/api/protectedApiClient.ts b/src/api/protectedApiClient.ts index e9410fb6..71339a01 100644 --- a/src/api/protectedApiClient.ts +++ b/src/api/protectedApiClient.ts @@ -127,6 +127,7 @@ export interface ProtectedApiClient { ) => Promise; readonly addSites: (request: AddSitesRequest) => Promise; readonly sendEmail: (request: SendEmailRequest) => Promise; + readonly deleteImage: (imageId: number) => Promise; readonly filterSites: ( params: FilterSitesParams, ) => Promise; @@ -197,6 +198,8 @@ export const ParameterizedApiRoutes = { UPDATE_SITE: (siteId: number): string => `${baseSiteRoute}${siteId}/update`, NAME_SITE_ENTRY: (siteId: number): string => `${baseSiteRoute}${siteId}/name_entry`, + DELETE_IMAGE: (imageId: number): string => + `${baseSiteRoute}site_image/${imageId}`, }; export const ParameterizedAdminApiRoutes = { @@ -556,6 +559,12 @@ const sendEmail = (request: SendEmailRequest): Promise => { ); }; +const deleteImage = (imageId: number): Promise => { + return AppAxiosInstance.delete( + ParameterizedApiRoutes.DELETE_IMAGE(imageId), + ).then((res) => res.data); +}; + const filterSites = ( params: FilterSitesParams, ): Promise => { @@ -611,6 +620,7 @@ const Client: ProtectedApiClient = Object.freeze({ nameSiteEntry, addSites, sendEmail, + deleteImage, filterSites, }); diff --git a/src/api/test/protectedApiClient.test.ts b/src/api/test/protectedApiClient.test.ts index 27be87dd..a0ca463b 100644 --- a/src/api/test/protectedApiClient.test.ts +++ b/src/api/test/protectedApiClient.test.ts @@ -1489,6 +1489,33 @@ describe('Protected API Client Tests', () => { expect(result).toEqual(response); }); }); + + describe('deleteSiteImage', () => { + it('makes the right request', async () => { + const response = 'Image Deleted Correctly'; + + nock(BASE_URL) + .delete(ParameterizedApiRoutes.DELETE_IMAGE(1)) + .reply(200, response); + + const result = await ProtectedApiClient.deleteImage(1); + + expect(result).toEqual(response); + }); + it('makes a bad request', async () => { + const response = 'Invalid Image ID'; + + nock(BASE_URL) + .delete(ParameterizedApiRoutes.DELETE_IMAGE(-1)) + .reply(400, response); + + const result = await ProtectedApiClient.deleteImage(-1).catch( + (err) => err.response.data, + ); + + expect(result).toEqual(response); + }); + }); }); describe('updateSite', () => { diff --git a/src/components/careEntry/index.tsx b/src/components/careEntry/index.tsx index 44909a93..b09a0587 100644 --- a/src/components/careEntry/index.tsx +++ b/src/components/careEntry/index.tsx @@ -26,6 +26,7 @@ import { C4CState } from '../../store'; import { useTranslation } from 'react-i18next'; import { site } from '../../constants'; import { n } from '../../utils/stringFormat'; +import ConfirmationModal from '../confirmationModal'; const Entry = styled.div` margin: 15px; @@ -60,15 +61,6 @@ const DeleteActivityButton = styled(LinkButton)` } `; -const ConfirmDelete = styled(Button)` - margin: 10px; - padding-left: 10px; - - & :hover { - background-color: ${LIGHT_GREY}; - } -`; - interface CareEntryProps { readonly activity: TreeCare; } @@ -184,19 +176,14 @@ const CareEntry: React.FC = ({ activity }) => { initialDate={treeCareToMoment(activity)} /> - setShowDeleteForm(false)} onCancel={() => setShowDeleteForm(false)} - footer={null} - closeIcon={} - > -

{t('delete_message')}

- - {t('delete')} - -
+ title={t('delete_title')} + confirmationMessage={t('delete_message')} + onConfirm={onClickDeleteActivity} + /> ); }; diff --git a/src/components/confirmationModal/index.tsx b/src/components/confirmationModal/index.tsx new file mode 100644 index 00000000..8dc06daf --- /dev/null +++ b/src/components/confirmationModal/index.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Button, Modal } from 'antd'; +import { StyledClose } from '../themedComponents'; +import styled from 'styled-components'; +import { LIGHT_GREY } from '../../utils/colors'; + +const ConfirmDeleteButton = styled(Button)` + margin: 10px; + padding-left: 10px; + + & :hover { + background-color: ${LIGHT_GREY}; + } +`; + +interface ConfirmationModalProps { + visible: boolean; + onOk: () => void; + onCancel: () => void; + title: string; + confirmationMessage: string; + onConfirm: () => void; +} + +const ConfirmationModal: React.FC = ({ + visible, + onOk, + onCancel, + confirmationMessage, + title, + onConfirm, +}) => { + return ( + <> + } + > +

{confirmationMessage}

+ Delete +
+ + ); +}; + +export default ConfirmationModal; diff --git a/src/components/themedComponents/index.tsx b/src/components/themedComponents/index.tsx index 5b9981a1..03f95d6d 100644 --- a/src/components/themedComponents/index.tsx +++ b/src/components/themedComponents/index.tsx @@ -138,6 +138,14 @@ export const MenuLinkButton = styled(LinkButton)` text-align: left; `; +export const ConfirmDeleteButton = styled(Button)` + margin: 10px; + padding-left: 10px; + & :hover { + background-color: ${LIGHT_GREY}; + } +`; + export const MainContent = styled.div` height: 100%; `; diff --git a/src/components/treePage/siteImageCarousel.tsx b/src/components/treePage/siteImageCarousel.tsx index a86964e8..341a5e1f 100644 --- a/src/components/treePage/siteImageCarousel.tsx +++ b/src/components/treePage/siteImageCarousel.tsx @@ -1,15 +1,24 @@ import React, { useState } from 'react'; -import { Carousel } from 'antd'; +import { Carousel, message, Space } from 'antd'; import LeftOutlined from '@ant-design/icons/lib/icons/LeftOutlined'; import RightOutlined from '@ant-design/icons/lib/icons/RightOutlined'; +import { isAdmin, getUserID } from '../../auth/ducks/selectors'; import { useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; +import { getSiteData } from '../../containers/treePage/ducks/thunks'; +import { TreeParams } from '../../containers/treePage'; +import { useParams } from 'react-router-dom'; import { getLatestEntrySiteImages } from '../../containers/treePage/ducks/selectors'; import { C4CState } from '../../store'; import { n } from '../../utils/stringFormat'; import { site } from '../../constants'; +import { LinkButton } from '../linkButton'; +import { LIGHT_GREY, LIGHT_RED, WHITE } from '../../utils/colors'; +import protectedApiClient from '../../api/protectedApiClient'; +import ConfirmationModal from '../confirmationModal'; const CarouselContainer = styled.div` margin-top: 20px; @@ -50,8 +59,24 @@ const FooterContainer = styled.div` margin-top: 10px; `; +export const DeleteSiteImageButton = styled(LinkButton)` + color: ${WHITE}; + margin: 10px; + padding: 0px 10px; + background: ${LIGHT_RED}; + border: none; + & :hover { + color: ${LIGHT_RED}; + background-color: ${LIGHT_GREY}; + } +`; + export const SiteImageCarousel: React.FC = () => { const { t } = useTranslation(n(site, 'treePage'), { nsMode: 'fallback' }); + const dispatch = useDispatch(); + const id = Number(useParams().id); + + const [showDeleteForm, setShowDeleteForm] = useState(false); const latestEntrySiteImages = useSelector((state: C4CState) => { return getLatestEntrySiteImages(state.siteState.siteData); @@ -61,7 +86,23 @@ export const SiteImageCarousel: React.FC = () => { const onAfterChange = (currentSlide: number) => setCurrSlideIndex(currentSlide); + function onClickDeleteImage(imageId: number) { + protectedApiClient + .deleteImage(imageId) + .then((ok) => { + message.success('success'); + setShowDeleteForm(false); + }) + .finally(() => dispatch(getSiteData(id))); + } + + const userIsAdmin: boolean = useSelector((state: C4CState) => + isAdmin(state.authenticationState.tokens), + ); + const userId: number = useSelector((state: C4CState) => + getUserID(state.authenticationState.tokens), + ); return ( <> {latestEntrySiteImages.length > 0 && ( @@ -87,11 +128,34 @@ export const SiteImageCarousel: React.FC = () => { 'Anonymous', })} -
- {latestEntrySiteImages[currSlideIndex].uploadedAt || - t('site_image.no_upload_date')} -
+ +
+ {latestEntrySiteImages[currSlideIndex].uploadedAt || + t('site_image.no_upload_date')} +
+
+ {(latestEntrySiteImages[currSlideIndex].uploaderId === userId || + userIsAdmin) && ( + setShowDeleteForm(!showDeleteForm)} + > + Delete + + )} +
+
+ setShowDeleteForm(false)} + onCancel={() => setShowDeleteForm(false)} + title="Delete Site Image" + confirmationMessage="Are you sure you want to delete this image?" + onConfirm={() => + onClickDeleteImage(latestEntrySiteImages[currSlideIndex].imageId) + } + /> )} diff --git a/src/containers/treePage/ducks/types.ts b/src/containers/treePage/ducks/types.ts index f7482e1c..ad041ef4 100644 --- a/src/containers/treePage/ducks/types.ts +++ b/src/containers/treePage/ducks/types.ts @@ -295,6 +295,7 @@ export interface SiteEntryImage { uploaderUsername: string; uploadedAt: string; imageUrl: string; + uploaderId: number; } export interface TreeBenefits { diff --git a/src/containers/treePage/test/selectors.test.ts b/src/containers/treePage/test/selectors.test.ts index 43fcf202..9f4ceb7f 100644 --- a/src/containers/treePage/test/selectors.test.ts +++ b/src/containers/treePage/test/selectors.test.ts @@ -124,12 +124,14 @@ describe('Tree Page Selectors', () => { imageUrl: 'http://www.some-address.com', uploadedAt: '09/06/2023', uploaderUsername: 'First Last', + uploaderId: 1, }, { imageId: 2, imageUrl: 'http://www.some-other-address.com', uploadedAt: '01/01/2023', uploaderUsername: 'Hello World', + uploaderId: 2, }, ], }, @@ -147,6 +149,7 @@ describe('Tree Page Selectors', () => { imageUrl: 'http://www.should-not-be-returned.com', uploadedAt: '01/01/2022', uploaderUsername: 'Code4Community', + uploaderId: 1, }, ], }, @@ -281,12 +284,14 @@ describe('Tree Page Selectors', () => { imageUrl: 'http://www.some-address.com', uploadedAt: '09/06/2023', uploaderUsername: 'First Last', + uploaderId: 1, }, { imageId: 2, imageUrl: 'http://www.some-other-address.com', uploadedAt: '01/01/2023', uploaderUsername: 'Hello World', + uploaderId: 2, }, ];