Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Adds payment error popups and filtering by failed payments #42

Merged
merged 4 commits into from
Jun 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/components/Popup/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useState } from "react";
import { usePopper } from "react-popper";
import PT from "prop-types";
import cn from "classnames";
import compStyles from "./styles.module.scss";

const Popup = ({ children, className, referenceElement }) => {
const [popperElement, setPopperElement] = useState(null);
const [arrowElement, setArrowElement] = useState(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: "bottom",
modifiers: [
{ name: "arrow", options: { element: arrowElement, padding: 10 } },
{ name: "offset", options: { offset: [0, 5] } },
{ name: "preventOverflow", options: { padding: 15 } },
],
});

return (
<div
ref={setPopperElement}
className={cn(compStyles.container, styles.container, className)}
style={styles.popper}
{...attributes.popper}
>
{children}
<div className="popup-arrow" ref={setArrowElement} style={styles.arrow} />
</div>
);
};

Popup.propTypes = {
children: PT.node,
className: PT.string,
referenceElement: PT.object.isRequired,
};

export default Popup;
13 changes: 13 additions & 0 deletions src/components/Popup/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@import "styles/variables";

.container {
z-index: 10;
border-radius: 8px;
padding: $popover-padding;
background: #fff;
box-shadow: $popover-box-shadow;

:global(.popup-arrow) {
display: none;
}
}
36 changes: 28 additions & 8 deletions src/constants/workPeriods.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
// @ts-ignore
import { API } from "../../config";
import * as API_CHALLENGE_PAYMENT_STATUS from "./workPeriods/apiChallengePaymentStatus";
import * as API_PAYMENT_STATUS from "./workPeriods/apiPaymentStatus";
import * as API_SORT_BY from "./workPeriods/apiSortBy";
import * as SORT_BY from "./workPeriods/sortBy";
import * as SORT_ORDER from "./workPeriods/sortOrder";
import * as PAYMENT_STATUS from "./workPeriods/paymentStatus";

export { API_PAYMENT_STATUS, API_SORT_BY, SORT_BY, SORT_ORDER, PAYMENT_STATUS };
export {
API_CHALLENGE_PAYMENT_STATUS,
API_PAYMENT_STATUS,
API_SORT_BY,
SORT_BY,
SORT_ORDER,
PAYMENT_STATUS,
};

// resource bookings API url
export const RB_API_URL = `${API.V5}/resourceBookings`;
Expand Down Expand Up @@ -37,6 +45,13 @@ export const API_REQUIRED_FIELDS = [
"workPeriods.paymentTotal",
"workPeriods.daysWorked",
"workPeriods.daysPaid",
"workPeriods.payments.amount",
"workPeriods.payments.challengeId",
"workPeriods.payments.days",
"workPeriods.payments.id",
"workPeriods.payments.memberRate",
"workPeriods.payments.status",
"workPeriods.payments.statusDetails",
];

// Valid parameter names for requests.
Expand Down Expand Up @@ -65,11 +80,15 @@ export const SORT_BY_MAP = {
};

export const PAYMENT_STATUS_LABELS = {
[PAYMENT_STATUS.NO_DAYS]: "No Days",
[PAYMENT_STATUS.CANCELLED]: "Cancelled",
[PAYMENT_STATUS.COMPLETED]: "Completed",
[PAYMENT_STATUS.FAILED]: "Failed",
[PAYMENT_STATUS.IN_PROGRESS]: "In Progress",
[PAYMENT_STATUS.NO_DAYS]: "No Days",
[PAYMENT_STATUS.PARTIALLY_COMPLETED]: "Partially Completed",
[PAYMENT_STATUS.PENDING]: "Pending",
[PAYMENT_STATUS.IN_PROGRESS]: "In Progress",
[PAYMENT_STATUS.SCHEDULED]: "Scheduled",
[PAYMENT_STATUS.UNDEFINED]: "NA",
};

