strapi-plugin-payone-provider 1.6.7 → 4.6.10

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.
@@ -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
- <Box
232
- padding={2}
233
- background="#f6f6f9"
234
- borderRadius="6px"
235
- >
236
- <Typography
237
- variant="pi"
238
- textColor="neutral600"
239
- fontWeight="bold"
240
- >
241
- Page {currentPage} of {totalPages}
242
- </Typography>
243
- </Box>
244
-
245
- <Button
246
- variant="default"
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: "success",
7
- PENDING: "warning",
8
- ERROR: "danger",
9
- FAILED: "danger",
10
- INVALID: "danger",
11
- REDIRECT: "secondary"
9
+ APPROVED: "success200",
10
+ PENDING: "warning200",
11
+ ERROR: "danger200",
12
+ CANCELLED: "warning100",
13
+ REDIRECT: "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
- <Badge
16
- textColor="neutral0"
17
- backgroundColor={statusColors[status] || "default"}
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
- {status}
20
- </Badge>
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