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.
@@ -0,0 +1,113 @@
1
+ import React from "react";
2
+ import {
3
+ Box,
4
+ Flex,
5
+ Select,
6
+ Option,
7
+ Button,
8
+ TextInput,
9
+ Typography,
10
+ } from "@strapi/design-system";
11
+ import { Search } from "@strapi/icons";
12
+
13
+ const TransactionHistoryTableFilters = ({
14
+ filters,
15
+ onFilterChange,
16
+ onFilterApply,
17
+ isLoading,
18
+ }) => {
19
+ const handleDateFromClick = (e) => {
20
+ const input = e.target.closest('div')?.querySelector('input[type="date"]');
21
+ if (input) {
22
+ input.showPicker?.();
23
+ }
24
+ };
25
+
26
+ const handleDateToClick = (e) => {
27
+ const input = e.target.closest('div')?.querySelector('input[type="date"]');
28
+ if (input) {
29
+ input.showPicker?.();
30
+ }
31
+ };
32
+ return (
33
+ <Box marginBottom={2}>
34
+ <Flex gap={3} marginBottom={3} alignItems="center">
35
+ <TextInput
36
+ label="Search"
37
+ name="search"
38
+ value={filters?.search || ""}
39
+ onChange={(e) => onFilterChange("search", e.target.value)}
40
+ placeholder="Search by Status, Transaction ID, or Reference"
41
+ style={{ flex: 1, minWidth: "250px" }}
42
+ />
43
+ <Select
44
+ label="Status"
45
+ name="status"
46
+ value={filters?.status || ""}
47
+ onChange={(value) => onFilterChange("status", value)}
48
+ placeholder="All Statuses"
49
+ style={{ width: "180px", minWidth: "180px" }}
50
+ >
51
+ <Option value="">All Statuses</Option>
52
+ <Option value="APPROVED">APPROVED</Option>
53
+ <Option value="PENDING">PENDING</Option>
54
+ <Option value="ERROR">ERROR</Option>
55
+ <Option value="CANCELLED">CANCELLED</Option>
56
+ <Option value="REDIRECTED">REDIRECTED</Option>
57
+ <Option value="CREATED">CREATED</Option>
58
+ </Select>
59
+ <Select
60
+ label="Request Type"
61
+ name="request_type"
62
+ value={filters?.request_type || ""}
63
+ onChange={(value) => onFilterChange("request_type", value)}
64
+ placeholder="Select request type"
65
+ style={{ width: "220px", minWidth: "220px" }}
66
+ >
67
+ <Option value="">All Types</Option>
68
+ <Option value="preauthorization">Preauthorization</Option>
69
+ <Option value="authorization">Authorization</Option>
70
+ <Option value="capture">Capture</Option>
71
+ <Option value="refund">Refund</Option>
72
+ </Select>
73
+ </Flex>
74
+ <Flex gap={3} marginBottom={3} alignItems="center">
75
+ <Box onClick={handleDateFromClick} style={{ cursor: "pointer" }}>
76
+ <TextInput
77
+ label="Date From"
78
+ name="date_from"
79
+ value={filters?.date_from || ""}
80
+ onChange={(e) => onFilterChange("date_from", e.target.value)}
81
+ placeholder="YYYY-MM-DD"
82
+ type="date"
83
+ style={{ minWidth: "150px" }}
84
+ />
85
+ </Box>
86
+ <Box onClick={handleDateToClick} style={{ cursor: "pointer" }}>
87
+ <TextInput
88
+ label="Date To"
89
+ name="date_to"
90
+ value={filters?.date_to || ""}
91
+ onChange={(e) => onFilterChange("date_to", e.target.value)}
92
+ placeholder="YYYY-MM-DD"
93
+ type="date"
94
+ style={{ minWidth: "150px" }}
95
+ />
96
+ </Box>
97
+ <Typography variant="pi" textColor="neutral600" style={{ fontSize: "12px", marginTop: "20px" }}>
98
+ By default, the last 30 days are shown
99
+ </Typography>
100
+ </Flex>
101
+ <Button
102
+ variant="default"
103
+ onClick={onFilterApply}
104
+ loading={isLoading}
105
+ startIcon={<Search />}
106
+ >
107
+ Apply Filters
108
+ </Button>
109
+ </Box>
110
+ );
111
+ };
112
+
113
+ export default TransactionHistoryTableFilters;
@@ -0,0 +1,180 @@
1
+ import React, { useState } from "react";
2
+ import {
3
+ Box,
4
+ Flex,
5
+ Button,
6
+ TextInput,
7
+ Typography,
8
+ Select,
9
+ Option,
10
+ } from "@strapi/design-system";
11
+ import { ArrowLeft, ArrowRight, ChevronLeft, ChevronRight } from "@strapi/icons";
12
+
13
+ const TransactionHistoryTablePagination = ({
14
+ currentPage,
15
+ totalPages,
16
+ pageSize,
17
+ totalItems,
18
+ onPageChange,
19
+ onPageSizeChange,
20
+ isLoading,
21
+ }) => {
22
+ const [pageInput, setPageInput] = useState(currentPage.toString());
23
+
24
+ // Update input when currentPage changes externally
25
+ React.useEffect(() => {
26
+ setPageInput(currentPage.toString());
27
+ }, [currentPage]);
28
+
29
+ const handlePageInputChange = (e) => {
30
+ const value = e.target.value;
31
+ // Allow empty input or valid numbers
32
+ if (value === "" || /^\d+$/.test(value)) {
33
+ setPageInput(value);
34
+ }
35
+ };
36
+
37
+ const handlePageInputBlur = () => {
38
+ const pageNum = parseInt(pageInput, 10);
39
+ if (pageNum && pageNum >= 1 && pageNum <= totalPages) {
40
+ onPageChange(pageNum);
41
+ } else {
42
+ // Reset to current page if invalid
43
+ setPageInput(currentPage.toString());
44
+ }
45
+ };
46
+
47
+ const handlePageInputKeyPress = (e) => {
48
+ if (e.key === "Enter") {
49
+ handlePageInputBlur();
50
+ }
51
+ };
52
+
53
+ const goToFirstPage = () => {
54
+ if (currentPage > 1) {
55
+ onPageChange(1);
56
+ }
57
+ };
58
+
59
+ const goToLastPage = () => {
60
+ if (currentPage < totalPages) {
61
+ onPageChange(totalPages);
62
+ }
63
+ };
64
+
65
+ const goToPreviousPage = () => {
66
+ if (currentPage > 1) {
67
+ onPageChange(currentPage - 1);
68
+ }
69
+ };
70
+
71
+ const goToNextPage = () => {
72
+ if (currentPage < totalPages) {
73
+ onPageChange(currentPage + 1);
74
+ }
75
+ };
76
+
77
+ const startItem = totalItems > 0 ? (currentPage - 1) * pageSize + 1 : 0;
78
+ const endItem = Math.min(currentPage * pageSize, totalItems);
79
+
80
+ return (
81
+ <Box>
82
+ <Flex
83
+ justifyContent="space-between"
84
+ alignItems="center"
85
+ gap={4}
86
+ wrap="wrap"
87
+ >
88
+ <Flex alignItems="center" gap={2}>
89
+ <Typography variant="pi" textColor="neutral600">
90
+ {totalItems > 0
91
+ ? `Showing ${startItem}-${endItem} of ${totalItems} transactions`
92
+ : `No transactions`}
93
+ </Typography>
94
+ <Flex alignItems="center" gap={2}>
95
+ <Typography variant="pi" textColor="neutral600">
96
+ Page size:
97
+ </Typography>
98
+ <Select
99
+ value={pageSize.toString()}
100
+ onChange={(value) => onPageSizeChange(parseInt(value, 10))}
101
+ disabled={isLoading}
102
+ style={{ width: "80px", minWidth: "80px" }}
103
+ >
104
+ <Option value="10">10</Option>
105
+ <Option value="20">20</Option>
106
+ <Option value="30">30</Option>
107
+ <Option value="50">50</Option>
108
+ <Option value="100">100</Option>
109
+ </Select>
110
+ </Flex>
111
+ </Flex>
112
+
113
+ {totalPages > 1 && (
114
+ <Flex alignItems="center" gap={2}>
115
+ <Button
116
+ variant="tertiary"
117
+ onClick={goToFirstPage}
118
+ disabled={currentPage === 1 || isLoading}
119
+ startIcon={<ChevronLeft />}
120
+ size="S"
121
+ >
122
+ First
123
+ </Button>
124
+
125
+ <Button
126
+ variant="tertiary"
127
+ onClick={goToPreviousPage}
128
+ disabled={currentPage === 1 || isLoading}
129
+ startIcon={<ArrowLeft />}
130
+ size="S"
131
+ >
132
+ Prev
133
+ </Button>
134
+
135
+ <Flex alignItems="center" gap={2}>
136
+ <Typography variant="pi" textColor="neutral600">
137
+ Page
138
+ </Typography>
139
+ <TextInput
140
+ value={pageInput}
141
+ onChange={handlePageInputChange}
142
+ onBlur={handlePageInputBlur}
143
+ onKeyPress={handlePageInputKeyPress}
144
+ disabled={isLoading}
145
+ aria-label="Page number"
146
+ style={{ width: "60px", textAlign: "center" }}
147
+ />
148
+ <Typography variant="pi" textColor="neutral600">
149
+ of {totalPages}
150
+ </Typography>
151
+ </Flex>
152
+
153
+ <Button
154
+ variant="tertiary"
155
+ onClick={goToNextPage}
156
+ disabled={currentPage === totalPages || isLoading}
157
+ endIcon={<ArrowRight />}
158
+ size="S"
159
+ >
160
+ Next
161
+ </Button>
162
+
163
+ <Button
164
+ variant="tertiary"
165
+ onClick={goToLastPage}
166
+ disabled={currentPage === totalPages || isLoading}
167
+ endIcon={<ChevronRight />}
168
+ size="S"
169
+ >
170
+ Last
171
+ </Button>
172
+ </Flex>
173
+ )}
174
+ </Flex>
175
+ </Box>
176
+ );
177
+ };
178
+
179
+ export default TransactionHistoryTablePagination;
180
+
@@ -0,0 +1,225 @@
1
+ import React from "react";
2
+ import {
3
+ Box,
4
+ Table,
5
+ Thead,
6
+ Tbody,
7
+ Tr,
8
+ Th,
9
+ Td,
10
+ Typography,
11
+ Button,
12
+ Flex,
13
+ } from "@strapi/design-system";
14
+ import { User, File, ArrowUp, ArrowDown } from "@strapi/icons";
15
+ import StatusBadge from "../StatusBadge";
16
+ import CustomerInfoPopover from "../CustomerInfoPopover";
17
+ import RawDataPopover from "../RawDataPopover";
18
+ import TransactionHistoryTableFilters from "./TransactionHistoryTableFilters";
19
+ import TransactionHistoryTablePagination from "./TransactionHistoryTablePagination";
20
+
21
+ const TransactionHistoryTable = ({
22
+ transactions,
23
+ isLoading,
24
+ filters,
25
+ onFilterChange,
26
+ onFilterApply,
27
+ sorting,
28
+ onSort,
29
+ currentPage,
30
+ totalPages,
31
+ pageSize,
32
+ totalItems,
33
+ onPageChange,
34
+ onPageSizeChange,
35
+ }) => {
36
+ const SortableHeader = ({ column, label, sortable = true }) => {
37
+ const isActive = sorting?.sortBy === column;
38
+ const sortOrder = isActive ? sorting?.sortOrder : null;
39
+
40
+ const handleClick = () => {
41
+ if (sortable && onSort) {
42
+ onSort(column);
43
+ }
44
+ };
45
+
46
+ return (
47
+ <Th>
48
+ <Flex
49
+ alignItems="center"
50
+ gap={2}
51
+ onClick={handleClick}
52
+ style={{ cursor: sortable ? "pointer" : "default" }}
53
+ >
54
+ <Typography fontWeight="bold" textColor="primary500">
55
+ {label}
56
+ </Typography>
57
+ {sortable && (
58
+ <Box style={{ display: "flex", alignItems: "center" }}>
59
+ {sortOrder === "asc" && <ArrowUp size={8} />}
60
+ {sortOrder === "desc" && <ArrowDown size={8} />}
61
+ {!isActive && (
62
+ <Box style={{ opacity: 0.3 }}>
63
+ <ArrowUp size={8} />
64
+ </Box>
65
+ )}
66
+ </Box>
67
+ )}
68
+ </Flex>
69
+ </Th>
70
+ );
71
+ };
72
+ const getCardTypeName = (cardtype) => {
73
+ switch (cardtype) {
74
+ case "V":
75
+ return "Visa";
76
+ case "M":
77
+ return "MasterCard";
78
+ default:
79
+ return cardtype || "";
80
+ }
81
+ };
82
+
83
+ const getPaymentMethodName = (clearingtype, wallettype, cardtype) => {
84
+ switch (clearingtype) {
85
+ case "cc":
86
+ const cardTypeName = getCardTypeName(cardtype);
87
+ return cardTypeName ? `CC / ${cardTypeName}` : "Credit Card";
88
+ case "sb":
89
+ return "Online Banking";
90
+ case "wlt":
91
+ return wallettype === "PPE" ? "PayPal" : "Wallet";
92
+ case "elv":
93
+ return "Direct Debit (SEPA)";
94
+ default:
95
+ return "Unknown";
96
+ }
97
+ };
98
+
99
+ const formatAmount = (amount, currency) => {
100
+ return `${(amount / 100).toFixed(2)} ${currency}`;
101
+ };
102
+
103
+ const formatDate = (dateString) => {
104
+ return new Date(dateString).toLocaleString("de-DE", {
105
+ year: "numeric",
106
+ month: "2-digit",
107
+ day: "2-digit",
108
+ hour: "2-digit",
109
+ minute: "2-digit",
110
+ });
111
+ };
112
+
113
+ return (
114
+ <Box>
115
+ {/* Table Filters */}
116
+ <TransactionHistoryTableFilters
117
+ filters={filters}
118
+ onFilterChange={onFilterChange}
119
+ onFilterApply={onFilterApply}
120
+ isLoading={isLoading}
121
+ />
122
+
123
+
124
+ <Box style={{ maxHeight: "600px", overflow: "auto", marginBottom: "1rem" }}>
125
+ <Table colCount={6} rowCount={transactions.length}>
126
+ <Thead>
127
+ <Tr>
128
+ <SortableHeader column="amount" label="Amount" />
129
+ <SortableHeader column="created_at" label="Created At" />
130
+ <SortableHeader column="status" label="Status" />
131
+ <SortableHeader column="reference" label="Reference" />
132
+ <SortableHeader column="method" label="Method" />
133
+ <Th>
134
+ <Typography fontWeight="bold" textColor="primary500">
135
+ Actions
136
+ </Typography>
137
+ </Th>
138
+ </Tr>
139
+ </Thead>
140
+ <Tbody>
141
+ {transactions.length === 0 && !isLoading ? (
142
+ <Tr>
143
+ <Td colSpan={6}>
144
+ <Box padding={6}>
145
+ <Typography textAlign="center" textColor="neutral600">
146
+ No transactions found
147
+ </Typography>
148
+ </Box>
149
+ </Td>
150
+ </Tr>
151
+ ) : (
152
+ transactions.map((transaction) => (
153
+ <Tr key={transaction.id}>
154
+ <Td>
155
+ <Typography variant="pi" fontWeight="bold" textColor="primary600">
156
+ {formatAmount(transaction.amount, transaction.currency)}
157
+ </Typography>
158
+ </Td>
159
+ <Td>
160
+ <Typography variant="pi" textColor="neutral800">
161
+ {formatDate(transaction.created_at)}
162
+ </Typography>
163
+ </Td>
164
+ <Td>
165
+ <StatusBadge status={transaction.status} transaction={transaction} />
166
+ </Td>
167
+ <Td>
168
+ <Typography variant="pi" textColor="neutral800">
169
+ {transaction.reference}
170
+ </Typography>
171
+ </Td>
172
+ <Td>
173
+ <Typography variant="pi" textColor="neutral800">
174
+ {getPaymentMethodName(
175
+ transaction.raw_request?.clearingtype,
176
+ transaction.raw_request?.wallettype,
177
+ transaction.raw_request?.cardtype
178
+ )}
179
+ </Typography>
180
+ </Td>
181
+ <Td>
182
+ <Flex gap={2} justifyContent="flex-start">
183
+ <CustomerInfoPopover transaction={transaction}>
184
+ <Button
185
+ variant="secondary"
186
+ size="S"
187
+ startIcon={<User />}
188
+ >
189
+ View Customer
190
+ </Button>
191
+ </CustomerInfoPopover>
192
+ <RawDataPopover transaction={transaction}>
193
+ <Button
194
+ variant="secondary"
195
+ size="S"
196
+ startIcon={<File />}
197
+ >
198
+ View Raw Data
199
+ </Button>
200
+ </RawDataPopover>
201
+ </Flex>
202
+ </Td>
203
+ </Tr>
204
+ ))
205
+ )}
206
+ </Tbody>
207
+ </Table>
208
+ </Box>
209
+
210
+ {/* Pagination */}
211
+ <TransactionHistoryTablePagination
212
+ currentPage={currentPage}
213
+ totalPages={totalPages}
214
+ pageSize={pageSize}
215
+ totalItems={totalItems}
216
+ onPageChange={onPageChange}
217
+ onPageSizeChange={onPageSizeChange}
218
+ isLoading={isLoading}
219
+ />
220
+ </Box>
221
+ );
222
+ };
223
+
224
+ export default TransactionHistoryTable;
225
+
@@ -114,6 +114,8 @@ const App = () => {
114
114
  filters={transactionHistory.filters}
115
115
  onFilterChange={transactionHistory.handleFilterChange}
116
116
  onFilterApply={transactionHistory.handleFilterApply}
117
+ sorting={transactionHistory.sorting}
118
+ onSort={transactionHistory.handleSort}
117
119
  isLoadingHistory={transactionHistory.isLoadingHistory}
118
120
  transactionHistory={transactionHistory.transactionHistory}
119
121
  paginatedTransactions={transactionHistory.paginatedTransactions}
@@ -122,6 +124,7 @@ const App = () => {
122
124
  pageSize={transactionHistory.pageSize}
123
125
  onRefresh={transactionHistory.loadTransactionHistory}
124
126
  onPageChange={transactionHistory.handlePageChange}
127
+ onPageSizeChange={transactionHistory.handlePageSizeChange}
125
128
  selectedTransaction={transactionHistory.selectedTransaction}
126
129
  onTransactionSelect={transactionHistory.handleTransactionSelect}
127
130
  paymentActions={paymentActions}
@@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
2
2
  import { useNotification } from "@strapi/helper-plugin";
3
3
  import payoneRequests from "../utils/api";
4
4
 
5
- const PAGE_SIZE = 10;
5
+ const DEFAULT_PAGE_SIZE = 10;
6
6
 
7
7
  const useTransactionHistory = () => {
8
8
  const toggleNotification = useNotification();
@@ -10,12 +10,32 @@ const useTransactionHistory = () => {
10
10
  const [isLoadingHistory, setIsLoadingHistory] = useState(false);
11
11
  const [selectedTransaction, setSelectedTransaction] = useState(null);
12
12
  const [currentPage, setCurrentPage] = useState(1);
13
+ const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
14
+ // Calculate default dates
15
+ const getDefaultDateFrom = () => {
16
+ const date = new Date();
17
+ date.setDate(date.getDate() - 30);
18
+ return date.toISOString().split('T')[0];
19
+ };
20
+
21
+ const getDefaultDateTo = () => {
22
+ const date = new Date();
23
+ date.setDate(date.getDate() + 1); // Add 1 day to include today's transactions
24
+ return date.toISOString().split('T')[0];
25
+ };
26
+
13
27
  const [filters, setFilters] = useState({
14
28
  search: "",
15
29
  request_type: "",
16
30
  payment_method: "",
17
- date_from: "",
18
- date_to: ""
31
+ date_from: getDefaultDateFrom(),
32
+ date_to: getDefaultDateTo(),
33
+ status: ""
34
+ });
35
+
36
+ const [sorting, setSorting] = useState({
37
+ sortBy: null,
38
+ sortOrder: null // 'asc' or 'desc'
19
39
  });
20
40
 
21
41
  useEffect(() => {
@@ -25,7 +45,12 @@ const useTransactionHistory = () => {
25
45
  const loadTransactionHistory = async () => {
26
46
  setIsLoadingHistory(true);
27
47
  try {
28
- const result = await payoneRequests.getTransactionHistory(filters);
48
+ const params = { ...filters };
49
+ if (sorting.sortBy && sorting.sortOrder) {
50
+ params.sort_by = sorting.sortBy;
51
+ params.sort_order = sorting.sortOrder;
52
+ }
53
+ const result = await payoneRequests.getTransactionHistory(params);
29
54
  setTransactionHistory(result.data || []);
30
55
  setCurrentPage(1);
31
56
  } catch (error) {
@@ -46,6 +71,37 @@ const useTransactionHistory = () => {
46
71
  loadTransactionHistory();
47
72
  };
48
73
 
74
+ const handleSort = (column) => {
75
+ setSorting((prev) => {
76
+ // If clicking the same column, cycle through: null -> asc -> desc -> null
77
+ if (prev.sortBy === column) {
78
+ if (!prev.sortOrder) {
79
+ return { sortBy: column, sortOrder: "asc" };
80
+ } else if (prev.sortOrder === "asc") {
81
+ return { sortBy: column, sortOrder: "desc" };
82
+ } else {
83
+ return { sortBy: null, sortOrder: null };
84
+ }
85
+ } else {
86
+ // If clicking a different column, reset and set new column to asc
87
+ return { sortBy: column, sortOrder: "asc" };
88
+ }
89
+ });
90
+ };
91
+
92
+ // Reload when sorting changes (but not on initial mount)
93
+ const [isInitialMount, setIsInitialMount] = useState(true);
94
+
95
+ useEffect(() => {
96
+ if (isInitialMount) {
97
+ setIsInitialMount(false);
98
+ return;
99
+ }
100
+ // Only reload if sorting is actually set or cleared
101
+ loadTransactionHistory();
102
+ // eslint-disable-next-line react-hooks/exhaustive-deps
103
+ }, [sorting.sortBy, sorting.sortOrder]);
104
+
49
105
  const handleTransactionSelect = (transaction) => {
50
106
  if (selectedTransaction?.id === transaction?.id) {
51
107
  setSelectedTransaction(null);
@@ -59,10 +115,16 @@ const useTransactionHistory = () => {
59
115
  setSelectedTransaction(null);
60
116
  };
61
117
 
118
+ const handlePageSizeChange = (newPageSize) => {
119
+ setPageSize(newPageSize);
120
+ setCurrentPage(1); // Reset to first page when page size changes
121
+ setSelectedTransaction(null);
122
+ };
123
+
62
124
  // Pagination calculations
63
- const totalPages = Math.ceil(transactionHistory.length / PAGE_SIZE);
64
- const startIndex = (currentPage - 1) * PAGE_SIZE;
65
- const endIndex = startIndex + PAGE_SIZE;
125
+ const totalPages = Math.ceil(transactionHistory.length / pageSize);
126
+ const startIndex = (currentPage - 1) * pageSize;
127
+ const endIndex = startIndex + pageSize;
66
128
  const paginatedTransactions = transactionHistory.slice(startIndex, endIndex);
67
129
 
68
130
  return {
@@ -71,13 +133,16 @@ const useTransactionHistory = () => {
71
133
  isLoadingHistory,
72
134
  selectedTransaction,
73
135
  filters,
136
+ sorting,
74
137
  currentPage,
75
138
  totalPages,
76
- pageSize: PAGE_SIZE,
139
+ pageSize,
77
140
  handleFilterChange,
78
141
  handleFilterApply,
142
+ handleSort,
79
143
  handleTransactionSelect,
80
144
  handlePageChange,
145
+ handlePageSizeChange,
81
146
  loadTransactionHistory
82
147
  };
83
148
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-payone-provider",
3
- "version": "1.6.7",
3
+ "version": "4.6.9",
4
4
  "description": "Strapi plugin for Payone payment gateway integration",
5
5
  "license": "MIT",
6
6
  "maintainers": [
@@ -12,7 +12,8 @@
12
12
  "dependencies": {
13
13
  "apple-pay-button": "^1.2.1",
14
14
  "axios": "^1.6.3",
15
- "prop-types": "^15.7.2"
15
+ "prop-types": "^15.7.2",
16
+ "@uiw/react-json-view": "^2.0.0-alpha.40"
16
17
  },
17
18
  "devDependencies": {
18
19
  "react": "^18.2.0",