Skip to content

Commit

Permalink
Merge pull request #227 from wri/feature/delete-confirm-modal
Browse files Browse the repository at this point in the history
Confirm delete modal for removing documents and annexes
  • Loading branch information
tsubik authored Dec 10, 2024
2 parents 3a92b04 + b9d635e commit 6e87824
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
- name: Install API dependencies
run: |
sudo apt update --fix-missing
sudo apt-get -yqq install libpq-dev build-essential libcurl4-openssl-dev libgdal-dev
sudo apt-get -yqq install libpq-dev build-essential libcurl4-openssl-dev libgdal-dev imagemagick
npm install -g mjml
- name: Setup API Ruby
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ function DocumentsCertification(props) {
user.operator_ids.includes(+id))) && (
<DocCardUpload
{...doc}
title={intl.formatMessage({ id: 'operator-detail.license' })}
properties={{
type: 'operator',
id,
Expand Down
59 changes: 59 additions & 0 deletions components/ui/confirm-modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useState } from 'react';
import { useIntl } from 'react-intl';

import Spinner from 'components/ui/spinner';

const ConfirmModal = ({
title,
text,
confirmText,
cancelText,
onCancel,
onConfirm
}) => {
const intl = useIntl();
const [submitting, setSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const _cancelText = cancelText || intl.formatMessage({ id: 'cancel', defaultMessage: 'Cancel' });
const _confirmText = confirmText || intl.formatMessage({ id: 'confirm', defaultMessage: 'Confirm' });

const handleConfirm = () => {
setSubmitting(true);
onConfirm({
onSuccess: () => {
setErrorMessage(null);
},
onError: (errorMessage) => {
setSubmitting(false);
setErrorMessage(errorMessage);
}
});
};

return (
<div className="c-confirm-modal">
<Spinner
isLoading={submitting}
className="-tiny"
/>

{title && <h2 className="c-title -extrabig">{title}</h2>}
<p className="c-text">
{text}
</p>
<p className="c-text -error">
{errorMessage}
</p>
<div className="actions">
<button className="c-button -primary" data-test-id="confirm-modal-cancel" onClick={onCancel}>
{_cancelText}
</button>
<button className="c-button -dangerous" data-test-id="confirm-modal-confirm" onClick={handleConfirm}>
{_confirmText}
</button>
</div>
</div>
)
};

export default ConfirmModal;
7 changes: 2 additions & 5 deletions components/ui/doc-annex.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
// import classnames from 'classnames';
import Tooltip from 'rc-tooltip';
import { useIntl } from 'react-intl';

import Icon from 'components/ui/icon';
import Spinner from 'components/ui/spinner';

export default function DocAnnex({ annex, isRemoving, showRemoveButton, onRemove }) {
export default function DocAnnex({ annex, showRemoveButton, visible, onRemove }) {
const intl = useIntl();
const formatDate = (date) => intl.formatDate(date, {
day: '2-digit',
Expand All @@ -18,12 +16,12 @@ export default function DocAnnex({ annex, isRemoving, showRemoveButton, onRemove
return (
<Tooltip
placement="bottom"
visible={visible}
align={{
offset: [0, 10],
}}
overlay={
<div>
<Spinner isLoading={isRemoving} className="-tiny -transparent" />
<h4 className="c-title -default tooltip-title"><strong>{annex.name}</strong></h4>
<dl className="tooltip-content">
<dt><strong>{intl.formatMessage({ id: 'annex.start_date' })}:</strong></dt>
Expand Down Expand Up @@ -68,6 +66,5 @@ DocAnnex.defaultProps = {
DocAnnex.propTypes = {
annex: PropTypes.object.isRequired,
showRemoveButton: PropTypes.bool,
isRemoving: PropTypes.bool,
onRemove: PropTypes.func
}
42 changes: 27 additions & 15 deletions components/ui/doc-card-upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import dayjs from 'dayjs';
import * as Sentry from '@sentry/nextjs';

import { connect } from 'react-redux';

Expand All @@ -15,18 +16,13 @@ import DocumentationService from 'services/documentationService';
import modal from 'services/modal';

// Components
import ConfirmModal from 'components/ui/confirm-modal';
import DocModal from 'components/ui/doc-modal';
import Spinner from 'components/ui/spinner';

class DocCardUpload extends React.Component {
constructor(props) {
super(props);

// STATE
this.state = {
deleteLoading: false,
};

// BINDINGS
this.triggerAddFile = this.triggerAddFile.bind(this);
this.triggerEditFile = this.triggerEditFile.bind(this);
Expand Down Expand Up @@ -92,24 +88,43 @@ class DocCardUpload extends React.Component {

triggerDeleteFile(e) {
e && e.preventDefault();
const { docId } = this.props;
const { title, intl } = this.props;

modal.toggleModal(true, {
children: ConfirmModal,
childrenProps: {
title: intl.formatMessage({ id: 'delete.document.title', defaultMessage: 'Delete {document}?' }, { document: title }),
text: intl.formatMessage(
{ id: 'delete.document.text', defaultMessage: 'Are you sure you want to delete document {document}?' }, { document: title }
),
confirmText: intl.formatMessage({ id: 'delete', defaultMessage: 'Delete' }),
onConfirm: this.triggerConfirmedDeleteFile.bind(this),
onCancel: () => modal.toggleModal(false),
},
size: '-small'
});
}

this.setState({ deleteLoading: true });
triggerConfirmedDeleteFile({ onSuccess, onError } = {}) {
const { docId, intl } = this.props;

this.documentationService.deleteDocument(docId)
.then(() => {
this.setState({ deleteLoading: false });
modal.toggleModal(false);
onSuccess && onSuccess();
this.props.onChange && this.props.onChange();
})
.catch((err) => {
this.setState({ deleteLoading: false });
onError && onError(
intl.formatMessage({ id: 'document.delete.error', defaultMessage: 'An error occurred while deleting the document.' })
);
Sentry.captureException(err);
console.error(err);
});
}

render() {
const { status, buttons, date } = this.props;
const { deleteLoading } = this.state;
const currentDate = dayjs(new Date());
const selectedDate = dayjs(date);
const isEditable =
Expand Down Expand Up @@ -153,10 +168,6 @@ class DocCardUpload extends React.Component {
className="c-button -small -primary"
>
{this.props.intl.formatMessage({ id: 'delete' })}
<Spinner
isLoading={deleteLoading}
className="-tiny -transparent"
/>
</button>
</li>
)}
Expand Down Expand Up @@ -215,6 +226,7 @@ class DocCardUpload extends React.Component {

DocCardUpload.propTypes = {
status: PropTypes.string,
title: PropTypes.string,
user: PropTypes.object,
docId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
onChange: PropTypes.func,
Expand Down
58 changes: 45 additions & 13 deletions components/ui/doc-card.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as Sentry from '@sentry/nextjs';

// Intl
import { connect } from 'react-redux';
Expand All @@ -11,6 +12,7 @@ import modal from 'services/modal';
import DocumentationService from 'services/documentationService';

// Components
import ConfirmModal from 'components/ui/confirm-modal';
import DocAnnexesModal from 'components/ui/doc-annexes-modal';
import DocAnnex from 'components/ui/doc-annex';
import Icon from 'components/ui/icon';
Expand Down Expand Up @@ -47,16 +49,15 @@ class DocCard extends React.Component {
constructor(props) {
super(props);

this.state = {
annexTooltipVisible: undefined
};

this.documentationService = new DocumentationService({
authorization: props.user.token
});
}

state = {
deleteLoading: false
}


triggerWhy = (e) => {
e && e.preventDefault();
const { title, reason } = this.props;
Expand Down Expand Up @@ -125,23 +126,54 @@ class DocCard extends React.Component {
}

triggerRemoveAnnex = (id) => {
this.setState({ deleteLoading: true });
const { annexes, title, intl } = this.props;
const annex = annexes.find(a => a.id === id);

// workaround to close tooltip before opening modal, but show it again when hovering
this.setState({ annexTooltipVisible: false });
setTimeout(() => {
this.setState({ annexTooltipVisible: undefined });
});

modal.toggleModal(true, {
children: ConfirmModal,
childrenProps: {
title: intl.formatMessage({ id: 'delete.document.title', defaultMessage: 'Delete {document}?' }, { document: annex.name }),
text: intl.formatMessage(
{ id: 'delete.document.text', defaultMessage: 'Are you sure you want to delete document {document}?' }, { document: annex.name }
),
confirmText: intl.formatMessage({ id: 'delete', defaultMessage: 'Delete' }),
onConfirm: (options) => {
this.triggerConfirmedRemoveAnnex({ ...options, annexId: id });
},
onCancel: () => modal.toggleModal(false),
},
size: '-small'
});
}

triggerConfirmedRemoveAnnex({ annexId, onSuccess, onError } = {}) {
const { intl } = this.props;

this.documentationService.deleteAnnex(id)
this.documentationService.deleteAnnex(annexId)
.then(() => {
this.setState({ deleteLoading: false });
modal.toggleModal(false);
onSuccess && onSuccess();
this.props.onChange && this.props.onChange();
})
.catch((err) => {
this.setState({ deleteLoading: false });
onError && onError(
intl.formatMessage({ id: 'document.delete.error', defaultMessage: 'An error occurred while deleting the document.' })
);
Sentry.captureException(err);
console.error(err);
});
}

render() {
const { user, adminComment, public: publicState, startDate, endDate, status, source, sourceInfo, title, explanation, url, annexes, layout, properties } = this.props;
const { id } = properties;
const { deleteLoading } = this.state;
const { annexTooltipVisible } = this.state;
const isActiveUser = (user && user.role === 'admin') ||
(user && (user.role === 'operator' || user.role === 'holding') && user.operator_ids && user.operator_ids.includes(+id)) ||
(user && user.role === 'government' && user.country && user.country.toString() === id);
Expand Down Expand Up @@ -223,7 +255,7 @@ class DocCard extends React.Component {
<ul className="doc-card-list">
{approvedAnnexes.map(annex => (
<li className="doc-card-list-item" key={annex.id}>
<DocAnnex annex={annex} isRemoving={deleteLoading} showRemoveButton={isActiveUser} onRemove={this.triggerRemoveAnnex} />
<DocAnnex annex={annex} showRemoveButton={isActiveUser} onRemove={this.triggerRemoveAnnex} visible={annexTooltipVisible} />
</li>
))}
{isActiveUser &&
Expand Down Expand Up @@ -296,7 +328,7 @@ class DocCard extends React.Component {
<ul className="doc-card-list">
{approvedAnnexes.map(annex => (
<li className="doc-card-list-item" key={annex.id}>
<DocAnnex annex={annex} isRemoving={deleteLoading} showRemoveButton={isActiveUser} onRemove={this.triggerRemoveAnnex} />
<DocAnnex annex={annex} showRemoveButton={isActiveUser} onRemove={this.triggerRemoveAnnex} visible={annexTooltipVisible} />
</li>
))}
{isActiveUser &&
Expand Down Expand Up @@ -363,7 +395,7 @@ class DocCard extends React.Component {
<ul className="doc-card-list">
{approvedAnnexes.map(annex => (
<li className="doc-card-list-item" key={annex.id}>
<DocAnnex annex={annex} isRemoving={deleteLoading} showRemoveButton={isActiveUser} onRemove={this.triggerRemoveAnnex} />
<DocAnnex annex={annex} showRemoveButton={isActiveUser} onRemove={this.triggerRemoveAnnex} visible={annexTooltipVisible} />
</li>
))}

Expand Down
12 changes: 12 additions & 0 deletions css/components/ui/_button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@
}
}

&.-dangerous {
border-color: $color-error;
color: $color-text-2;
fill: $color-error;
background: $color-error;

&:hover {
color: darken($color-text-2, 10%);
fill: darken($color-text-2, 10%);
}
}

&.-tertiary {
border-color: $color-tertiary;
color: $color-text-1;
Expand Down
19 changes: 19 additions & 0 deletions css/components/ui/_confirm-modal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.c-confirm-modal {
padding: 20px;

p {
font-size: $font-size-big;
}

p.-error {
color: $color-error;
font-size: $font-size-default;
}

.actions {
display: flex;
margin-top: 30px;
gap: 20px;
justify-content: flex-end;
}
}
11 changes: 11 additions & 0 deletions css/components/ui/_modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@
}
}

&.-small {
.modal-container {
width: calc(100% - 20px);

@include breakpoint(medium) {
width: unset;
max-width: 600px;
}
}
}

&.-auto {
.modal-container {
width: unset;
Expand Down
1 change: 1 addition & 0 deletions css/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
@import 'components/ui/chart';
@import 'components/ui/chart-legend';
@import 'components/ui/country-card';
@import 'components/ui/confirm-modal';
@import 'components/ui/datepicker';
@import 'components/ui/doc-by-category';
@import 'components/ui/doc-card';
Expand Down
Loading

0 comments on commit 6e87824

Please sign in to comment.