export const PAYMENT_STATUS_MAP = {
Expand All @@ -91,16 +110,17 @@ export const API_PAYMENT_STATUS_MAP = (function () {
})();

export const API_CHALLENGE_PAYMENT_STATUS_MAP = {
cancelled: PAYMENT_STATUS.CANCELLED,
completed: PAYMENT_STATUS.COMPLETED,
failed: PAYMENT_STATUS.FAILED,
"in-progress": PAYMENT_STATUS.IN_PROGRESS,
scheduled: PAYMENT_STATUS.SCHEDULED,
[API_CHALLENGE_PAYMENT_STATUS.CANCELLED]: PAYMENT_STATUS.CANCELLED,
[API_CHALLENGE_PAYMENT_STATUS.COMPLETED]: PAYMENT_STATUS.COMPLETED,
[API_CHALLENGE_PAYMENT_STATUS.FAILED]: PAYMENT_STATUS.FAILED,
[API_CHALLENGE_PAYMENT_STATUS.IN_PROGRESS]: PAYMENT_STATUS.IN_PROGRESS,
[API_CHALLENGE_PAYMENT_STATUS.SCHEDULED]: PAYMENT_STATUS.SCHEDULED,
};

export const URL_QUERY_PARAM_MAP = new Map([
["startDate", "startDate"],
["paymentStatuses", "status"],
["onlyFailedPayments", "onlyFailed"],
["userHandle", "user"],
["criteria", "by"],
["order", "order"],
Expand Down
5 changes: 5 additions & 0 deletions src/constants/workPeriods/apiChallengePaymentStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const CANCELLED = "cancelled";
export const COMPLETED = "completed";
export const FAILED = "failed";
export const IN_PROGRESS = "in-progress";
export const SCHEDULED = "scheduled";
8 changes: 4 additions & 4 deletions src/constants/workPeriods/paymentStatus.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export const PARTIALLY_COMPLETED = "PARTIALLY_COMPLETED";
export const CANCELLED = "CANCELLED";
export const COMPLETED = "COMPLETED";
export const PENDING = "PENDING";
export const FAILED = "FAILED";
export const IN_PROGRESS = "IN_PROGRESS";
export const NO_DAYS = "NO_DAYS";
export const PARTIALLY_COMPLETED = "PARTIALLY_COMPLETED";
export const PENDING = "PENDING";
export const SCHEDULED = "SCHEDULED";
export const FAILED = "FAILED";
export const CANCELLED = "CANCELLED";
export const UNDEFINED = "UNDEFINED";
4 changes: 2 additions & 2 deletions src/constants/workPeriods/sortBy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ export const START_DATE = "START_DATE";
export const END_DATE = "END_DATE";
export const ALERT = "ALERT";
export const WEEKLY_RATE = "WEEKLY_RATE";
export const PAYMENT_STATUS = "STATUS";
export const PAYMENT_TOTAL = "TOTAL_PAYMENT";
export const PAYMENT_STATUS = "PAYMENT_STATUS";
export const PAYMENT_TOTAL = "PAYMENT_TOTAL";
export const WORKING_DAYS = "WORKING_DAYS";
59 changes: 59 additions & 0 deletions src/routes/WorkPeriods/components/PaymentError/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useCallback, useRef, useState } from "react";
import PT from "prop-types";
import cn from "classnames";
import Popup from "components/Popup";
import PaymentErrorDetails from "../PaymentErrorDetails";
import { useClickOutside } from "utils/hooks";
import { negate } from "utils/misc";
import styles from "./styles.module.scss";

/**
* Displays an error icon and error details popup.
*
* @param {Object} props component properties
* @param {string} [props.className] class name to be added to root element
* @param {Object} [props.errorDetails] error details object
* @param {boolean} [props.isImportant] whether the error deemed important
* @returns {JSX.Element}
*/
const PaymentError = ({ className, errorDetails, isImportant = true }) => {
const [isShowPopup, setIsShowPopup] = useState(false);
const [refElem, setRefElem] = useState(null);
const containerRef = useRef(null);

const onIconClick = useCallback((event) => {
event.stopPropagation();
setIsShowPopup(negate);
}, []);

const onClickOutside = useCallback(() => {
setIsShowPopup(false);
}, []);

useClickOutside(containerRef, onClickOutside, []);

return (
<div className={cn(styles.container, className)} ref={containerRef}>
<span
ref={setRefElem}
className={cn(styles.icon, { [styles.isImportant]: isImportant })}
onClick={onIconClick}
role="button"
tabIndex={0}
/>
{isShowPopup && errorDetails && (
<Popup referenceElement={refElem}>
<PaymentErrorDetails details={errorDetails} />
</Popup>
)}
</div>
);
};

PaymentError.propTypes = {
className: PT.string,
errorDetails: PT.object,
isImportant: PT.bool,
};

export default PaymentError;
30 changes: 30 additions & 0 deletions src/routes/WorkPeriods/components/PaymentError/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@import "styles/variables";

.container {
display: inline-block;
position: relative;
}

.icon {
display: inline-block;
padding: 2px 0 0;
width: 20px;
height: 20px;
border-radius: 10px;
line-height: 18px;
text-align: center;
background: $error-color;
color: #fff;
opacity: 0.3;
cursor: pointer;

&.isImportant {
opacity: 1;
}

&::before {
content: "!";
display: inline;
font-weight: 700;
}
}
66 changes: 66 additions & 0 deletions src/routes/WorkPeriods/components/PaymentErrorDetails/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from "react";
import PT from "prop-types";
import cn from "classnames";
import { formatChallengeUrl } from "utils/formatters";
import styles from "./styles.module.scss";

/**
* Displays payment error details.
*
* @param {Object} props component properties
* @param {string} [props.className] class name to be added to root element
* @param {Object} props.details error details
* @returns {JSX.Element}
*/
const PaymentErrorDetails = ({ className, details }) => {
const { challengeId, errorMessage, errorCode, retry, step } = details;
return (
<div className={cn(styles.container, className)}>
<div className={styles.row}>
<span className={styles.label}>Challenge:</span>
{challengeId ? (
<a
href={formatChallengeUrl(challengeId)}
target="_blank"
rel="noreferrer"
>
{challengeId}
</a>
) : (
<span>{"<Missing challenge id>"}</span>
)}
</div>
<div className={cn(styles.row, styles.errorMessage)}>
<span className={styles.label}>Error:</span>
{errorMessage}
</div>
<div className={styles.row}>
<span className={styles.cell}>
<span className={styles.label}>Code:</span>
{errorCode}
</span>
<span className={styles.cell}>
<span className={styles.label}>Retry:</span>
{retry}
</span>
<span className={styles.cell}>
<span className={styles.label}>Step:</span>
{step}
</span>
</div>
</div>
);
};

PaymentErrorDetails.propTypes = {
className: PT.string,
details: PT.shape({
challengeId: PT.string,
errorCode: PT.number.isRequired,
errorMessage: PT.string.isRequired,
retry: PT.number.isRequired,
step: PT.string.isRequired,
}).isRequired,
};

export default PaymentErrorDetails;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@import "styles/mixins";

.container {
display: block;
max-width: 480px;
text-align: left;
}

.row {
display: flex;
align-items: baseline;
white-space: nowrap;

a {
color: #0d61bf;
}
}

.cell {
margin-left: 20px;
white-space: nowrap;

&:first-child {
margin-left: 0;
}
}

.errorMessage {
white-space: normal;
}

.label {
margin-right: 5px;
@include roboto-medium;
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
}

.failed {
background: #e90c5a;
background: $error-color;
}

.undefined {
Expand Down
Loading