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

Commit 3dbd970

Browse files
authored
Merge pull request #42 from MadOPcode/dev
Adds payment error popups and filtering by failed payments
2 parents b6025a7 + d01eced commit 3dbd970

File tree

39 files changed

+648
-290
lines changed

39 files changed

+648
-290
lines changed

src/components/Popup/index.jsx

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { useState } from "react";
2+
import { usePopper } from "react-popper";
3+
import PT from "prop-types";
4+
import cn from "classnames";
5+
import compStyles from "./styles.module.scss";
6+
7+
const Popup = ({ children, className, referenceElement }) => {
8+
const [popperElement, setPopperElement] = useState(null);
9+
const [arrowElement, setArrowElement] = useState(null);
10+
const { styles, attributes } = usePopper(referenceElement, popperElement, {
11+
placement: "bottom",
12+
modifiers: [
13+
{ name: "arrow", options: { element: arrowElement, padding: 10 } },
14+
{ name: "offset", options: { offset: [0, 5] } },
15+
{ name: "preventOverflow", options: { padding: 15 } },
16+
],
17+
});
18+
19+
return (
20+
<div
21+
ref={setPopperElement}
22+
className={cn(compStyles.container, styles.container, className)}
23+
style={styles.popper}
24+
{...attributes.popper}
25+
>
26+
{children}
27+
<div className="popup-arrow" ref={setArrowElement} style={styles.arrow} />
28+
</div>
29+
);
30+
};
31+
32+
Popup.propTypes = {
33+
children: PT.node,
34+
className: PT.string,
35+
referenceElement: PT.object.isRequired,
36+
};
37+
38+
export default Popup;
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@import "styles/variables";
2+
3+
.container {
4+
z-index: 10;
5+
border-radius: 8px;
6+
padding: $popover-padding;
7+
background: #fff;
8+
box-shadow: $popover-box-shadow;
9+
10+
:global(.popup-arrow) {
11+
display: none;
12+
}
13+
}

src/constants/workPeriods.js

+28-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
// @ts-ignore
22
import { API } from "../../config";
3+
import * as API_CHALLENGE_PAYMENT_STATUS from "./workPeriods/apiChallengePaymentStatus";
34
import * as API_PAYMENT_STATUS from "./workPeriods/apiPaymentStatus";
45
import * as API_SORT_BY from "./workPeriods/apiSortBy";
56
import * as SORT_BY from "./workPeriods/sortBy";
67
import * as SORT_ORDER from "./workPeriods/sortOrder";
78
import * as PAYMENT_STATUS from "./workPeriods/paymentStatus";
89

9-
export { API_PAYMENT_STATUS, API_SORT_BY, SORT_BY, SORT_ORDER, PAYMENT_STATUS };
10+
export {
11+
API_CHALLENGE_PAYMENT_STATUS,
12+
API_PAYMENT_STATUS,
13+
API_SORT_BY,
14+
SORT_BY,
15+
SORT_ORDER,
16+
PAYMENT_STATUS,
17+
};
1018

1119
// resource bookings API url
1220
export const RB_API_URL = `${API.V5}/resourceBookings`;
@@ -37,6 +45,13 @@ export const API_REQUIRED_FIELDS = [
3745
"workPeriods.paymentTotal",
3846
"workPeriods.daysWorked",
3947
"workPeriods.daysPaid",
48+
"workPeriods.payments.amount",
49+
"workPeriods.payments.challengeId",
50+
"workPeriods.payments.days",
51+
"workPeriods.payments.id",
52+
"workPeriods.payments.memberRate",
53+
"workPeriods.payments.status",
54+
"workPeriods.payments.statusDetails",
4055
];
4156

4257
// Valid parameter names for requests.
@@ -65,11 +80,15 @@ export const SORT_BY_MAP = {
6580
};
6681

6782
export const PAYMENT_STATUS_LABELS = {
68-
[PAYMENT_STATUS.NO_DAYS]: "No Days",
83+
[PAYMENT_STATUS.CANCELLED]: "Cancelled",
6984
[PAYMENT_STATUS.COMPLETED]: "Completed",
85+
[PAYMENT_STATUS.FAILED]: "Failed",
86+
[PAYMENT_STATUS.IN_PROGRESS]: "In Progress",
87+
[PAYMENT_STATUS.NO_DAYS]: "No Days",
7088
[PAYMENT_STATUS.PARTIALLY_COMPLETED]: "Partially Completed",
7189
[PAYMENT_STATUS.PENDING]: "Pending",
72-
[PAYMENT_STATUS.IN_PROGRESS]: "In Progress",
90+
[PAYMENT_STATUS.SCHEDULED]: "Scheduled",
91+
[PAYMENT_STATUS.UNDEFINED]: "NA",
7392
};
7493

