strapi-plugin-payone-provider 1.6.0 → 1.6.1

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.
Files changed (42) hide show
  1. package/admin/src/components/Initializer/index.js +16 -0
  2. package/admin/src/components/PluginIcon/index.js +6 -0
  3. package/admin/src/pages/App/components/AppHeader.js +55 -0
  4. package/admin/src/pages/App/components/AppTabs.js +158 -0
  5. package/admin/src/pages/App/components/ApplePayButton.js +950 -0
  6. package/admin/src/pages/App/components/ApplePayConfig.js +364 -0
  7. package/admin/src/pages/App/components/ApplePayConfigPanel.js +81 -0
  8. package/admin/src/pages/App/components/ConfigurationPanel.js +280 -0
  9. package/admin/src/pages/App/components/DocsPanel.js +1057 -0
  10. package/admin/src/pages/App/components/GooglePayConfig.js +217 -0
  11. package/admin/src/pages/App/components/GooglePayConfigPanel.js +82 -0
  12. package/admin/src/pages/App/components/GooglePaybutton.js +300 -0
  13. package/admin/src/pages/App/components/HistoryPanel.js +285 -0
  14. package/admin/src/pages/App/components/PaymentActionsPanel.js +190 -0
  15. package/admin/src/pages/App/components/StatusBadge.js +24 -0
  16. package/admin/src/pages/App/components/TransactionHistoryItem.js +377 -0
  17. package/admin/src/pages/App/components/icons/BankIcon.js +10 -0
  18. package/admin/src/pages/App/components/icons/ChevronDownIcon.js +9 -0
  19. package/admin/src/pages/App/components/icons/ChevronUpIcon.js +9 -0
  20. package/admin/src/pages/App/components/icons/CreditCardIcon.js +9 -0
  21. package/admin/src/pages/App/components/icons/ErrorIcon.js +10 -0
  22. package/admin/src/pages/App/components/icons/InfoIcon.js +9 -0
  23. package/admin/src/pages/App/components/icons/PaymentIcon.js +10 -0
  24. package/admin/src/pages/App/components/icons/PendingIcon.js +9 -0
  25. package/admin/src/pages/App/components/icons/PersonIcon.js +9 -0
  26. package/admin/src/pages/App/components/icons/SuccessIcon.js +9 -0
  27. package/admin/src/pages/App/components/icons/WalletIcon.js +9 -0
  28. package/admin/src/pages/App/components/icons/index.js +11 -0
  29. package/admin/src/pages/App/components/paymentActions/AuthorizationForm.js +195 -0
  30. package/admin/src/pages/App/components/paymentActions/CaptureForm.js +65 -0
  31. package/admin/src/pages/App/components/paymentActions/CardDetailsInput.js +191 -0
  32. package/admin/src/pages/App/components/paymentActions/PaymentMethodSelector.js +156 -0
  33. package/admin/src/pages/App/components/paymentActions/PaymentResult.js +148 -0
  34. package/admin/src/pages/App/components/paymentActions/PreauthorizationForm.js +199 -0
  35. package/admin/src/pages/App/components/paymentActions/RefundForm.js +90 -0
  36. package/admin/src/pages/App/index.js +127 -0
  37. package/admin/src/pages/hooks/usePaymentActions.js +171 -0
  38. package/package.json +49 -49
  39. package/server/bootstrap.js +7 -0
  40. package/server/controllers/payone.js +0 -2
  41. package/server/services/transactionService.js +28 -0
  42. package/server/utils/paymentMethodParams.js +1 -7
