strapi-plugin-payone-provider 1.6.7 → 4.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/src/pages/App/components/AppTabs.jsx +6 -0
- package/admin/src/pages/App/components/CustomerInfoPopover.jsx +147 -0
- package/admin/src/pages/App/components/HistoryPanel.jsx +22 -240
- package/admin/src/pages/App/components/RawDataPopover.jsx +113 -0
- package/admin/src/pages/App/components/StatusBadge.jsx +72 -14
- package/admin/src/pages/App/components/TransactionHistoryTable/TransactionHistoryTableFilters.jsx +113 -0
- package/admin/src/pages/App/components/TransactionHistoryTable/TransactionHistoryTablePagination.jsx +180 -0
- package/admin/src/pages/App/components/TransactionHistoryTable/index.jsx +225 -0
- package/admin/src/pages/App/index.jsx +3 -0
- package/admin/src/pages/hooks/useTransactionHistory.js +73 -8
- package/package.json +3 -2
- package/server/services/transactionService.js +69 -5
|
@@ -28,6 +28,8 @@ const AppTabs = ({
|
|
|
28
28
|
filters,
|
|
29
29
|
onFilterChange,
|
|
30
30
|
onFilterApply,
|
|
31
|
+
sorting,
|
|
32
|
+
onSort,
|
|
31
33
|
isLoadingHistory,
|
|
32
34
|
transactionHistory,
|
|
33
35
|
paginatedTransactions,
|
|
@@ -36,6 +38,7 @@ const AppTabs = ({
|
|
|
36
38
|
pageSize,
|
|
37
39
|
onRefresh,
|
|
38
40
|
onPageChange,
|
|
41
|
+
onPageSizeChange,
|
|
39
42
|
selectedTransaction,
|
|
40
43
|
onTransactionSelect,
|
|
41
44
|
// Payment actions props
|
|
@@ -105,6 +108,8 @@ const AppTabs = ({
|
|
|
105
108
|
filters={filters}
|
|
106
109
|
onFilterChange={onFilterChange}
|
|
107
110
|
onFilterApply={onFilterApply}
|
|
111
|
+
sorting={sorting}
|
|
112
|
+
onSort={onSort}
|
|
108
113
|
isLoadingHistory={isLoadingHistory}
|
|
109
114
|
transactionHistory={transactionHistory}
|
|
110
115
|
paginatedTransactions={paginatedTransactions}
|
|
@@ -113,6 +118,7 @@ const AppTabs = ({
|
|
|
113
118
|
pageSize={pageSize}
|
|
114
119
|
onRefresh={onRefresh}
|
|
115
120
|
onPageChange={onPageChange}
|
|
121
|
+
onPageSizeChange={onPageSizeChange}
|
|
116
122
|
selectedTransaction={selectedTransaction}
|
|
117
123
|
onTransactionSelect={onTransactionSelect}
|
|
118
124
|
/>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import { Box, Typography } from "@strapi/design-system";
|
|
3
|
+
|
|
4
|
+
const CustomerInfoPopover = ({ transaction, children }) => {
|
|
5
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
6
|
+
const [position, setPosition] = useState("top"); // 'top' or 'bottom'
|
|
7
|
+
const popoverRef = useRef(null);
|
|
8
|
+
const buttonRef = useRef(null);
|
|
9
|
+
|
|
10
|
+
const calculatePosition = (buttonElement) => {
|
|
11
|
+
if (!buttonElement) return "top";
|
|
12
|
+
|
|
13
|
+
const buttonRect = buttonElement.getBoundingClientRect();
|
|
14
|
+
// Find the scrollable parent container
|
|
15
|
+
let scrollableParent = buttonElement.parentElement;
|
|
16
|
+
while (scrollableParent && scrollableParent !== document.body) {
|
|
17
|
+
const style = window.getComputedStyle(scrollableParent);
|
|
18
|
+
if (style.overflow === 'auto' || style.overflowY === 'auto' || style.overflow === 'scroll' || style.overflowY === 'scroll') {
|
|
19
|
+
const parentRect = scrollableParent.getBoundingClientRect();
|
|
20
|
+
const spaceAbove = buttonRect.top - parentRect.top;
|
|
21
|
+
const spaceBelow = parentRect.bottom - buttonRect.bottom;
|
|
22
|
+
// If there's less space above than below (or less than 200px), show popover below
|
|
23
|
+
return spaceAbove < 200 || spaceAbove < spaceBelow ? "bottom" : "top";
|
|
24
|
+
}
|
|
25
|
+
scrollableParent = scrollableParent.parentElement;
|
|
26
|
+
}
|
|
27
|
+
// Fallback: use viewport space
|
|
28
|
+
const spaceAbove = buttonRect.top;
|
|
29
|
+
return spaceAbove < 200 ? "bottom" : "top";
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const handleButtonClick = () => {
|
|
33
|
+
if (!isOpen && buttonRef.current) {
|
|
34
|
+
// Calculate position before opening
|
|
35
|
+
const newPosition = calculatePosition(buttonRef.current);
|
|
36
|
+
setPosition(newPosition);
|
|
37
|
+
}
|
|
38
|
+
setIsOpen(!isOpen);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const handleClickOutside = (event) => {
|
|
43
|
+
if (
|
|
44
|
+
popoverRef.current &&
|
|
45
|
+
buttonRef.current &&
|
|
46
|
+
!popoverRef.current.contains(event.target) &&
|
|
47
|
+
!buttonRef.current.contains(event.target)
|
|
48
|
+
) {
|
|
49
|
+
setIsOpen(false);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (isOpen) {
|
|
54
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
55
|
+
return () => {
|
|
56
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}, [isOpen]);
|
|
60
|
+
|
|
61
|
+
const rawRequest = transaction?.raw_request || {};
|
|
62
|
+
const salutation = rawRequest.salutation || "";
|
|
63
|
+
const firstname = rawRequest.firstname || "";
|
|
64
|
+
const lastname = rawRequest.lastname || "";
|
|
65
|
+
const street = rawRequest.street || "";
|
|
66
|
+
const zip = rawRequest.zip || "";
|
|
67
|
+
const city = rawRequest.city || "";
|
|
68
|
+
const telephonenumber = rawRequest.telephonenumber || "";
|
|
69
|
+
const email = rawRequest.email || "";
|
|
70
|
+
|
|
71
|
+
const hasCustomerInfo =
|
|
72
|
+
salutation ||
|
|
73
|
+
firstname ||
|
|
74
|
+
lastname ||
|
|
75
|
+
street ||
|
|
76
|
+
zip ||
|
|
77
|
+
city ||
|
|
78
|
+
telephonenumber ||
|
|
79
|
+
email;
|
|
80
|
+
|
|
81
|
+
if (!hasCustomerInfo) {
|
|
82
|
+
return children;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Box position="relative" style={{ display: "inline-block" }}>
|
|
87
|
+
<Box
|
|
88
|
+
ref={buttonRef}
|
|
89
|
+
onClick={handleButtonClick}
|
|
90
|
+
style={{ display: "inline-block" }}
|
|
91
|
+
>
|
|
92
|
+
{children}
|
|
93
|
+
</Box>
|
|
94
|
+
{isOpen && (
|
|
95
|
+
<Box
|
|
96
|
+
ref={popoverRef}
|
|
97
|
+
position="absolute"
|
|
98
|
+
zIndex={1000}
|
|
99
|
+
left={0}
|
|
100
|
+
{...(position === "top"
|
|
101
|
+
? { bottom: "100%", marginBottom: 2 }
|
|
102
|
+
: { top: "100%", marginTop: 2 }
|
|
103
|
+
)}
|
|
104
|
+
padding={3}
|
|
105
|
+
background="neutral0"
|
|
106
|
+
hasRadius
|
|
107
|
+
style={{
|
|
108
|
+
minWidth: "250px",
|
|
109
|
+
maxWidth: "350px",
|
|
110
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
|
111
|
+
border: "1px solid var(--strapi-colors-neutral200)",
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<Box paddingBottom={2}>
|
|
115
|
+
<Typography variant="pi" fontWeight="bold" textColor="neutral800">
|
|
116
|
+
👤 {[salutation, firstname, lastname].filter(Boolean).join(" ")}
|
|
117
|
+
</Typography>
|
|
118
|
+
</Box>
|
|
119
|
+
{(street || zip || city) && (
|
|
120
|
+
<Box paddingBottom={2}>
|
|
121
|
+
<Typography variant="pi" textColor="neutral800">
|
|
122
|
+
📍 {[street, zip, city].filter(Boolean).join(" ")}
|
|
123
|
+
</Typography>
|
|
124
|
+
</Box>
|
|
125
|
+
)}
|
|
126
|
+
{telephonenumber && (
|
|
127
|
+
<Box paddingBottom={2}>
|
|
128
|
+
<Typography variant="pi" textColor="neutral800">
|
|
129
|
+
📞 {telephonenumber}
|
|
130
|
+
</Typography>
|
|
131
|
+
</Box>
|
|
132
|
+
)}
|
|
133
|
+
{email && (
|
|
134
|
+
<Box>
|
|
135
|
+
<Typography variant="pi" textColor="neutral800">
|
|
136
|
+
✉️ {email}
|
|
137
|
+
</Typography>
|
|
138
|
+
</Box>
|
|
139
|
+
)}
|
|
140
|
+
</Box>
|
|
141
|
+
)}
|
|
142
|
+
</Box>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export default CustomerInfoPopover;
|
|
147
|
+
|
|
@@ -14,11 +14,14 @@ import {
|
|
|
14
14
|
} from "@strapi/design-system";
|
|
15
15
|
import { Search } from "@strapi/icons";
|
|
16
16
|
import TransactionHistoryItem from "./TransactionHistoryItem";
|
|
17
|
+
import TransactionHistoryTable from "./TransactionHistoryTable";
|
|
17
18
|
|
|
18
19
|
const HistoryPanel = ({
|
|
19
20
|
filters,
|
|
20
21
|
onFilterChange,
|
|
21
22
|
onFilterApply,
|
|
23
|
+
sorting,
|
|
24
|
+
onSort,
|
|
22
25
|
isLoadingHistory,
|
|
23
26
|
transactionHistory,
|
|
24
27
|
paginatedTransactions,
|
|
@@ -27,7 +30,10 @@ const HistoryPanel = ({
|
|
|
27
30
|
pageSize,
|
|
28
31
|
onRefresh,
|
|
29
32
|
onPageChange,
|
|
33
|
+
onPageSizeChange,
|
|
30
34
|
}) => {
|
|
35
|
+
console.log(transactionHistory);
|
|
36
|
+
|
|
31
37
|
return (
|
|
32
38
|
<Box
|
|
33
39
|
className="payment-container"
|
|
@@ -55,247 +61,23 @@ const HistoryPanel = ({
|
|
|
55
61
|
View and filter all payment transactions processed through Payone
|
|
56
62
|
</Typography>
|
|
57
63
|
</Box>
|
|
58
|
-
{/* Filters */}
|
|
59
|
-
<Box>
|
|
60
|
-
<Box marginBottom={4}>
|
|
61
|
-
<Typography variant="delta" as="h3" fontWeight="bold">
|
|
62
|
-
Transaction Filters
|
|
63
|
-
</Typography>
|
|
64
|
-
<Typography variant="pi" textColor="neutral600" marginTop={2}>
|
|
65
|
-
Filter transactions by status, type, date range, and more
|
|
66
|
-
</Typography>
|
|
67
|
-
</Box>
|
|
68
|
-
<Card className="payment-card">
|
|
69
|
-
<CardBody padding={6}>
|
|
70
|
-
<Stack spacing={4}>
|
|
71
|
-
<Flex gap={4} wrap="wrap" alignItems="center">
|
|
72
|
-
<TextInput
|
|
73
|
-
label="Search"
|
|
74
|
-
name="search"
|
|
75
|
-
value={filters.search || ""}
|
|
76
|
-
onChange={(e) => onFilterChange("search", e.target.value)}
|
|
77
|
-
placeholder="Search by Status, Transaction ID, or Reference"
|
|
78
|
-
className="payment-input"
|
|
79
|
-
style={{ flex: 1, minWidth: "250px" }}
|
|
80
|
-
/>
|
|
81
|
-
<Select
|
|
82
|
-
label="Request Type"
|
|
83
|
-
name="request_type"
|
|
84
|
-
value={filters.request_type || ""}
|
|
85
|
-
onChange={(value) => onFilterChange("request_type", value)}
|
|
86
|
-
placeholder="Select request type"
|
|
87
|
-
className="payment-input"
|
|
88
|
-
style={{ flex: 1, minWidth: "200px" }}
|
|
89
|
-
>
|
|
90
|
-
<Option value="">All Types</Option>
|
|
91
|
-
<Option value="preauthorization">Preauthorization</Option>
|
|
92
|
-
<Option value="authorization">Authorization</Option>
|
|
93
|
-
<Option value="capture">Capture</Option>
|
|
94
|
-
<Option value="refund">Refund</Option>
|
|
95
|
-
</Select>
|
|
96
|
-
<Select
|
|
97
|
-
label="Payment Method"
|
|
98
|
-
name="payment_method"
|
|
99
|
-
value={filters.payment_method || ""}
|
|
100
|
-
onChange={(value) =>
|
|
101
|
-
onFilterChange("payment_method", value)
|
|
102
|
-
}
|
|
103
|
-
placeholder="Select payment method"
|
|
104
|
-
className="payment-input"
|
|
105
|
-
style={{ flex: 1, minWidth: "200px" }}
|
|
106
|
-
>
|
|
107
|
-
<Option value="">All Methods</Option>
|
|
108
|
-
<Option value="credit_card">Credit Card</Option>
|
|
109
|
-
<Option value="paypal">PayPal</Option>
|
|
110
|
-
<Option value="google_pay">Google Pay</Option>
|
|
111
|
-
<Option value="apple_pay">Apple Pay</Option>
|
|
112
|
-
<Option value="sofort">Sofort Banking</Option>
|
|
113
|
-
<Option value="sepa">SEPA Direct Debit</Option>
|
|
114
|
-
</Select>
|
|
115
|
-
<TextInput
|
|
116
|
-
label="Date From"
|
|
117
|
-
name="date_from"
|
|
118
|
-
value={filters.date_from || ""}
|
|
119
|
-
onChange={(e) =>
|
|
120
|
-
onFilterChange("date_from", e.target.value)
|
|
121
|
-
}
|
|
122
|
-
placeholder="YYYY-MM-DD"
|
|
123
|
-
type="date"
|
|
124
|
-
className="payment-input"
|
|
125
|
-
style={{ flex: 1, minWidth: "150px" }}
|
|
126
|
-
/>
|
|
127
|
-
<TextInput
|
|
128
|
-
label="Date To"
|
|
129
|
-
name="date_to"
|
|
130
|
-
value={filters.date_to || ""}
|
|
131
|
-
onChange={(e) => onFilterChange("date_to", e.target.value)}
|
|
132
|
-
placeholder="YYYY-MM-DD"
|
|
133
|
-
type="date"
|
|
134
|
-
className="payment-input"
|
|
135
|
-
style={{ flex: 1, minWidth: "150px" }}
|
|
136
|
-
/>
|
|
137
|
-
<Button
|
|
138
|
-
variant="default"
|
|
139
|
-
onClick={onFilterApply}
|
|
140
|
-
loading={isLoadingHistory}
|
|
141
|
-
startIcon={<Search />}
|
|
142
|
-
className="payment-button payment-button-primary"
|
|
143
|
-
>
|
|
144
|
-
Apply Filters
|
|
145
|
-
</Button>
|
|
146
|
-
</Flex>
|
|
147
|
-
</Stack>
|
|
148
|
-
</CardBody>
|
|
149
|
-
</Card>
|
|
150
|
-
</Box>
|
|
151
|
-
|
|
152
|
-
<Divider />
|
|
153
|
-
|
|
154
|
-
{/* Transaction History */}
|
|
155
|
-
<Box>
|
|
156
|
-
<Box marginBottom={6}>
|
|
157
|
-
<Flex
|
|
158
|
-
justifyContent="space-between"
|
|
159
|
-
alignItems="center"
|
|
160
|
-
marginBottom={4}
|
|
161
|
-
>
|
|
162
|
-
<Box>
|
|
163
|
-
<Typography variant="delta" as="h3" fontWeight="bold">
|
|
164
|
-
Transaction History
|
|
165
|
-
</Typography>
|
|
166
|
-
<Typography variant="pi" textColor="neutral600" marginTop={2}>
|
|
167
|
-
{transactionHistory.length} total transactions •{" "}
|
|
168
|
-
{paginatedTransactions.length} on page {currentPage} of{" "}
|
|
169
|
-
{totalPages}
|
|
170
|
-
</Typography>
|
|
171
|
-
</Box>
|
|
172
|
-
<Button
|
|
173
|
-
variant="default"
|
|
174
|
-
onClick={onRefresh}
|
|
175
|
-
loading={isLoadingHistory}
|
|
176
|
-
startIcon={<Search />}
|
|
177
|
-
size="S"
|
|
178
|
-
className="payment-button payment-button-success"
|
|
179
|
-
>
|
|
180
|
-
Refresh
|
|
181
|
-
</Button>
|
|
182
|
-
</Flex>
|
|
183
|
-
</Box>
|
|
184
|
-
|
|
185
|
-
{isLoadingHistory ? (
|
|
186
|
-
<Box padding={4} textAlign="center">
|
|
187
|
-
<Typography>Loading transactions...</Typography>
|
|
188
|
-
</Box>
|
|
189
|
-
) : transactionHistory.length === 0 ? (
|
|
190
|
-
<Box padding={4} textAlign="center">
|
|
191
|
-
<Typography textColor="neutral600">
|
|
192
|
-
No transactions found
|
|
193
|
-
</Typography>
|
|
194
|
-
</Box>
|
|
195
|
-
) : (
|
|
196
|
-
<Box>
|
|
197
|
-
{paginatedTransactions.map((transaction) => (
|
|
198
|
-
<TransactionHistoryItem
|
|
199
|
-
key={transaction.id}
|
|
200
|
-
transaction={transaction}
|
|
201
|
-
/>
|
|
202
|
-
))}
|
|
203
|
-
|
|
204
|
-
{/* Pagination */}
|
|
205
|
-
<Box paddingTop={6} paddingBottom={4}>
|
|
206
|
-
<Card className="payment-card">
|
|
207
|
-
<CardBody padding={4}>
|
|
208
|
-
<Flex justifyContent="space-between" alignItems="center">
|
|
209
|
-
{transactionHistory.length > pageSize &&
|
|
210
|
-
totalPages > 1 ? (
|
|
211
|
-
<Flex gap={3} alignItems="center">
|
|
212
|
-
<Button
|
|
213
|
-
variant="default"
|
|
214
|
-
size="S"
|
|
215
|
-
onClick={() =>
|
|
216
|
-
onPageChange(Math.max(1, currentPage - 1))
|
|
217
|
-
}
|
|
218
|
-
disabled={currentPage === 1}
|
|
219
|
-
className={`payment-button ${
|
|
220
|
-
currentPage === 1 ? "" : "payment-button-success"
|
|
221
|
-
}`}
|
|
222
|
-
style={{
|
|
223
|
-
background:
|
|
224
|
-
currentPage === 1 ? "#f6f6f9" : undefined,
|
|
225
|
-
color: currentPage === 1 ? "#666687" : undefined,
|
|
226
|
-
}}
|
|
227
|
-
>
|
|
228
|
-
← Previous
|
|
229
|
-
</Button>
|
|
230
64
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
size="S"
|
|
248
|
-
onClick={() =>
|
|
249
|
-
onPageChange(
|
|
250
|
-
Math.min(totalPages, currentPage + 1)
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
disabled={currentPage === totalPages}
|
|
254
|
-
className={`payment-button ${
|
|
255
|
-
currentPage === totalPages
|
|
256
|
-
? ""
|
|
257
|
-
: "payment-button-success"
|
|
258
|
-
}`}
|
|
259
|
-
style={{
|
|
260
|
-
background:
|
|
261
|
-
currentPage === totalPages
|
|
262
|
-
? "#f6f6f9"
|
|
263
|
-
: undefined,
|
|
264
|
-
color:
|
|
265
|
-
currentPage === totalPages
|
|
266
|
-
? "#666687"
|
|
267
|
-
: undefined,
|
|
268
|
-
}}
|
|
269
|
-
>
|
|
270
|
-
Next →
|
|
271
|
-
</Button>
|
|
272
|
-
</Flex>
|
|
273
|
-
) : (
|
|
274
|
-
<Typography
|
|
275
|
-
variant="pi"
|
|
276
|
-
textColor="neutral600"
|
|
277
|
-
fontWeight="medium"
|
|
278
|
-
>
|
|
279
|
-
{transactionHistory.length <= pageSize
|
|
280
|
-
? "All transactions shown"
|
|
281
|
-
: "No pagination needed"}
|
|
282
|
-
</Typography>
|
|
283
|
-
)}
|
|
284
|
-
</Flex>
|
|
285
|
-
</CardBody>
|
|
286
|
-
</Card>
|
|
287
|
-
<Typography
|
|
288
|
-
variant="pi"
|
|
289
|
-
textColor="neutral600"
|
|
290
|
-
fontWeight="medium"
|
|
291
|
-
>
|
|
292
|
-
Showing {paginatedTransactions.length} of{" "}
|
|
293
|
-
{transactionHistory.length} transactions
|
|
294
|
-
</Typography>
|
|
295
|
-
</Box>
|
|
296
|
-
</Box>
|
|
297
|
-
)}
|
|
298
|
-
</Box>
|
|
65
|
+
{/* Transaction Table */}
|
|
66
|
+
<TransactionHistoryTable
|
|
67
|
+
transactions={paginatedTransactions}
|
|
68
|
+
isLoading={isLoadingHistory}
|
|
69
|
+
filters={filters}
|
|
70
|
+
onFilterChange={onFilterChange}
|
|
71
|
+
onFilterApply={onFilterApply}
|
|
72
|
+
sorting={sorting}
|
|
73
|
+
onSort={onSort}
|
|
74
|
+
currentPage={currentPage}
|
|
75
|
+
totalPages={totalPages}
|
|
76
|
+
pageSize={pageSize}
|
|
77
|
+
totalItems={transactionHistory.length}
|
|
78
|
+
onPageChange={onPageChange}
|
|
79
|
+
onPageSizeChange={onPageSizeChange}
|
|
80
|
+
/>
|
|
299
81
|
|
|
300
82
|
<Box paddingTop={4}>
|
|
301
83
|
<Typography variant="sigma" textColor="neutral600">
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import { Box, Typography } from "@strapi/design-system";
|
|
3
|
+
import JsonView from "@uiw/react-json-view";
|
|
4
|
+
|
|
5
|
+
const RawDataPopover = ({ transaction, children }) => {
|
|
6
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
7
|
+
const [position, setPosition] = useState("top"); // 'top' or 'bottom'
|
|
8
|
+
const popoverRef = useRef(null);
|
|
9
|
+
const buttonRef = useRef(null);
|
|
10
|
+
|
|
11
|
+
const calculatePosition = (buttonElement) => {
|
|
12
|
+
if (!buttonElement) return "top";
|
|
13
|
+
|
|
14
|
+
const buttonRect = buttonElement.getBoundingClientRect();
|
|
15
|
+
// Find the scrollable parent container
|
|
16
|
+
let scrollableParent = buttonElement.parentElement;
|
|
17
|
+
while (scrollableParent && scrollableParent !== document.body) {
|
|
18
|
+
const style = window.getComputedStyle(scrollableParent);
|
|
19
|
+
if (style.overflow === 'auto' || style.overflowY === 'auto' || style.overflow === 'scroll' || style.overflowY === 'scroll') {
|
|
20
|
+
const parentRect = scrollableParent.getBoundingClientRect();
|
|
21
|
+
const spaceAbove = buttonRect.top - parentRect.top;
|
|
22
|
+
const spaceBelow = parentRect.bottom - parentRect.bottom;
|
|
23
|
+
// RawDataPopover can be up to 500px tall, so we need more space
|
|
24
|
+
// If there's less space above than below (or less than 520px), show popover below
|
|
25
|
+
return spaceAbove < 520 || spaceAbove < spaceBelow ? "bottom" : "top";
|
|
26
|
+
}
|
|
27
|
+
scrollableParent = scrollableParent.parentElement;
|
|
28
|
+
}
|
|
29
|
+
// Fallback: use viewport space
|
|
30
|
+
const spaceAbove = buttonRect.top;
|
|
31
|
+
return spaceAbove < 520 ? "bottom" : "top";
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleButtonClick = () => {
|
|
35
|
+
if (!isOpen && buttonRef.current) {
|
|
36
|
+
// Calculate position before opening
|
|
37
|
+
const newPosition = calculatePosition(buttonRef.current);
|
|
38
|
+
setPosition(newPosition);
|
|
39
|
+
}
|
|
40
|
+
setIsOpen(!isOpen);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const handleClickOutside = (event) => {
|
|
45
|
+
if (
|
|
46
|
+
popoverRef.current &&
|
|
47
|
+
buttonRef.current &&
|
|
48
|
+
!popoverRef.current.contains(event.target) &&
|
|
49
|
+
!buttonRef.current.contains(event.target)
|
|
50
|
+
) {
|
|
51
|
+
setIsOpen(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (isOpen) {
|
|
56
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
57
|
+
return () => {
|
|
58
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}, [isOpen]);
|
|
62
|
+
|
|
63
|
+
if (!transaction) {
|
|
64
|
+
return children;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Box position="relative" style={{ display: "inline-block" }}>
|
|
69
|
+
<Box
|
|
70
|
+
ref={buttonRef}
|
|
71
|
+
onClick={handleButtonClick}
|
|
72
|
+
style={{ display: "inline-block" }}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</Box>
|
|
76
|
+
{isOpen && (
|
|
77
|
+
<Box
|
|
78
|
+
ref={popoverRef}
|
|
79
|
+
position="absolute"
|
|
80
|
+
zIndex={1000}
|
|
81
|
+
left={0}
|
|
82
|
+
{...(position === "top"
|
|
83
|
+
? { bottom: "100%", marginBottom: 2 }
|
|
84
|
+
: { top: "100%", marginTop: 2 }
|
|
85
|
+
)}
|
|
86
|
+
padding={3}
|
|
87
|
+
background="neutral0"
|
|
88
|
+
hasRadius
|
|
89
|
+
style={{
|
|
90
|
+
minWidth: "400px",
|
|
91
|
+
maxWidth: "600px",
|
|
92
|
+
maxHeight: "500px",
|
|
93
|
+
overflow: "auto",
|
|
94
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
|
95
|
+
border: "1px solid var(--strapi-colors-neutral200)",
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
<Box paddingBottom={2}>
|
|
99
|
+
<Typography variant="pi" fontWeight="bold" textColor="neutral800">
|
|
100
|
+
Raw Transaction Data
|
|
101
|
+
</Typography>
|
|
102
|
+
</Box>
|
|
103
|
+
<Box>
|
|
104
|
+
<JsonView value={transaction} style={{ fontSize: "12px" }} />
|
|
105
|
+
</Box>
|
|
106
|
+
</Box>
|
|
107
|
+
)}
|
|
108
|
+
</Box>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default RawDataPopover;
|
|
113
|
+
|
|
@@ -1,23 +1,81 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Badge } from "@strapi/design-system";
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Badge, Box, Typography, Flex } from "@strapi/design-system";
|
|
3
|
+
import { ExclamationMarkCircle } from "@strapi/icons";
|
|
3
4
|
|
|
4
|
-
const StatusBadge = ({ status }) => {
|
|
5
|
+
const StatusBadge = ({ status, transaction }) => {
|
|
6
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
7
|
+
|
|
5
8
|
const statusColors = {
|
|
6
|
-
APPROVED: "
|
|
7
|
-
PENDING: "
|
|
8
|
-
ERROR: "
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
APPROVED: "success200",
|
|
10
|
+
PENDING: "warning200",
|
|
11
|
+
ERROR: "danger200",
|
|
12
|
+
CANCELLED: "warning100",
|
|
13
|
+
REDIRECTED: "success100",
|
|
14
|
+
CREATED: "success100"
|
|
12
15
|
};
|
|
13
16
|
|
|
17
|
+
const getDisplayText = () => {
|
|
18
|
+
if (status === "ERROR" && transaction?.raw_response?.Error?.ErrorCode) {
|
|
19
|
+
return `${status} - ${transaction.raw_response.Error.ErrorCode}`;
|
|
20
|
+
}
|
|
21
|
+
return status;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const displayText = getDisplayText();
|
|
25
|
+
const errorMessage = status === "ERROR" && transaction?.raw_response?.Error?.ErrorMessage
|
|
26
|
+
? transaction.raw_response.Error.ErrorMessage
|
|
27
|
+
: null;
|
|
28
|
+
|
|
29
|
+
const errorCode = status === "ERROR" && transaction?.raw_response?.Error?.ErrorCode
|
|
30
|
+
? transaction.raw_response.Error.ErrorCode
|
|
31
|
+
: null;
|
|
32
|
+
|
|
33
|
+
const showExclamationIcon = status === "ERROR" && !errorCode && !errorMessage;
|
|
34
|
+
|
|
14
35
|
return (
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
36
|
+
<Box
|
|
37
|
+
position="relative"
|
|
38
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
39
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
40
|
+
style={{ display: "inline-block", cursor: status === "ERROR" ? "pointer" : "default" }}
|
|
18
41
|
>
|
|
19
|
-
{
|
|
20
|
-
|
|
42
|
+
<Flex gap={2} alignItems="center">
|
|
43
|
+
<Badge backgroundColor={statusColors[status] || "warning100"}>
|
|
44
|
+
{displayText}
|
|
45
|
+
</Badge>
|
|
46
|
+
{showExclamationIcon && (
|
|
47
|
+
<ExclamationMarkCircle color="danger500"
|
|
48
|
+
style={{
|
|
49
|
+
width: "16px",
|
|
50
|
+
height: "16px"
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
</Flex>
|
|
55
|
+
{isHovered && errorMessage && (
|
|
56
|
+
<Box
|
|
57
|
+
position="absolute"
|
|
58
|
+
zIndex={1000}
|
|
59
|
+
bottom="100%"
|
|
60
|
+
left="50%"
|
|
61
|
+
transform="translateX(-50%)"
|
|
62
|
+
marginBottom={2}
|
|
63
|
+
padding={3}
|
|
64
|
+
background="neutral900"
|
|
65
|
+
hasRadius
|
|
66
|
+
style={{
|
|
67
|
+
whiteSpace: "pre-line",
|
|
68
|
+
minWidth: "200px",
|
|
69
|
+
maxWidth: "300px",
|
|
70
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<Typography variant="pi" textColor="neutral0" style={{ fontSize: "12px" }}>
|
|
74
|
+
Error: {errorMessage}
|
|
75
|
+
</Typography>
|
|
76
|
+
</Box>
|
|
77
|
+
)}
|
|
78
|
+
</Box>
|
|
21
79
|
);
|
|
22
80
|
};
|
|
23
81
|
|