herald-exchange-onramp_offramp-widget 1.0.1 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "herald-exchange-onramp_offramp-widget",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -907,6 +907,9 @@ button:disabled {
907
907
  .input-error {
908
908
  color: var(--faile-sdk-color);
909
909
  }
910
+ .input_error {
911
+ color: var(--faile-sdk-color);
912
+ }
910
913
 
911
914
  .required {
912
915
  color: var(--faile-sdk-color);
@@ -1433,3 +1436,144 @@ button:disabled {
1433
1436
  transform: rotate(1turn);
1434
1437
  }
1435
1438
  }
1439
+
1440
+ /* SANDBOX */
1441
+
1442
+ .card_form_container {
1443
+ display: flex;
1444
+ justify-content: center;
1445
+ align-items: center;
1446
+ padding: 2rem;
1447
+ }
1448
+
1449
+ .card_form {
1450
+ width: 100%;
1451
+ max-width: 400px;
1452
+ }
1453
+
1454
+ .card_form h2 {
1455
+ text-align: center;
1456
+ margin-bottom: 1.5rem;
1457
+ }
1458
+
1459
+ .card_form label {
1460
+ display: block;
1461
+ margin-bottom: 0.3rem;
1462
+ font-weight: 500;
1463
+ }
1464
+
1465
+ .card_form input {
1466
+ width: 100%;
1467
+ padding: 0.5rem;
1468
+ margin-bottom: 0.2rem;
1469
+ border-radius: 6px;
1470
+ border: 1px solid #aaa;
1471
+ font-size: 1rem;
1472
+ }
1473
+
1474
+ .card_row {
1475
+ display: flex;
1476
+ gap: 1rem;
1477
+ }
1478
+
1479
+ .card_row > div {
1480
+ flex: 1;
1481
+ }
1482
+
1483
+ /* SANDBOX Result selection */
1484
+ .result_step_container {
1485
+ max-width: 400px;
1486
+ margin: auto;
1487
+ padding: 2rem;
1488
+ border: 1px solid #ccc;
1489
+ border-radius: 10px;
1490
+ background: transparent;
1491
+ text-align: center;
1492
+ }
1493
+
1494
+ .result_step_container h2 {
1495
+ margin-bottom: 1.5rem;
1496
+ }
1497
+
1498
+ .result_options {
1499
+ display: flex;
1500
+ justify-content: space-between;
1501
+ margin-bottom: 1.5rem;
1502
+ gap: 1rem;
1503
+ }
1504
+
1505
+ .result_option {
1506
+ padding: 1rem;
1507
+ border: 1px solid var(--dark-sdk-color);
1508
+ border-radius: 6px;
1509
+ cursor: pointer;
1510
+ font-size: 1.1rem;
1511
+ transition: all 0.2s ease-in-out;
1512
+ text-align: center;
1513
+ }
1514
+
1515
+ /* SUCCESS selected */
1516
+ .success {
1517
+ background-color: #32cd32;
1518
+ color: white;
1519
+ border-color: #32cd32;
1520
+ font-weight: bold;
1521
+ transform: scale(1.1);
1522
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
1523
+ }
1524
+
1525
+ /* FAILURE selected */
1526
+ .failure {
1527
+ background-color: #dc3545;
1528
+ color: white;
1529
+ border-color: #dc3545;
1530
+ font-weight: bold;
1531
+ transform: scale(1.1);
1532
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
1533
+ }
1534
+
1535
+ .result_option:hover {
1536
+ border-color: var(--primay-sdk-color);
1537
+ }
1538
+
1539
+ /* SELL ADMIN CRYPTO SUCCESS / FAILURE OPTION */
1540
+
1541
+ .sell_admin_crypto_result_options {
1542
+ display: flex;
1543
+
1544
+ align-items: center;
1545
+ margin-bottom: 1.5rem;
1546
+ gap: 1rem;
1547
+ }
1548
+ .sell_admin_crypto_result_option {
1549
+ padding: 0.5rem 1rem;
1550
+ cursor: pointer;
1551
+ font-size: 1rem;
1552
+ transition: all 0.2s ease-in-out;
1553
+ text-align: center;
1554
+ border: 1px solid var(--dark-sdk-color);
1555
+ border-radius: 0.5rem;
1556
+ }
1557
+
1558
+ .sell_admin_crypto_success {
1559
+ background-color: #32cd32;
1560
+ color: white;
1561
+ border-color: #32cd32;
1562
+
1563
+ font-weight: bold;
1564
+ transform: scale(1.1);
1565
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
1566
+ }
1567
+
1568
+ .sell_admin_crypto_failure {
1569
+ background-color: #dc3545;
1570
+ color: white;
1571
+ border-color: #dc3545;
1572
+ font-weight: bold;
1573
+ transform: scale(1.1);
1574
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
1575
+ }
1576
+
1577
+ .hidden_radio {
1578
+ display: none;
1579
+ }
@@ -14,6 +14,7 @@ import type {
14
14
  rulesType,
15
15
  } from "./types";
16
16
  import styles from "../assets/css/style.module.css";
17
+ import SandBox from "./sandbox/SandBox";
17
18
 
18
19
  type NewBuyFieldProps = {
19
20
  apiKey: string;
@@ -67,6 +68,7 @@ const NewBuyField = ({
67
68
  maxAmount: "1000.00000",
68
69
  });
69
70
 
71
+ const [sandboxData, setSandboxData] = useState<any>(null);
70
72
  useEffect(() => {
71
73
  fetchCurrencies();
72
74
  fetchCommissions();
@@ -75,7 +77,6 @@ const NewBuyField = ({
75
77
 
76
78
  const fetchCurrencies = () => {
77
79
  const baseUrl = getBaseUrl(mode);
78
- console.log({ mode, baseUrl });
79
80
 
80
81
  handleApiCall({
81
82
  url: `${baseUrl}/api/lookup/currencies`,
@@ -361,6 +362,10 @@ const NewBuyField = ({
361
362
  .then((data) => {
362
363
  if (data?.data?.redirect_url) {
363
364
  setRedirectURL(data.data.redirect_url);
365
+ setSandboxData({
366
+ ...fiatDetails,
367
+ apiResponse: data?.data,
368
+ });
364
369
  setBuyStep(2);
365
370
  } else if (!data.success) {
366
371
  addToast(data?.message, "error");
@@ -664,20 +669,29 @@ const NewBuyField = ({
664
669
  </div>
665
670
  <div className={styles.bank_titles}>Back</div>
666
671
  </div>
667
- <iframe
668
- title="Buy Crypto"
669
- id="chat-widget"
670
- src={redirectURL}
671
- style={{
672
- width: "100%",
673
- minHeight: "calc(100vh - 400px)",
674
- // minHeight: "calc(100vh - 23%)",
675
- // maxHeight: "calc(100vh - 10%)",
676
- border: "none",
677
- zIndex: "9999",
678
- overflow: "auto",
679
- }}
680
- ></iframe>
672
+ {mode === "development" ? (
673
+ <SandBox
674
+ apiKey={apiKey}
675
+ clientReferenceID={clientReferenceID}
676
+ data={sandboxData}
677
+ setSandboxData={setSandboxData}
678
+ />
679
+ ) : (
680
+ <iframe
681
+ title="Buy Crypto"
682
+ id="chat-widget"
683
+ src={redirectURL}
684
+ style={{
685
+ width: "100%",
686
+ minHeight: "calc(100vh - 400px)",
687
+ // minHeight: "calc(100vh - 23%)",
688
+ // maxHeight: "calc(100vh - 10%)",
689
+ border: "none",
690
+ zIndex: "9999",
691
+ overflow: "auto",
692
+ }}
693
+ ></iframe>
694
+ )}
681
695
  </>
682
696
  )}
683
697
  </>
@@ -201,7 +201,7 @@ const SellAdminCryptoAccount = ({
201
201
  const currency = tokenSellData.selectedCrypto;
202
202
  const expected_amount = tokenSellData.from_amount;
203
203
 
204
- if (walletAddress) {
204
+ if (walletAddress && mode === "production") {
205
205
  const startTimestamp = Date.now();
206
206
  // let rpcUrl = SUPPORTED_RPC_NODES[SupportedTokens[token]];
207
207
 
@@ -219,6 +219,8 @@ const SellAdminCryptoAccount = ({
219
219
  fetchTransactions(token, walletAddress, startTimestamp, expected_amount);
220
220
  }
221
221
  }, 10000);
222
+ } else {
223
+ handleManualHash();
222
224
  }
223
225
  // generateQRDetails(walletAddress, token, tokenSellData.from_currency);
224
226
  }
@@ -423,10 +425,10 @@ const SellAdminCryptoAccount = ({
423
425
  setMonitoring(false);
424
426
  };
425
427
 
426
- // const handleManualHash = () => {
427
- // setMonitoring(false);
428
- // setAutomaticHash(false);
429
- // };
428
+ const handleManualHash = () => {
429
+ setMonitoring(false);
430
+ setAutomaticHash(false);
431
+ };
430
432
  return (
431
433
  <>
432
434
  <div className={styles.bank_head}>
@@ -0,0 +1,211 @@
1
+ import React, { useState } from "react";
2
+ // import "./sandbox.css";
3
+ import styles from "../../assets/css/style.module.css";
4
+ const CardForm = ({
5
+ setSandboxStep,
6
+ }: {
7
+ setSandboxStep: React.Dispatch<React.SetStateAction<number>>;
8
+ }) => {
9
+ const [form, setForm] = useState({
10
+ name: "",
11
+ number: "",
12
+ expiry: "",
13
+ cvv: "",
14
+ });
15
+
16
+ const [errors, setErrors] = useState({
17
+ name: "",
18
+ number: "",
19
+ expiry: "",
20
+ cvv: "",
21
+ });
22
+
23
+ const formatCardNumber = (value: string) => {
24
+ return value
25
+ .replace(/\D/g, "") // Remove non-digits
26
+ .replace(/(.{4})/g, "$1 ") // Add space every 4 digits
27
+ .trim();
28
+ };
29
+
30
+ const formatExpiry = (value: string) => {
31
+ const cleaned = value.replace(/\D/g, "");
32
+ if (cleaned.length >= 3) {
33
+ return cleaned.slice(0, 2) + "/" + cleaned.slice(2, 4);
34
+ }
35
+ return cleaned;
36
+ };
37
+
38
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
39
+ const { name, value } = e.target;
40
+
41
+ let error = "";
42
+
43
+ let formattedValue = value;
44
+ if (name === "name") {
45
+ if (!formattedValue.trim()) {
46
+ error = "Cardholder name is required.";
47
+ } else {
48
+ const isAlpha = /^[A-Za-z\s]+$/.test(formattedValue);
49
+ if (!isAlpha) {
50
+ error = "Enter only alphabets";
51
+ }
52
+ }
53
+ }
54
+
55
+ if (name === "number") {
56
+ formattedValue = formatCardNumber(value);
57
+
58
+ if (!/^\d{4} \d{4} \d{4} \d{4}$/.test(formattedValue)) {
59
+ error = "Enter a valid 16-digit card number.";
60
+ }
61
+ }
62
+ if (name === "expiry") {
63
+ formattedValue = formatExpiry(value);
64
+
65
+ if (!/^(0[1-9]|1[0-2])\/\d{2}$/.test(formattedValue)) {
66
+ error = "Use MM/YY format.";
67
+ } else {
68
+ const [mm, yy] = formattedValue.split("/").map(Number);
69
+ const now = new Date();
70
+ const currentMonth = now.getMonth() + 1;
71
+ const currentYear = now.getFullYear() % 100; // last two digits
72
+
73
+ if (yy < currentYear || (yy === currentYear && mm < currentMonth)) {
74
+ error = "Expiry date must be in the future.";
75
+ }
76
+ }
77
+ }
78
+ if (name === "cvv") {
79
+ formattedValue = value.replace(/\D/g, "");
80
+
81
+ if (!/^\d{3,4}$/.test(formattedValue)) {
82
+ error = "CVV must be 3 or 4 digits.";
83
+ }
84
+ } // only digits
85
+
86
+ setErrors((prevErrors) => ({
87
+ ...prevErrors,
88
+ [name]: error,
89
+ }));
90
+
91
+ setForm((prev) => ({ ...prev, [name]: formattedValue }));
92
+ };
93
+
94
+ const validate = () => {
95
+ const newErrors = {
96
+ name: "",
97
+ number: "",
98
+ expiry: "",
99
+ cvv: "",
100
+ };
101
+
102
+ if (!form.name.trim()) {
103
+ newErrors.name = "Cardholder name is required.";
104
+ }
105
+
106
+ if (!/^\d{4} \d{4} \d{4} \d{4}$/.test(form.number)) {
107
+ newErrors.number = "Enter a valid 16-digit card number.";
108
+ }
109
+
110
+ if (!/^(0[1-9]|1[0-2])\/\d{2}$/.test(form.expiry)) {
111
+ newErrors.expiry = "Use MM/YY format.";
112
+ } else {
113
+ const [mm, yy] = form.expiry.split("/").map(Number);
114
+ const now = new Date();
115
+ const currentMonth = now.getMonth() + 1;
116
+ const currentYear = now.getFullYear() % 100; // last two digits
117
+
118
+ if (yy < currentYear || (yy === currentYear && mm < currentMonth)) {
119
+ newErrors.expiry = "Expiry date must be in the future.";
120
+ }
121
+ }
122
+
123
+ if (!/^\d{3,4}$/.test(form.cvv)) {
124
+ newErrors.cvv = "CVV must be 3 or 4 digits.";
125
+ }
126
+
127
+ setErrors(newErrors);
128
+
129
+ return Object.values(newErrors).every((error) => !error);
130
+ };
131
+ const handleSubmit = (e: React.FormEvent) => {
132
+ e.preventDefault();
133
+ if (validate()) {
134
+ console.log("Valid card data:", form);
135
+ setSandboxStep(2);
136
+ }
137
+ };
138
+
139
+ return (
140
+ <div className={styles.card_form_container}>
141
+ <form onSubmit={handleSubmit} className={styles.card_form} noValidate>
142
+ <h2>Enter Card Details</h2>
143
+
144
+ <label htmlFor="name">Cardholder Name</label>
145
+ <input
146
+ type="text"
147
+ id="name"
148
+ name="name"
149
+ placeholder="John Doe"
150
+ value={form.name}
151
+ onChange={handleChange}
152
+ required
153
+ />
154
+ {errors.name && <span className={styles.input_error}>{errors.name}</span>}
155
+
156
+ <label htmlFor="number">Card Number</label>
157
+ <input
158
+ type="text"
159
+ id="number"
160
+ name="number"
161
+ placeholder="1234 5678 9012 3456"
162
+ maxLength={19}
163
+ value={form.number}
164
+ onChange={handleChange}
165
+ required
166
+ />
167
+ {errors.number && <span className={styles.input_error}>{errors.number}</span>}
168
+
169
+ <div className={styles.card_row}>
170
+ <div>
171
+ <label htmlFor="expiry">Expiry (MM/YY)</label>
172
+ <input
173
+ type="text"
174
+ id="expiry"
175
+ name="expiry"
176
+ placeholder="08/27"
177
+ maxLength={5}
178
+ value={form.expiry}
179
+ onChange={handleChange}
180
+ required
181
+ />
182
+ {errors.expiry && <span className={styles.input_error}>{errors.expiry}</span>}
183
+ </div>
184
+
185
+ <div>
186
+ <label htmlFor="cvv">CVV</label>
187
+ <input
188
+ type="password"
189
+ id="cvv"
190
+ name="cvv"
191
+ placeholder="123"
192
+ maxLength={4}
193
+ value={form.cvv}
194
+ onChange={handleChange}
195
+ required
196
+ />
197
+ {errors.cvv && <span className={styles.input_error}>{errors.cvv}</span>}
198
+ </div>
199
+ </div>
200
+
201
+ <div className={styles.ramp_action}>
202
+ <button type="submit" className={`${styles.action_btn} ${styles.primary}`}>
203
+ Continue
204
+ </button>
205
+ </div>
206
+ </form>
207
+ </div>
208
+ );
209
+ };
210
+
211
+ export default CardForm;
@@ -0,0 +1,121 @@
1
+ import React, { useState } from "react";
2
+ import styles from "../../assets/css/style.module.css";
3
+ import { handleApiCall } from "../api";
4
+ import { getBaseUrl } from "../utils";
5
+ import { useToast } from "../../hooks/toastProvider";
6
+ import { Spinner } from "../loader";
7
+
8
+ const CardResultOption = ({
9
+ setSandboxStep,
10
+ data,
11
+ apiKey,
12
+ clientReferenceID,
13
+ setSandboxData,
14
+ }: {
15
+ setSandboxStep: React.Dispatch<React.SetStateAction<number>>;
16
+ data: any;
17
+ apiKey: string;
18
+ clientReferenceID: string;
19
+ setSandboxData: React.Dispatch<React.SetStateAction<any>>;
20
+ }) => {
21
+ const { addToast } = useToast();
22
+ const [transactionLoading, setTransactionLoading] = useState(false);
23
+ const handleConfirm = () => {
24
+ setTransactionLoading(true);
25
+
26
+ const baseUrl = getBaseUrl("development");
27
+
28
+ handleApiCall({
29
+ url: `${baseUrl}/api/onramp/transactions/status/${data?.apiResponse?.onramp_transaction?.unique_id}`,
30
+ method: "POST",
31
+ headers: {
32
+ "X-API-KEY": apiKey,
33
+ "User-Id": clientReferenceID,
34
+ },
35
+ onSuccess: (result: any) => {
36
+ if (result?.success) {
37
+ setSandboxData({
38
+ ...data,
39
+ transactionDetails: result?.data,
40
+ });
41
+ addToast(`${result?.message}`, "success");
42
+ } else {
43
+ setSandboxData({
44
+ ...data,
45
+ transactionDetails: result?.data,
46
+ });
47
+ addToast(`${result?.message}`, "error");
48
+ }
49
+ setTransactionLoading(false);
50
+ setSandboxStep(3);
51
+ },
52
+ onError: (error: any) => {
53
+ addToast(`${error}`, "error");
54
+ setTransactionLoading(false);
55
+ },
56
+ });
57
+ };
58
+
59
+ return (
60
+ <div className={styles.details_frame}>
61
+ <div className={styles.f_details_head}>
62
+ <div></div>
63
+ <div className={styles.f_details_titles}>Overview</div>
64
+ </div>
65
+ <div className={styles.f_details_info_box}>
66
+ <div className={styles.f_details_card}>
67
+ <span>You pay</span>
68
+ <span>
69
+ {data?.apiResponse?.onramp_transaction?.from_value}
70
+ {data?.apiResponse?.onramp_transaction?.from}
71
+ </span>
72
+ </div>
73
+ <div className={styles.f_details_card}>
74
+ <span>Rate</span>
75
+ <span>
76
+ 1 {data?.apiResponse?.onramp_transaction?.from} =
77
+ {data?.apiResponse?.onramp_transaction?.exchange_rate}{" "}
78
+ {data?.apiResponse?.onramp_transaction?.to}
79
+ </span>
80
+ </div>
81
+ <div className={styles.f_details_card}>
82
+ <span>Fee</span>
83
+ <span>
84
+ {data?.apiResponse?.onramp_transaction?.service_fee}
85
+ {data?.apiResponse?.onramp_transaction?.from}
86
+ </span>
87
+ </div>
88
+ <div className={styles.f_details_card}>
89
+ <span>You Receive</span>
90
+ <span>
91
+ {data?.apiResponse?.onramp_transaction?.to_value}{" "}
92
+ {data?.apiResponse?.onramp_transaction?.to}
93
+ </span>
94
+ </div>
95
+
96
+ <div className={styles.f_details_card}>
97
+ <span>Wallet</span>
98
+ <span>
99
+ {data?.apiResponse?.onramp_transaction?.wallet_address?.length > 15
100
+ ? `${data?.apiResponse?.onramp_transaction?.wallet_address?.slice(
101
+ 0,
102
+ 5
103
+ )}...${data?.apiResponse?.onramp_transaction?.wallet_address?.slice(-5)}`
104
+ : data?.apiResponse?.onramp_transaction?.wallet_address}
105
+ </span>
106
+ </div>
107
+ </div>
108
+ <div className={styles.ramp_action}>
109
+ <button
110
+ type="button"
111
+ onClick={handleConfirm}
112
+ className={`${styles.action_btn} ${styles.primary}`}
113
+ >
114
+ Confirm {transactionLoading && <Spinner />}
115
+ </button>
116
+ </div>
117
+ </div>
118
+ );
119
+ };
120
+
121
+ export default CardResultOption;
@@ -0,0 +1,35 @@
1
+ import { useState } from "react";
2
+ import CardForm from "./CardForm";
3
+ import CardResultOption from "./Overview";
4
+ import CardTransactionDetails from "./SandboxTransactionDetails";
5
+
6
+ const SandBox = ({
7
+ data,
8
+ apiKey,
9
+ clientReferenceID,
10
+ setSandboxData,
11
+ }: {
12
+ data: any;
13
+ apiKey: string;
14
+ clientReferenceID: string;
15
+ setSandboxData: (data: any) => void;
16
+ }) => {
17
+ const [sandboxStep, setSandboxStep] = useState(1);
18
+ return (
19
+ <div>
20
+ {sandboxStep === 1 && <CardForm setSandboxStep={setSandboxStep} />}
21
+ {sandboxStep === 2 && (
22
+ <CardResultOption
23
+ apiKey={apiKey}
24
+ clientReferenceID={clientReferenceID}
25
+ data={data}
26
+ setSandboxStep={setSandboxStep}
27
+ setSandboxData={setSandboxData}
28
+ />
29
+ )}
30
+ {sandboxStep === 3 && <CardTransactionDetails data={data} />}
31
+ </div>
32
+ );
33
+ };
34
+
35
+ export default SandBox;