7594
export const PAYMENT_STATUS_MAP = {
@@ -91,16 +110,17 @@ export const API_PAYMENT_STATUS_MAP = (function () {
91110
})();
92111

93112
export const API_CHALLENGE_PAYMENT_STATUS_MAP = {
94-
cancelled: PAYMENT_STATUS.CANCELLED,
95-
completed: PAYMENT_STATUS.COMPLETED,
96-
failed: PAYMENT_STATUS.FAILED,
97-
"in-progress": PAYMENT_STATUS.IN_PROGRESS,
98-
scheduled: PAYMENT_STATUS.SCHEDULED,
113+
[API_CHALLENGE_PAYMENT_STATUS.CANCELLED]: PAYMENT_STATUS.CANCELLED,
114+
[API_CHALLENGE_PAYMENT_STATUS.COMPLETED]: PAYMENT_STATUS.COMPLETED,
115+
[API_CHALLENGE_PAYMENT_STATUS.FAILED]: PAYMENT_STATUS.FAILED,
116+
[API_CHALLENGE_PAYMENT_STATUS.IN_PROGRESS]: PAYMENT_STATUS.IN_PROGRESS,
117+
[API_CHALLENGE_PAYMENT_STATUS.SCHEDULED]: PAYMENT_STATUS.SCHEDULED,
99118
};
100119

101120
export const URL_QUERY_PARAM_MAP = new Map([
102121
["startDate", "startDate"],
103122
["paymentStatuses", "status"],
123+
["onlyFailedPayments", "onlyFailed"],
104124
["userHandle", "user"],
105125
["criteria", "by"],
106126
["order", "order"],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const CANCELLED = "cancelled";
2+
export const COMPLETED = "completed";
3+
export const FAILED = "failed";
4+
export const IN_PROGRESS = "in-progress";
5+
export const SCHEDULED = "scheduled";
+4-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
export const PARTIALLY_COMPLETED = "PARTIALLY_COMPLETED";
1+
export const CANCELLED = "CANCELLED";
22
export const COMPLETED = "COMPLETED";
3-
export const PENDING = "PENDING";
3+
export const FAILED = "FAILED";
44
export const IN_PROGRESS = "IN_PROGRESS";
55
export const NO_DAYS = "NO_DAYS";
6+
export const PARTIALLY_COMPLETED = "PARTIALLY_COMPLETED";
7+
export const PENDING = "PENDING";
68
export const SCHEDULED = "SCHEDULED";
7-
export const FAILED = "FAILED";
8-
export const CANCELLED = "CANCELLED";
99
export const UNDEFINED = "UNDEFINED";

src/constants/workPeriods/sortBy.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ export const START_DATE = "START_DATE";
44
export const END_DATE = "END_DATE";
55
export const ALERT = "ALERT";
66
export const WEEKLY_RATE = "WEEKLY_RATE";
7-
export const PAYMENT_STATUS = "STATUS";
8-
export const PAYMENT_TOTAL = "TOTAL_PAYMENT";
7+
export const PAYMENT_STATUS = "PAYMENT_STATUS";
8+
export const PAYMENT_TOTAL = "PAYMENT_TOTAL";
99
export const WORKING_DAYS = "WORKING_DAYS";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React, { useCallback, useRef, useState } from "react";
2+
import PT from "prop-types";
3+
import cn from "classnames";
4+
import Popup from "components/Popup";
5+
import PaymentErrorDetails from "../PaymentErrorDetails";
6+
import { useClickOutside } from "utils/hooks";
7+
import { negate } from "utils/misc";
8+
import styles from "./styles.module.scss";
9+
10+
/**
11+
* Displays an error icon and error details popup.
12+
*
13+
* @param {Object} props component properties
14+
* @param {string} [props.className] class name to be added to root element
15+
* @param {Object} [props.errorDetails] error details object
16+
* @param {boolean} [props.isImportant] whether the error deemed important
17+
* @returns {JSX.Element}
18+
*/
19+
const PaymentError = ({ className, errorDetails, isImportant = true }) => {
20+
const [isShowPopup, setIsShowPopup] = useState(false);
21+
const [refElem, setRefElem] = useState(null);
22+
const containerRef = useRef(null);
23+
24+
const onIconClick = useCallback((event) => {
25+
event.stopPropagation();
26+
setIsShowPopup(negate);
27+
}, []);
28+
29+
const onClickOutside = useCallback(() => {
30+
setIsShowPopup(false);
31+
}, []);
32+
33+
useClickOutside(containerRef, onClickOutside, []);
34+
35+
return (
36+
<div className={cn(styles.container, className)} ref={containerRef}>
37+
<span
38+
ref={setRefElem}
39+
className={cn(styles.icon, { [styles.isImportant]: isImportant })}
40+
onClick={onIconClick}
41+
role="button"
42+
tabIndex={0}
43+
/>
44+
{isShowPopup && errorDetails && (
45+
<Popup referenceElement={refElem}>
46+
<PaymentErrorDetails details={errorDetails} />
47+
</Popup>
48+
)}
49+
</div>
50+
);
51+
};
52+
53+
PaymentError.propTypes = {
54+
className: PT.string,
55+
errorDetails: PT.object,
56+
isImportant: PT.bool,
57+
};
58+
59+
export default PaymentError;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@import "styles/variables";
2+
3+
.container {
4+
display: inline-block;
5+
position: relative;
6+
}
7+
8+
.icon {
9+
display: inline-block;
10+
padding: 2px 0 0;
11+
width: 20px;
12+
height: 20px;
13+
border-radius: 10px;
14+
line-height: 18px;
15+
text-align: center;
16+
background: $error-color;
17+
color: #fff;
18+
opacity: 0.3;
19+
cursor: pointer;
20+
21+
&.isImportant {
22+
opacity: 1;
23+
}
24+
25+
&::before {
26+
content: "!";
27+
display: inline;
28+
font-weight: 700;
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from "react";
2+
import PT from "prop-types";
3+
import cn from "classnames";
4+
import { formatChallengeUrl } from "utils/formatters";
5+
import styles from "./styles.module.scss";
6+
7+
/**
8+
* Displays payment error details.
9+
*
10+
* @param {Object} props component properties
11+
* @param {string} [props.className] class name to be added to root element
12+
* @param {Object} props.details error details
13+
* @returns {JSX.Element}
14+
*/
15+
const PaymentErrorDetails = ({ className, details }) => {
16+
const { challengeId, errorMessage, errorCode, retry, step } = details;
17+
return (
18+
<div className={cn(styles.container, className)}>
19+
<div className={styles.row}>
20+
<span className={styles.label}>Challenge:</span>
21+
{challengeId ? (
22+
<a
23+
href={formatChallengeUrl(challengeId)}
24+
target="_blank"
25+
rel="noreferrer"
26+
>
27+
{challengeId}
28+
</a>
29+
) : (
30+
<span>{"<Missing challenge id>"}</span>
31+
)}
32+
</div>
33+
<div className={cn(styles.row, styles.errorMessage)}>
34+
<span className={styles.label}>Error:</span>
35+
{errorMessage}
36+
</div>
37+
<div className={styles.row}>
38+
<span className={styles.cell}>
39+
<span className={styles.label}>Code:</span>
40+
{errorCode}
41+
</span>
42+
<span className={styles.cell}>
43+
<span className={styles.label}>Retry:</span>
44+
{retry}
45+
</span>
46+
<span className={styles.cell}>
47+
<span className={styles.label}>Step:</span>
48+
{step}
49+
</span>
50+
</div>
51+
</div>
52+
);
53+
};
54+
55+
PaymentErrorDetails.propTypes = {
56+
className: PT.string,
57+
details: PT.shape({
58+
challengeId: PT.string,
59+
errorCode: PT.number.isRequired,
60+
errorMessage: PT.string.isRequired,
61+
retry: PT.number.isRequired,
62+
step: PT.string.isRequired,
63+
}).isRequired,
64+
};
65+
66+
export default PaymentErrorDetails;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
@import "styles/mixins";
2+
3+
.container {
4+
display: block;
5+
max-width: 480px;
6+
text-align: left;
7+
}
8+
9+
.row {
10+
display: flex;
11+
align-items: baseline;
12+
white-space: nowrap;
13+
14+
a {
15+
color: #0d61bf;
16+
}
17+
}
18+
19+
.cell {
20+
margin-left: 20px;
21+
white-space: nowrap;
22+
23+
&:first-child {
24+
margin-left: 0;
25+
}
26+
}
27+
28+
.errorMessage {
29+
white-space: normal;
30+
}
31+
32+
.label {
33+
margin-right: 5px;
34+
@include roboto-medium;
35+
}

src/routes/WorkPeriods/components/PaymentStatus/styles.module.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
}
3838

3939
.failed {
40-
background: #e90c5a;
40+
background: $error-color;
4141
}
4242

4343
.undefined {

0 commit comments

Comments
 (0)