@@ -0,0 +1,148 @@
1
+ import React from "react";
2
+ import {
3
+ Box,
4
+ Card,
5
+ CardBody,
6
+ Divider,
7
+ Flex,
8
+ Stack,
9
+ Typography,
10
+ Alert
11
+ } from "@strapi/design-system";
12
+ import StatusBadge from "../StatusBadge";
13
+ import { formatTransactionData } from "../../../utils/formatTransactionData";
14
+
15
+ const PaymentResult = ({ paymentError, paymentResult }) => {
16
+ if (!paymentError && !paymentResult) {
17
+ return null;
18
+ }
19
+
20
+ const status = paymentResult?.status || paymentResult?.Status || "";
21
+ const errorCode = paymentResult?.errorcode || paymentResult?.errorCode || paymentResult?.ErrorCode;
22
+ const errorMessage = paymentResult?.errormessage || paymentResult?.errorMessage || paymentResult?.ErrorMessage;
23
+ const customerMessage = paymentResult?.customermessage || paymentResult?.customerMessage || paymentResult?.CustomerMessage;
24
+ const isError = status === "ERROR" || status === "INVALID" || errorCode;
25
+
26
+ return (
27
+ <>
28
+ {paymentError && (
29
+ <Alert
30
+ variant="danger"
31
+ title="Error"
32
+ className="payment-alert"
33
+ >
34
+ {paymentError}
35
+ </Alert>
36
+ )}
37
+
38
+ {paymentResult && (
39
+ <Card className="payment-result-card">
40
+ <CardBody>
41
+ <Stack spacing={4}>
42
+ <Flex justifyContent="space-between" alignItems="center">
43
+ <Typography variant="delta" as="h3" className="payment-section-title">
44
+ Payment Result
45
+ </Typography>
46
+ {(status || paymentResult.Status) && (
47
+ <StatusBadge status={status || paymentResult.Status} />
48
+ )}
49
+ </Flex>
50
+
51
+ <hr className="payment-divider" style={{ margin: '16px 0' }} />
52
+
53
+ {/* Show error information prominently if error */}
54
+ {isError && (
55
+ <Alert variant="danger" title="Transaction Failed">
56
+ <Stack spacing={2}>
57
+ {errorCode && (
58
+ <Typography variant="pi">
59
+ <strong>Error Code:</strong> {errorCode}
60
+ </Typography>
61
+ )}
62
+ {errorMessage && (
63
+ <Typography variant="pi">
64
+ <strong>Error Message:</strong> {errorMessage}
65
+ </Typography>
66
+ )}
67
+ {customerMessage && (
68
+ <Typography variant="pi">
69
+ <strong>Customer Message:</strong> {customerMessage}
70
+ </Typography>
71
+ )}
72
+ </Stack>
73
+ </Alert>
74
+ )}
75
+
76
+ <Box>
77
+ <Typography variant="omega" fontWeight="semiBold" marginBottom={2}>
78
+ Full Response Details:
79
+ </Typography>
80
+ <Stack spacing={3}>
81
+ {formatTransactionData(paymentResult).map((item, index) => (
82
+ <Flex
83
+ key={index}
84
+ justifyContent="space-between"
85
+ alignItems="start"
86
+ style={{
87
+ padding: '8px 0',
88
+ borderBottom: index < formatTransactionData(paymentResult).length - 1 ? '1px solid #e8e8ea' : 'none'
89
+ }}
90
+ >
91
+ <Typography
92
+ variant="pi"
93
+ textColor="neutral600"
94
+ style={{ minWidth: "200px", fontWeight: '500' }}
95
+ >
96
+ {item.key}:
97
+ </Typography>
98
+ <Typography
99
+ variant="pi"
100
+ style={{
101
+ flex: 1,
102
+ textAlign: "right",
103
+ fontWeight: '400',
104
+ wordBreak: 'break-word',
105
+ fontFamily: item.key.toLowerCase().includes('raw') ? 'monospace' : 'inherit',
106
+ fontSize: item.key.toLowerCase().includes('raw') ? '11px' : 'inherit'
107
+ }}
108
+ >
109
+ {item.value}
110
+ </Typography>
111
+ </Flex>
112
+ ))}
113
+ </Stack>
114
+ </Box>
115
+
116
+ {/* 3DS Required Warning */}
117
+ {paymentResult?.is3DSRequired && !paymentResult?.redirectUrl && (
118
+ <Alert variant="warning" title="3D Secure Authentication Required">
119
+ <Stack spacing={2}>
120
+ <Typography variant="pi">
121
+ Payone requires 3D Secure authentication, but no redirect URL was provided in the response.
122
+ </Typography>
123
+ <Typography variant="pi" fontWeight="semiBold">
124
+ Possible solutions:
125
+ </Typography>
126
+ <Typography variant="pi" component="ul" style={{ marginLeft: '20px' }}>
127
+ <li>Check Payone portal configuration for 3DS settings</li>
128
+ <li>Verify that redirect URLs (successurl, errorurl, backurl) are properly configured</li>
129
+ <li>Ensure you're using test mode with proper test credentials</li>
130
+ <li>Check if 3dscheck request is needed before authorization</li>
131
+ </Typography>
132
+ <Typography variant="pi" textColor="neutral600" marginTop={2}>
133
+ <strong>Error Code:</strong> {paymentResult?.errorCode || paymentResult?.ErrorCode || "4219"}
134
+ </Typography>
135
+ </Stack>
136
+ </Alert>
137
+ )}
138
+
139
+ </Stack>
140
+ </CardBody>
141
+ </Card>
142
+ )}
143
+ </>
144
+ );
145
+ };
146
+
147
+ export default PaymentResult;
148
+
@@ -0,0 +1,199 @@
1
+ import React from "react";
2
+ import { Box, Flex, Typography, TextInput, Button } from "@strapi/design-system";
3
+ import { Play } from "@strapi/icons";
4
+ import GooglePayButton from "../GooglePaybutton";
5
+ import ApplePayButton from "../ApplePayButton";
6
+ import CardDetailsInput from "./CardDetailsInput";
7
+
8
+ const PreauthorizationForm = ({
9
+ paymentAmount,
10
+ setPaymentAmount,
11
+ preauthReference,
12
+ setPreauthReference,
13
+ isProcessingPayment,
14
+ onPreauthorization,
15
+ paymentMethod,
16
+ settings,
17
+ googlePayToken,
18
+ setGooglePayToken,
19
+ applePayToken,
20
+ setApplePayToken,
21
+ cardtype,
22
+ setCardtype,
23
+ cardpan,
24
+ setCardpan,
25
+ cardexpiredate,
26
+ setCardexpiredate,
27
+ cardcvc2,
28
+ setCardcvc2,
29
+ isLiveMode = false
30
+ }) => {
31
+ const handleGooglePayToken = (token, paymentData) => {
32
+ if (!token) {
33
+ return;
34
+ }
35
+ setGooglePayToken(token);
36
+ onPreauthorization(token);
37
+ };
38
+
39
+ const handleGooglePayError = (error) => {
40
+ if (onError) {
41
+ onError(error);
42
+ }
43
+ };
44
+
45
+ const handleApplePayToken = async (token, paymentData) => {
46
+ if (!token) {
47
+ console.error("[Apple Pay] Token is missing in handleApplePayToken");
48
+ return Promise.reject(new Error("Token is missing"));
49
+ }
50
+
51
+ console.log("[Apple Pay] handleApplePayToken called with token:", {
52
+ hasToken: !!token,
53
+ tokenLength: token?.length,
54
+ paymentData: !!paymentData
55
+ });
56
+
57
+ // IMPORTANT: Set token in state immediately (synchronously)
58
+ // This ensures the token is saved before the dialog closes
59
+ setApplePayToken(token);
60
+
61
+ console.log("[Apple Pay] Token saved to state successfully");
62
+
63
+ // Don't call onPreauthorization immediately
64
+ // Let the user manually trigger the payment using the button
65
+ // This prevents the dialog from closing prematurely if there's an error
66
+ // The dialog will close with success, and the user will see the "Process Preauthorization" button
67
+
68
+ // Return success immediately so the dialog closes properly
69
+ // The actual payment processing will happen when the user clicks the button
70
+ return Promise.resolve({
71
+ success: true,
72
+ message: "Token received successfully. Please click 'Process Preauthorization' to complete the payment."
73
+ });
74
+ };
75
+
76
+ const handleApplePayError = (error) => {
77
+ if (onError) {
78
+ onError(error);
79
+ }
80
+ };
81
+ return (
82
+ <Flex direction="column" alignItems="stretch" gap={4}>
83
+ <Flex direction="row" gap={2}>
84
+ <Typography variant="omega" fontWeight="semiBold" textColor="neutral800" className="payment-form-title">
85
+ Preauthorization
86
+ </Typography>
87
+ <Typography variant="pi" textColor="neutral600" className="payment-form-description">
88
+ Reserve an amount on a credit card without capturing it immediately.
89
+ </Typography>
90
+ </Flex>
91
+
92
+ <Flex gap={4} wrap="wrap">
93
+ <TextInput
94
+ label="Amount (in cents) *"
95
+ name="paymentAmount"
96
+ value={paymentAmount}
97
+ onChange={(e) => setPaymentAmount(e.target.value)}
98
+ placeholder="Enter amount (e.g., 1000 for €10.00)"
99
+ hint="Amount in cents (e.g., 1000 = €10.00)"
100
+ required
101
+ className="payment-input"
102
+ style={{ flex: 1, minWidth: "250px" }}
103
+ />
104
+
105
+ <TextInput
106
+ label="Reference *"
107
+ name="preauthReference"
108
+ value={preauthReference}
109
+ onChange={(e) => setPreauthReference(e.target.value)}
110
+ placeholder="Auto-generated if empty"
111
+ hint="Reference will be auto-generated if left empty"
112
+ className="payment-input"
113
+ style={{ flex: 1, minWidth: "250px" }}
114
+ />
115
+ </Flex>
116
+
117
+ {paymentMethod === "cc" && settings?.enable3DSecure && (
118
+ <Box marginTop={4}>
119
+ <CardDetailsInput
120
+ cardtype={cardtype}
121
+ setCardtype={setCardtype}
122
+ cardpan={cardpan}
123
+ setCardpan={setCardpan}
124
+ cardexpiredate={cardexpiredate}
125
+ setCardexpiredate={setCardexpiredate}
126
+ cardcvc2={cardcvc2}
127
+ setCardcvc2={setCardcvc2}
128
+ />
129
+ </Box>
130
+ )}
131
+
132
+ {paymentMethod === "gpp" ? (
133
+ <GooglePayButton
134
+ amount={paymentAmount}
135
+ currency="EUR"
136
+ onTokenReceived={handleGooglePayToken}
137
+ onError={handleGooglePayError}
138
+ settings={settings}
139
+ />
140
+ ) : paymentMethod === "apl" ? (
141
+ <Box>
142
+ <ApplePayButton
143
+ amount={paymentAmount}
144
+ currency="EUR"
145
+ onTokenReceived={handleApplePayToken}
146
+ onError={handleApplePayError}
147
+ settings={settings}
148
+ />
149
+ {applePayToken && (
150
+ <Box marginTop={3} style={{ width: "100%", display: "flex", flexDirection: "column", alignItems: "flex-start", gap: "8px" }}>
151
+ <Typography variant="pi" textColor="success600" style={{ marginBottom: "8px", fontWeight: "bold" }}>
152
+ ✓ Apple Pay token received. You can now process the preauthorization:
153
+ </Typography>
154
+ <Button
155
+ variant="default"
156
+ onClick={() => onPreauthorization(applePayToken)}
157
+ loading={isProcessingPayment}
158
+ startIcon={<Play />}
159
+ style={{ maxWidth: '200px' }}
160
+ disabled={!paymentAmount.trim() || !preauthReference.trim() || isLiveMode}
161
+ className="payment-button payment-button-primary"
162
+ >
163
+ Process Preauthorization
164
+ </Button>
165
+ </Box>
166
+ )}
167
+ {!applePayToken && (
168
+ <Box marginTop={3} style={{ width: "100%", display: "flex", flexDirection: "column", alignItems: "flex-start", gap: "8px" }}>
169
+ <Typography variant="pi" textColor="neutral600" style={{ marginBottom: "8px" }}>
170
+ Apple Pay is not available on localhost. You can test the payment flow without Apple Pay token:
171
+ </Typography>
172
+ </Box>
173
+ )}
174
+ </Box>
175
+ ) : (
176
+ <Button
177
+ variant="default"
178
+ onClick={onPreauthorization}
179
+ loading={isProcessingPayment}
180
+ startIcon={<Play />}
181
+ style={{ maxWidth: '200px' }}
182
+ className="payment-button payment-button-primary"
183
+ disabled={
184
+ !paymentAmount.trim() ||
185
+ (paymentMethod === "cc" &&
186
+ settings?.enable3DSecure !== false &&
187
+ (!cardtype || !cardpan || !cardexpiredate || !cardcvc2)) ||
188
+ isLiveMode
189
+ }
190
+ >
191
+ Process Preauthorization
192
+ </Button>
193
+ )}
194
+ </Flex>
195
+ );
196
+ };
197
+
198
+ export default PreauthorizationForm;
199
+
@@ -0,0 +1,90 @@
1
+ import React from "react";
2
+ import { Box, Flex, Typography, TextInput, Button } from "@strapi/design-system";
3
+ import { Play } from "@strapi/icons";
4
+
5
+ const RefundForm = ({
6
+ paymentAmount,
7
+ setPaymentAmount,
8
+ refundTxid,
9
+ setRefundTxid,
10
+ refundSequenceNumber,
11
+ setRefundSequenceNumber,
12
+ refundReference,
13
+ setRefundReference,
14
+ isProcessingPayment,
15
+ onRefund
16
+ }) => {
17
+ return (
18
+ <Flex direction="column" alignItems="stretch" gap={4}>
19
+ <Flex direction="row" gap={2}>
20
+ <Typography variant="omega" fontWeight="semiBold" textColor="neutral800" className="payment-form-title">
21
+ Refund
22
+ </Typography>
23
+ <Typography variant="pi" textColor="neutral600" className="payment-form-description">
24
+ Refund a previously captured amount.
25
+ </Typography>
26
+ </Flex>
27
+
28
+ <Flex gap={4} wrap="wrap">
29
+ <TextInput
30
+ label="Transaction ID"
31
+ name="refundTxid"
32
+ value={refundTxid}
33
+ onChange={(e) => setRefundTxid(e.target.value)}
34
+ placeholder="Enter TxId from capture"
35
+ hint="Transaction ID from a previous capture"
36
+ className="payment-input"
37
+ style={{ flex: 1, minWidth: "200px" }}
38
+ />
39
+
40
+ <TextInput
41
+ label="Sequence Number"
42
+ name="refundSequenceNumber"
43
+ value={refundSequenceNumber}
44
+ onChange={(e) => setRefundSequenceNumber(e.target.value)}
45
+ placeholder="2"
46
+ hint="Sequence number for this refund (1-127) and by default for first 2"
47
+ className="payment-input"
48
+ style={{ flex: 1, minWidth: "200px" }}
49
+ />
50
+
51
+ <TextInput
52
+ label="Amount (in cents)"
53
+ name="refundAmount"
54
+ value={paymentAmount}
55
+ onChange={(e) => setPaymentAmount(e.target.value)}
56
+ placeholder="1000"
57
+ hint="Amount in cents to refund (will be negative)"
58
+ className="payment-input"
59
+ style={{ flex: 1, minWidth: "200px" }}
60
+ />
61
+
62
+ <TextInput
63
+ label="Reference"
64
+ name="refundReference"
65
+ value={refundReference}
66
+ onChange={(e) => setRefundReference(e.target.value)}
67
+ placeholder="Optional reference"
68
+ hint="Optional reference for this refund"
69
+ className="payment-input"
70
+ style={{ flex: 1, minWidth: "200px" }}
71
+ />
72
+ </Flex>
73
+
74
+ <Button
75
+ variant="default"
76
+ onClick={onRefund}
77
+ loading={isProcessingPayment}
78
+ startIcon={<Play />}
79
+ style={{ maxWidth: '200px' }}
80
+ className="payment-button payment-button-primary"
81
+ disabled={!refundTxid.trim() || !paymentAmount.trim()}
82
+ >
83
+ Process Refund
84
+ </Button>
85
+ </Flex>
86
+ );
87
+ };
88
+
89
+ export default RefundForm;
90
+
@@ -0,0 +1,127 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { useLocation, useHistory } from "react-router-dom";
3
+ import { Layout, ContentLayout, Box } from "@strapi/design-system";
4
+ import useSettings from "../hooks/useSettings";
5
+ import useTransactionHistory from "../hooks/useTransactionHistory";
6
+ import usePaymentActions from "../hooks/usePaymentActions";
7
+ import AppHeader from "./components/AppHeader";
8
+ import AppTabs from "./components/AppTabs";
9
+ import ApplePayConfigPanel from "./components/ApplePayConfigPanel";
10
+ import GooglePayConfigPanel from "./components/GooglePayConfigPanel";
11
+ import "./styles.css";
12
+ import pluginId from "../../pluginId";
13
+
14
+ const App = () => {
15
+ const location = useLocation();
16
+ const history = useHistory();
17
+ const [activeTab, setActiveTab] = useState(0);
18
+
19
+ // Custom hooks
20
+ const settings = useSettings();
21
+ const transactionHistory = useTransactionHistory();
22
+ const paymentActions = usePaymentActions();
23
+
24
+ useEffect(() => {
25
+ if (location.pathname.includes('/apple-pay-config') || location.pathname.includes('/google-pay-config')) {
26
+ } else {
27
+ const tabFromPath = location.pathname.includes('/history') ? 1 :
28
+ location.pathname.includes('/payment-actions') ? 2 :
29
+ location.pathname.includes('/documentation') ? 3 : 0;
30
+ setActiveTab(tabFromPath);
31
+ }
32
+ }, [location.pathname]);
33
+
34
+ const isApplePayConfigPage = location.pathname.includes('/apple-pay-config');
35
+ const isGooglePayConfigPage = location.pathname.includes('/google-pay-config');
36
+
37
+ if (isApplePayConfigPage) {
38
+ return (
39
+ <Layout>
40
+ <AppHeader
41
+ title="Apple Pay Configuration"
42
+ activeTab={null}
43
+ isSaving={settings.isSaving}
44
+ onSave={settings.handleSave}
45
+ onBack={() => history.push(`/plugins/${pluginId}`)}
46
+ />
47
+ <ContentLayout>
48
+ <Box padding={6}>
49
+ <ApplePayConfigPanel
50
+ settings={settings.settings}
51
+ onInputChange={settings.handleInputChange}
52
+ isSaving={settings.isSaving}
53
+ onSave={settings.handleSave}
54
+ />
55
+ </Box>
56
+ </ContentLayout>
57
+ </Layout>
58
+ );
59
+ }
60
+
61
+ if (isGooglePayConfigPage) {
62
+ return (
63
+ <Layout>
64
+ <AppHeader
65
+ title="Google Pay Configuration"
66
+ activeTab={null}
67
+ isSaving={settings.isSaving}
68
+ onSave={settings.handleSave}
69
+ onBack={() => history.push(`/plugins/${pluginId}`)}
70
+ />
71
+ <ContentLayout>
72
+ <Box padding={6}>
73
+ <GooglePayConfigPanel
74
+ settings={settings.settings}
75
+ onInputChange={settings.handleInputChange}
76
+ isSaving={settings.isSaving}
77
+ onSave={settings.handleSave}
78
+ onBack={() => history.push(`/plugins/${pluginId}`)}
79
+ />
80
+ </Box>
81
+ </ContentLayout>
82
+ </Layout>
83
+ );
84
+ }
85
+
86
+ return (
87
+ <Layout>
88
+ <AppHeader
89
+ activeTab={activeTab}
90
+ isSaving={settings.isSaving}
91
+ onSave={settings.handleSave}
92
+ />
93
+ <ContentLayout>
94
+ <Box padding={6}>
95
+ <AppTabs
96
+ activeTab={activeTab}
97
+ setActiveTab={setActiveTab}
98
+ settings={settings.settings}
99
+ isSaving={settings.isSaving}
100
+ isTesting={settings.isTesting}
101
+ testResult={settings.testResult}
102
+ onSave={settings.handleSave}
103
+ onTestConnection={settings.handleTestConnection}
104
+ onInputChange={settings.handleInputChange}
105
+ filters={transactionHistory.filters}
106
+ onFilterChange={transactionHistory.handleFilterChange}
107
+ onFilterApply={transactionHistory.handleFilterApply}
108
+ isLoadingHistory={transactionHistory.isLoadingHistory}
109
+ transactionHistory={transactionHistory.transactionHistory}
110
+ paginatedTransactions={transactionHistory.paginatedTransactions}
111
+ currentPage={transactionHistory.currentPage}
112
+ totalPages={transactionHistory.totalPages}
113
+ pageSize={transactionHistory.pageSize}
114
+ onRefresh={transactionHistory.loadTransactionHistory}
115
+ onPageChange={transactionHistory.handlePageChange}
116
+ selectedTransaction={transactionHistory.selectedTransaction}
117
+ onTransactionSelect={transactionHistory.handleTransactionSelect}
118
+ paymentActions={paymentActions}
119
+ history={history}
120
+ />
121
+ </Box>
122
+ </ContentLayout>
123
+ </Layout>
124
+ );
125
+ };
126
+
127
+ export default App;