/ src / components / Wallet / SendModal.tsx
SendModal.tsx
  1  import React, {FC, useRef, useState, useEffect } from "react";
  2  import * as Dialog from "@radix-ui/react-dialog";
  3  import { Button } from "@radix-ui/themes";
  4  import { Cross2Icon } from "@radix-ui/react-icons";
  5  import styles from "./sendmodal.module.scss";
  6  import { AccountRecordsDB, TransactionDB } from "../Database/Database";
  7  import { useLiveQuery } from "dexie-react-hooks";
  8  import { Principal } from "@dfinity/principal";
  9  import useAuth from "@/auth/hooks/useAuth";
 10  import { convertToBigInt } from "./utils/convertToBigInt";
 11  
 12  interface SendFormProps {
 13    open: any,
 14    onClose: any,
 15    selectedAccount: any,
 16  }
 17  
 18  const SendModal: FC<SendFormProps> = ({ open, onClose, selectedAccount }) => {
 19    const dialogRef = useRef(null);
 20    const [destination, setDestination] = useState("");
 21    const [amount, setAmount] = useState(null);
 22    const [inputAmount, setInputAmount] = useState(null);
 23    const [usdAmount, setUsdAmount] = useState(null);
 24    const [usdBalance, setUsdBalance] = useState(null);
 25    const [ethExchangeRate] = useState(2513.62);
 26    const [eurcExchangeRate] = useState(1.09);
 27    const [latestBalance, setLatestBalance] = useState(null);
 28    const [updatedBalance, setUpdatedBalance] = useState(null);
 29    const [selectedDestinationAccount, setSelectedDestinationAccount] = useState(null);
 30    const [senderUpdatedUsdBalance, setSenderUpdatedUsdBalance] = useState(null);
 31    const [receiverUsdBalance, setReceiverUsdBalance] = useState(null);
 32    const [ckBtcBalance, setCkBtcBalance] = useState<bigint | null | undefined>();
 33    const { principal, ledgerActor } = useAuth();
 34    
 35    const allAccountRecordsData = useLiveQuery(
 36      () => {
 37        return AccountRecordsDB.accountRecordsDetails
 38          .orderBy('timestamp')
 39          .reverse()
 40          .toArray()
 41          .then(records => {
 42            const uniqueRecords = records.reduce((unique, record) => {
 43              if (!unique.some(r => r.accountName === record.accountName)) {
 44                unique.push(record);
 45              }
 46              return unique;
 47            }, []);
 48            return uniqueRecords;
 49          });
 50      },
 51      [],
 52    );
 53  
 54    const newBalance = useLiveQuery(
 55      async () => {
 56        if (selectedAccount && selectedAccount.accountName) {
 57          const result = await AccountRecordsDB.accountRecordsDetails
 58            .where('accountName')
 59            .equals(selectedAccount.accountName)
 60            .last();
 61          return result;
 62        } else {
 63          return null;
 64        }
 65      },
 66      [selectedAccount, amount],
 67    );
 68  
 69    useEffect(() => {
 70      const fetchNewBalance = async () => {
 71        try {
 72          const newBalanceResult = await newBalance;
 73          if (newBalanceResult) {
 74            console.log("New balance result object: ", newBalanceResult);
 75            console.log("Your new balance is: ", parseFloat(newBalanceResult.nativeBalance));
 76            setLatestBalance(newBalanceResult.nativeBalance);
 77          } else {
 78            setLatestBalance(selectedAccount?.balance);
 79          }
 80        } catch (error) {
 81          console.error("Error updating balance:", error);
 82        }
 83      };
 84    
 85      fetchNewBalance();
 86    }, [newBalance, selectedAccount, amount]);
 87    
 88  
 89    const handleAmountChange = (event) => {
 90      event.preventDefault();
 91      const newInputAmount = event.target.value;
 92      setInputAmount(newInputAmount);
 93      setAmount(null);
 94    };
 95    
 96    
 97    const fetchSelectedDestinationAccountAmount = () => {
 98      if (selectedDestinationAccount) {
 99        console.log("Selected destination: ", selectedDestinationAccount);
100        setAmount(selectedDestinationAccount?.offerAmount || null);
101      } else {
102        console.log("Destination account has not been selected hence can't find destination amount");
103      }
104    };
105    
106    useEffect(() => {
107      fetchSelectedDestinationAccountAmount();
108    }, [selectedDestinationAccount, inputAmount]);
109    
110    
111  
112    const handleDestinationChange = (event) => {
113      event.preventDefault();
114      const selectedAccountName = event.target.value;
115      console.log("Selected account Id: ", selectedAccountName);
116      const selectedAccount = allAccountRecordsData.find(account => {
117        return String(account.accountName) === selectedAccountName;
118      }); 
119      console.log("Selected account destination account: ", selectedAccount);
120      setSelectedDestinationAccount(selectedAccount);
121      setAmount(null);
122    };
123    
124    
125    const dollarAmount = () => {
126      if (selectedDestinationAccount?.currency === "ETH") {
127        const calculatedAmount = inputAmount ? inputAmount : amount * ethExchangeRate;
128        setUsdAmount(calculatedAmount);
129      } else if (selectedDestinationAccount?.currency === "EURC") {
130        const calculatedAmount = inputAmount ? inputAmount : amount * eurcExchangeRate;
131        setUsdAmount(calculatedAmount);
132      } else {
133        setUsdAmount(inputAmount || amount);
134      }
135    };  
136  
137  const dollarBalance = () => {
138    if (selectedAccount?.currency === "ETH") {
139      const dollarBalance = latestBalance * ethExchangeRate;
140      console.log("Latest balance in send: ", latestBalance);
141      console.log("Usd Balance in send: ", dollarBalance);
142      setUsdBalance(dollarBalance);
143    } else if (selectedAccount?.currency === "EURC") {
144      const dollarBalance = latestBalance * eurcExchangeRate;
145      setUsdBalance(dollarBalance);
146    } else {
147      setUsdBalance(latestBalance || 0);
148    }
149  };
150  
151  useEffect(() => {
152    if (selectedAccount) {
153      dollarAmount();
154      dollarBalance();
155    }
156  }, [inputAmount, latestBalance, selectedDestinationAccount]);
157  
158  
159    const handleClose = () => {
160      if (onClose) {
161        onClose();
162      }
163      console.log("Selected Account arrived send component: ", selectedAccount);
164    };
165  
166  
167    const updateAccountRecordsSenderData = async () => {
168      try {
169        const accountName = selectedAccount?.accountName;
170        console.log("Account name: ", accountName);
171  
172        const senderNativeUpdatedBalance = latestBalance - amount
173        console.log("Sender native updated balance: ", senderNativeUpdatedBalance);
174        const senderUpdatedUsdBalance = parseFloat(usdBalance - usdAmount);
175        console.log("Sender updated usd balance: ", senderUpdatedUsdBalance);
176  
177        const latestUniqueRecord = await AccountRecordsDB.accountRecordsDetails
178        .where('accountName')
179        .equals(accountName)
180        .last()
181  
182        if(latestUniqueRecord) {
183          const accountKey = latestUniqueRecord.accountName
184          console.log("Account key: ", accountKey);
185          await AccountRecordsDB.accountRecordsDetails.update(accountKey,{
186            state: "Escrow",
187            sentAmount: usdAmount,
188            nativeBalance: senderNativeUpdatedBalance,
189            usdBalance: senderUpdatedUsdBalance,
190            timestamp: new Date(),
191          });
192        } else {
193          console.log("No existing records found for the specified account");
194        }
195        console.log(`Succesfully ${accountName} changed state to Purchase`)
196        setUpdatedBalance(updatedBalance);  
197      } catch (error) {
198        console.log("Could update to the account records database with sender records: ", error);
199      }
200    }
201  
202    const getReceiverUsdBalance = () => {
203      if (selectedDestinationAccount?.currency === "ETH") {
204        const receiverDollarBalance = selectedDestinationAccount.balance * ethExchangeRate;
205        setReceiverUsdBalance(receiverDollarBalance);
206      } else if (selectedDestinationAccount?.currency === "EURC") {
207        const receiverDollarBalance = selectedDestinationAccount.balance * eurcExchangeRate;
208        setReceiverUsdBalance(receiverDollarBalance);
209      } else {
210        setReceiverUsdBalance(selectedDestinationAccount?.balance || 0);
211      }
212    };
213  
214    useEffect(()=> {
215      getReceiverUsdBalance();
216    },[selectedDestinationAccount]);
217  
218    const updateAccountRecordsReceiverData = async () => {
219      try {
220        const accountName = selectedDestinationAccount?.accountName;
221        console.log("Desination account name: ", accountName);
222  
223        const receiverUpdatedNativeBalance = selectedDestinationAccount?.balance - amount
224        console.log(" Receiver updated native balance: ", receiverUpdatedNativeBalance);
225        const receiverUpdatedUsdBalance = receiverUsdBalance + usdAmount;
226        console.log("Receiver updated usd balance: ", receiverUpdatedUsdBalance);
227  
228        const latestUniqueRecord = await AccountRecordsDB.accountRecordsDetails
229        .where('accountName')
230        .equals(accountName)
231        .last()
232  
233        if(latestUniqueRecord) {
234          const accountKey = latestUniqueRecord.accountName;
235          await AccountRecordsDB.accountRecordsDetails.update(accountKey,{
236            state: "Escrow",
237            offerAmount: usdAmount,
238            receivedAmount: usdAmount,
239            nativeBalance: receiverUpdatedNativeBalance,
240            usdBalance: receiverUpdatedUsdBalance,
241            timestamp: new Date(),
242          });
243        } else {
244          console.log("No existing records found for the specified account")
245        }
246        console.log(`Succesfully ${accountName} changed state to Purchase`)
247        setUpdatedBalance(updatedBalance);
248      } catch(error) {
249        console.log("Could update to the account records database with receiver records: ", error)
250      }
251    }
252  
253    useEffect(()=> {
254      getCkBtcBalance();
255    },[]);
256  
257    const getCkBtcBalance = async () => {
258      if(ledgerActor && principal ) {
259        const ownerPrincipal = Principal.fromText(principal);
260        console.log("Owner principal: ", ownerPrincipal);
261        try { 
262          const res = await ledgerActor?.balance({
263          owner: ownerPrincipal,
264          certified: false,
265        });
266        console.log("cKBtc Balance: ", res);
267        setCkBtcBalance(res);
268        } catch(error) {
269          console.log("There was an issue fethcing the balance: ", error);
270        }
271      } else {
272        console.log("Make sure ledgerActor and principal are intitialized first");
273      }
274    }
275  
276    const handleTransferCkBtc = () => {
277      console.log("Destination:", selectedDestinationAccount?.sourcePrincipal);
278      console.log("Amount:", amount)
279  
280      if(!ledgerActor) return;
281  
282      let destinationPrincipal: Principal = Principal.fromText('');
283  
284      try {
285        destinationPrincipal = Principal.fromText(selectedDestinationAccount?.sourcePrincipal);
286      } catch(error) {
287        console.log("Invalid destination principal")
288      }
289      
290      const _amount = Number.parseFloat(amount);
291      if(Number.isNaN(_amount)) {
292        console.log("Amount must be a number");
293      }
294      if(_amount < 0) {
295        console.log("Amount must be greater than 0")
296      }
297  
298      let amountBigInt: bigint = BigInt(0);
299  
300      try {
301        amountBigInt = convertToBigInt(_amount)
302      } catch(error) {
303        console.log("Could not convert amount to bigInt")
304      }
305  
306      if(ckBtcBalance && amountBigInt > ckBtcBalance) {
307        console.log("Amount exceeds balance");
308      }
309    
310      try {
311        if(!destinationPrincipal || !amountBigInt) return;
312        const res = ledgerActor.transfer({
313          to: {
314            owner: destinationPrincipal,
315            subaccount: [],
316          },
317          amount: amountBigInt,
318        })
319        if(res) {
320          console.log("Transfer Successful: ", res);
321        } else {
322          console.log("An error occured")
323        }
324      } catch(error) {
325        console.log("There was an issue with completing the transfer: ", error);
326      }
327    }
328  
329    const handleSendButton = async () => {
330      console.log("Selected account currency: ",selectedAccount?.currency);
331      console.log("......Submiting to data base........");
332      await updateAccountRecordsSenderData();
333      console.log("Succesfully added sender data to database")
334      await updateAccountRecordsReceiverData();
335      console.log("Successfully added receiver data to database");
336      if(selectedAccount?.currency === "CKBTC") {
337        handleTransferCkBtc();
338      }
339      handleClose();
340    };
341  
342    return (
343      <Dialog.Root open={open} onClose={handleClose} ref={dialogRef} style={{ alignItems: 'center', justifyContent: 'center' }}>
344        <Dialog.Overlay className={styles.DialogOverlay} />
345        <Dialog.Content className={`${styles.DialogContent} ${styles.centeredContent}`}>
346          <Dialog.Close asChild>
347            <button className={styles.DialogCloseButton} onClick={handleClose}>
348              <Cross2Icon />
349            </button>
350          </Dialog.Close>
351          <Dialog.Title className={styles.DialogTitle}>Send</Dialog.Title>
352          
353          <fieldset>
354            <label className={styles.SendModalLabel}>Destination</label>
355            <select
356              className={styles.SendModalInput}
357              name="destination"
358              id="destination"
359              onChange={handleDestinationChange}
360              value={selectedDestinationAccount?.accountName || ""}
361            >
362              
363              <option value="" disabled>Select destination account</option>
364                {allAccountRecordsData?.filter(accountRecord => accountRecord.accountName !== selectedAccount.accountName).map((accountRecord)=> (
365                  <option key={accountRecord.accountName} value={accountRecord.accountName}>{accountRecord.accountName} {accountRecord.currency}</option>
366                ))}
367            </select>
368            <label className={styles.SendModalLabel}>Amount</label>
369            <input
370              className={styles.SendModalInput}
371              name="amount"
372              type="number"
373              id="amount"
374              onChange={handleAmountChange}
375              value={amount || inputAmount}
376            />
377          </fieldset>
378          
379          <div className={styles.AddressContainer}>
380            <span className={styles.AddressLabel}>Source:</span>
381            <span className={styles.AddressValue}>{principal}</span>
382          </div>
383          {/* <div className={styles.AddressContainer}>
384            <span className={styles.AddressLabel}>Dollar Worth:</span>
385            <span className={styles.AddressValue}>{usdAmount}</span>
386          </div> */}
387          <div className={styles.AddressContainer}>
388            <span className={styles.AddressLabel}>Balance:</span>
389            <span className={styles.AddressValue}>{latestBalance} {selectedAccount?.accountName === "CKBTC" ? ckBtcBalance : selectedAccount?.currency}</span>
390          </div>
391          {/* <div className={styles.AddressContainer}>
392            <span className={styles.AddressLabel}>Balance($):</span>
393            <span className={styles.AddressValue}>{latestBalance !== null ? `${usdBalance} USD` : ''}</span>
394          </div> */}
395          <div className={styles.ButtonContainer}>
396            <Button
397              className={`${styles.Button} ${styles.SendModalCancelButton}`}
398              onClick={handleClose}
399            >
400              Cancel
401            </Button> 
402            <Button
403              className={`${styles.Button} ${styles.SendModalNextButton}`}
404              onClick={handleSendButton}
405            >
406              Send
407            </Button>
408          </div>
409        </Dialog.Content>
410      </Dialog.Root>
411    );
412  };
413  
414  export default SendModal;