From c40b6021f9813544af55b780f5a23c3bf67b6da8 Mon Sep 17 00:00:00 2001 From: secondl1ght Date: Thu, 14 Nov 2024 00:52:47 -0700 Subject: [PATCH 1/3] refactor: contacts and chat features --- messages/en.json | 17 + messages/es.json | 17 + package-lock.json | 24 +- src/hooks/message.ts | 2 +- src/stores/contacts.ts | 4 - src/views/contacts/AddContact.tsx | 162 +++---- src/views/contacts/ContactMessageBox.tsx | 133 ------ src/views/contacts/Contacts.tsx | 231 ++++++---- src/views/contacts/Messages.tsx | 120 +++-- src/views/contacts/MoneySection.tsx | 93 ++++ src/views/contacts/PayButton.tsx | 486 ++++++++++++++++++++ src/views/contacts/SendMessageBox.tsx | 145 ++++++ src/views/contacts/boxes/PayMessageBox.tsx | 473 ------------------- src/views/contacts/boxes/SendMessageBox.tsx | 143 ------ src/views/wallet/send/Default.tsx | 23 - 15 files changed, 1053 insertions(+), 1020 deletions(-) delete mode 100644 src/views/contacts/ContactMessageBox.tsx create mode 100644 src/views/contacts/MoneySection.tsx create mode 100644 src/views/contacts/PayButton.tsx create mode 100644 src/views/contacts/SendMessageBox.tsx delete mode 100644 src/views/contacts/boxes/PayMessageBox.tsx delete mode 100644 src/views/contacts/boxes/SendMessageBox.tsx diff --git a/messages/en.json b/messages/en.json index 6a7b2c13..6bfdae16 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1,5 +1,22 @@ { "App": { + "Contacts": { + "add-contact": "Add a contact to start sending them messages or money.", + "address": "Address", + "create": "Create", + "input": "Input the MIBAN Code or Lightning Address from the new contact.", + "loading": "Loading...", + "locked": "Messages Locked", + "max": "Max", + "message": "Message", + "min": "Min", + "new-contact": "New Contact", + "no-pay": "Pay Not Supported", + "no-support": "Messages Not Supported", + "none": "No contacts found.", + "pay": "Pay", + "search": "Search" + }, "Dashboard": { "asset": "An asset in your wallet.", "buy": "Buy", diff --git a/messages/es.json b/messages/es.json index e7c2e3c5..ce75216a 100644 --- a/messages/es.json +++ b/messages/es.json @@ -1,5 +1,22 @@ { "App": { + "Contacts": { + "add-contact": "Add a contact to start sending them messages or money.", + "address": "Address", + "create": "Create", + "input": "Input the MIBAN Code or Lightning Address from the new contact.", + "loading": "Loading...", + "locked": "Messages Locked", + "max": "Max", + "message": "Message", + "min": "Min", + "new-contact": "New Contact", + "no-pay": "Pay Not Supported", + "no-support": "Messages Not Supported", + "none": "No contacts found.", + "pay": "Pay", + "search": "Search" + }, "Dashboard": { "asset": "Un activo en tu billetera.", "buy": "Comprar", diff --git a/package-lock.json b/package-lock.json index ef54578e..c07aa4bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9689,9 +9689,9 @@ "dev": true }, "node_modules/elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", + "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -14609,9 +14609,9 @@ } }, "node_modules/node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, "node_modules/node-fetch": { "version": "2.7.0", @@ -16504,17 +16504,17 @@ "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==" }, "node_modules/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.4.tgz", + "integrity": "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==", "hasInstallScript": true, "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", "node-gyp-build": "^4.2.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/semver": { diff --git a/src/hooks/message.ts b/src/hooks/message.ts index ca2b21d6..9bf73af0 100644 --- a/src/hooks/message.ts +++ b/src/hooks/message.ts @@ -71,7 +71,7 @@ export const useSendMessage = (cbk: () => void) => { if (error) { const messages = handleApolloError(error as ApolloError); toast({ - variant: 'default', + variant: 'destructive', title: 'Unable to send message.', description: messages.join(', '), }); diff --git a/src/stores/contacts.ts b/src/stores/contacts.ts index 9863da95..02bba221 100644 --- a/src/stores/contacts.ts +++ b/src/stores/contacts.ts @@ -33,16 +33,12 @@ export type PaymentOption = { }; type ChatState = { - currentChatBox: string; currentPaymentOption: PaymentOption | undefined; - setCurrentChatBox: (type: string) => void; setCurrentPaymentOption: (option: PaymentOption | undefined) => void; }; export const useChat = create()(set => ({ - currentChatBox: 'message', currentPaymentOption: undefined, - setCurrentChatBox: (type: string) => set({ currentChatBox: type }), setCurrentPaymentOption: (option: PaymentOption | undefined) => set({ currentPaymentOption: option }), })); diff --git a/src/views/contacts/AddContact.tsx b/src/views/contacts/AddContact.tsx index 348b1a58..1258eef0 100644 --- a/src/views/contacts/AddContact.tsx +++ b/src/views/contacts/AddContact.tsx @@ -1,27 +1,10 @@ 'use client'; -import { zodResolver } from '@hookform/resolvers/zod'; import { Loader2 } from 'lucide-react'; -import { Dispatch, FC, SetStateAction } from 'react'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; - -import { Button } from '@/components/ui/button'; -import { - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@/components/ui/form'; +import { useTranslations } from 'next-intl'; +import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'; + +import { Button } from '@/components/ui/button-v2'; import { Input } from '@/components/ui/input'; import { useToast } from '@/components/ui/use-toast'; import { useCreateContactMutation } from '@/graphql/mutations/__generated__/contact.generated'; @@ -29,31 +12,27 @@ import { GetWalletContactsDocument } from '@/graphql/queries/__generated__/conta import { useContactStore } from '@/stores/contacts'; import { handleApolloError } from '@/utils/error'; -const formSchema = z.object({ - money_address: z.string().min(1, { - message: - 'A MIBAN Code or Lightning Address is required to create a new contact.', - }), -}); - export const AddContact: FC<{ walletId: string; - setOpenDialog: Dispatch>; -}> = ({ walletId, setOpenDialog }) => { - const setContact = useContactStore(s => s.setCurrentContact); - + openAdd: boolean; + setOpenAdd: Dispatch>; +}> = ({ walletId, openAdd, setOpenAdd }) => { + const t = useTranslations(); const { toast } = useToast(); - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - money_address: '', - }, - }); + const [address, setAddress] = useState(''); + + useEffect(() => { + if (!openAdd) { + setAddress(''); + } + }, [openAdd]); + + const setContact = useContactStore(s => s.setCurrentContact); const [create, { loading }] = useCreateContactMutation({ onCompleted: data => { - setOpenDialog(false); + setOpenAdd(false); toast({ variant: 'default', @@ -61,15 +40,15 @@ export const AddContact: FC<{ description: 'New contact has been added successfully.', }); - form.reset(); + const { id, money_address } = data.contacts.create; const [user, domain] = data.contacts.create.money_address.split('@'); setContact({ - id: data.contacts.create.id, + id, user, domain, - address: data.contacts.create.money_address, + address: money_address, }); }, onError: err => { @@ -86,60 +65,51 @@ export const AddContact: FC<{ ], }); - const handleSubmit = async (formData: z.infer) => { - create({ - variables: { - input: { - wallet_id: walletId, - money_address: formData.money_address, - }, - }, - }); - }; - return ( - - - New Contact - - Add a contact to start sending them messages or money. - - - -
- - ( - - Address - - - - - - Input the MIBAN Code or Lightning Address from the new - contact. - - - )} - /> - - - - -
+
+

+ {t('App.Contacts.new-contact')} +

+ +

+ {t('App.Contacts.add-contact')} +

+ + + + setAddress(e.target.value)} + disabled={loading} + /> + +

+ {t('App.Contacts.input')} +

+ + +
); }; diff --git a/src/views/contacts/ContactMessageBox.tsx b/src/views/contacts/ContactMessageBox.tsx deleted file mode 100644 index 653d5ffc..00000000 --- a/src/views/contacts/ContactMessageBox.tsx +++ /dev/null @@ -1,133 +0,0 @@ -'use client'; - -import { DollarSign, Loader2, Mail } from 'lucide-react'; -import { useEffect } from 'react'; - -import { Button } from '@/components/ui/button'; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from '@/components/ui/tooltip'; -import { useContactInfo } from '@/hooks/user'; -import { useChat } from '@/stores/contacts'; - -import { PayMessageBox } from './boxes/PayMessageBox'; -import { SendMessageBox } from './boxes/SendMessageBox'; - -export const ContactMessageBox = () => { - const { - contact: { payment_options, encryption_pubkey }, - loading, - } = useContactInfo(); - - const currentChatBox = useChat(s => s.currentChatBox); - const setCurrentChatBox = useChat(s => s.setCurrentChatBox); - - const currentPaymentOption = useChat(s => s.currentPaymentOption); - const setCurrentPaymentOption = useChat(s => s.setCurrentPaymentOption); - - useEffect(() => { - if (loading) return; - if (!payment_options.length) return; - - const { - id, - name, - code, - network, - symbol, - max_sendable, - min_sendable, - decimals, - fixed_fee, - variable_fee_percentage, - } = payment_options[0]; - - setCurrentPaymentOption({ - id, - name, - code, - network, - symbol, - min_sendable: min_sendable ? Number(min_sendable) : null, - max_sendable: max_sendable ? Number(max_sendable) : null, - decimals, - fixed_fee: Number(fixed_fee), - variable_fee_percentage: Number(variable_fee_percentage), - }); - }, [payment_options, loading, setCurrentPaymentOption]); - - if (loading) { - return ( -
- -
- ); - } - - if (!payment_options.length && !encryption_pubkey) { - return ( -
-

- No action available for this contact. -

-
- ); - } - - if (!payment_options.length || !currentPaymentOption) { - return ; - } - - if (!encryption_pubkey) { - return ; - } - - switch (currentChatBox) { - case 'pay': - return ( - - - - - Send Message - - } - /> - ); - - default: - return ( - - - - - Send Money - - } - /> - ); - } -}; diff --git a/src/views/contacts/Contacts.tsx b/src/views/contacts/Contacts.tsx index 9524fa63..95d087de 100644 --- a/src/views/contacts/Contacts.tsx +++ b/src/views/contacts/Contacts.tsx @@ -1,22 +1,15 @@ 'use client'; -import { Plus, User } from 'lucide-react'; +import { Plus, Search } from 'lucide-react'; import { useSearchParams } from 'next/navigation'; import { useTranslations } from 'next-intl'; -import { FC, useEffect, useState } from 'react'; -import { useLocalStorage } from 'usehooks-ts'; +import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react'; +import { useDebounceValue, useLocalStorage, useMediaQuery } from 'usehooks-ts'; -import { Button } from '@/components/ui/button'; -import { Dialog, DialogTrigger } from '@/components/ui/dialog'; -import { - Drawer, - DrawerClose, - DrawerContent, - DrawerFooter, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from '@/components/ui/drawer'; +import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer'; +import { Input } from '@/components/ui/input'; +import { Skeleton } from '@/components/ui/skeleton'; +import { useToast } from '@/components/ui/use-toast'; import { GetWalletContactsQuery, useGetWalletContactsQuery, @@ -24,143 +17,185 @@ import { import { useContactStore } from '@/stores/contacts'; import { cn } from '@/utils/cn'; import { LOCALSTORAGE_KEYS } from '@/utils/constants'; +import { handleApolloError } from '@/utils/error'; import { AddContact } from './AddContact'; import { Messages } from './Messages'; +export type Views = 'contacts' | 'messages'; + type ContactType = GetWalletContactsQuery['wallets']['find_one']['contacts']['find_many'][0]; -const ContactCard: FC<{ contact: ContactType; cbk?: () => void }> = ({ - contact, - cbk, -}) => { +const ContactCard: FC<{ + contact: ContactType; + setView: Dispatch>; +}> = ({ contact, setView }) => { + const isDesktop = useMediaQuery('(min-width: 1024px)', { + initializeWithValue: false, + }); + const currentContact = useContactStore(s => s.currentContact); const setContact = useContactStore(s => s.setCurrentContact); + const { id, money_address } = contact; + const [user, domain] = contact.money_address.split('@'); return ( ); }; export const Contacts = () => { - const t = useTranslations('Index'); + const t = useTranslations(); + const isDesktop = useMediaQuery('(min-width: 1024px)', { + initializeWithValue: false, + }); + + const { toast } = useToast(); const searchParams = useSearchParams(); const addParam = searchParams.has('add'); - const [openDrawer, setOpenDrawer] = useState(false); - const [openDialog, setOpenDialog] = useState(addParam); + const [view, setView] = useState('contacts'); + const [openAdd, setOpenAdd] = useState(addParam); + + const [search, setSearch] = useDebounceValue('', 500); const currentContact = useContactStore(s => s.currentContact); const setContact = useContactStore(s => s.setCurrentContact); const [value] = useLocalStorage(LOCALSTORAGE_KEYS.currentWalletId, ''); - const { data } = useGetWalletContactsQuery({ + const { data, loading } = useGetWalletContactsQuery({ variables: { id: value }, skip: !value, - }); + onCompleted: data => { + if (!!currentContact) return; - useEffect(() => { - if (!!currentContact) return; - if (!data?.wallets.find_one.contacts.find_many.length) return; + const { id, money_address } = data.wallets.find_one.contacts.find_many[0]; - const { id, money_address } = data.wallets.find_one.contacts.find_many[0]; + const [user, domain] = money_address.split('@'); - const [user, domain] = money_address.split('@'); + setContact({ id, user, domain, address: money_address }); + }, + onError: err => { + const messages = handleApolloError(err); - setContact({ id, user, domain, address: money_address }); + toast({ + variant: 'destructive', + title: 'Error getting contacts.', + description: messages.join(', '), + }); + }, }); - const contacts = data?.wallets.find_one.contacts.find_many || []; + const contacts = useMemo(() => { + const contacts = data?.wallets.find_one.contacts.find_many; + + if (!contacts) return []; + if (!search) return contacts; + + return contacts.filter(c => + c.money_address.toLowerCase().includes(search.toLowerCase()) + ); + }, [data?.wallets.find_one.contacts.find_many, search]); return ( -
-
-
-

{t('contacts')}

-
- - - - - - - Contacts - - -
- {contacts.length ? ( - contacts.map(c => ( - setOpenDrawer(p => !p)} - /> - )) - ) : ( -
- Add a contact to start -
- )} -
+
+
+ {isDesktop || view === 'contacts' ? ( +
+
+

{t('Index.contacts')}

+ + + + + + + + + + +
- - - - - - - - - - - - - - -
-
- -
- {contacts.length ? ( - contacts.map(c => ) - ) : ( -
- Add a contact to start +
+ + + { + setSearch(e.target.value); + }} + disabled={loading} + className="w-full pl-10" + />
- )} -
-
- +
+ {loading ? ( + Array.from({ length: 8 }).map((_, i) => ( + + )) + ) : contacts.length ? ( + contacts.map(c => ( + + )) + ) : ( +
+ {t('App.Contacts.none')} +
+ )} +
+
+ ) : null} + + {isDesktop || view === 'messages' ? ( + + ) : null} +
); }; diff --git a/src/views/contacts/Messages.tsx b/src/views/contacts/Messages.tsx index 69552152..0cf057d8 100644 --- a/src/views/contacts/Messages.tsx +++ b/src/views/contacts/Messages.tsx @@ -1,8 +1,17 @@ import { format } from 'date-fns'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { ArrowLeft } from 'lucide-react'; +import { + Dispatch, + FC, + SetStateAction, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useLocalStorage } from 'usehooks-ts'; -import { Badge } from '@/components/ui/badge'; +import { Skeleton } from '@/components/ui/skeleton'; import { useToast } from '@/components/ui/use-toast'; import { useGetWalletContactMessagesQuery } from '@/graphql/queries/__generated__/contacts.generated'; import { useContactStore } from '@/stores/contacts'; @@ -14,7 +23,9 @@ import { CryptoWorkerResponse, } from '@/workers/crypto/types'; -import { ContactMessageBox } from './ContactMessageBox'; +import { Views } from './Contacts'; +import { MoneySection } from './MoneySection'; +import { SendMessageBox } from './SendMessageBox'; type Message = { id: string; @@ -23,7 +34,10 @@ type Message = { created_at: string; }; -export const Messages = () => { +export const Messages: FC<{ + contactsLoading: boolean; + setView: Dispatch>; +}> = ({ contactsLoading, setView }) => { const { toast } = useToast(); const scrollDiv = useRef(null); @@ -120,42 +134,74 @@ export const Messages = () => { }, []); return ( -
- {!!currentContact?.user ? ( - - {currentContact.user} - - ) : null} -
- {messages.map(m => ( +
+ {!contactsLoading && !currentContact ? null : ( + <> + {contactsLoading ? ( + <> +
+ + + + +
+ + + + ) : currentContact ? ( + <> +
+ + +
+ {currentContact.address.slice(0, 2)} +
+ +

{currentContact.user}

+
+ + + + ) : null} +
- {m.message.substring(0, 1) === '{' - ? 'Encrypted message' - : m.message} -

- {format(new Date(m.created_at), 'yyyy.MM.dd - HH:mm')} -

+ {messages.map(m => ( +
+ {m.message.substring(0, 1) === '{' + ? 'Encrypted message' + : m.message} + +

+ {format(new Date(m.created_at), 'yyyy.MM.dd - HH:mm')} +

+
+ ))}
- ))} -
-
- -
+ + + + )}
); }; diff --git a/src/views/contacts/MoneySection.tsx b/src/views/contacts/MoneySection.tsx new file mode 100644 index 00000000..d487778b --- /dev/null +++ b/src/views/contacts/MoneySection.tsx @@ -0,0 +1,93 @@ +'use client'; + +import { ArrowUp } from 'lucide-react'; +import { useTranslations } from 'next-intl'; +import { FC, useEffect } from 'react'; + +import { VaultButton } from '@/components/button/VaultButtonV2'; +import { Button } from '@/components/ui/button-v2'; +import { Skeleton } from '@/components/ui/skeleton'; +import { useContactInfo } from '@/hooks/user'; +import { useChat } from '@/stores/contacts'; +import { useKeyStore } from '@/stores/keys'; + +import { PayButton } from './PayButton'; + +export const MoneySection: FC<{ contactsLoading: boolean }> = ({ + contactsLoading, +}) => { + const t = useTranslations(); + + const keys = useKeyStore(s => s.keys); + + const { + contact: { payment_options }, + loading, + } = useContactInfo(); + + const currentPaymentOption = useChat(s => s.currentPaymentOption); + const setCurrentPaymentOption = useChat(s => s.setCurrentPaymentOption); + + useEffect(() => { + if (loading) return; + if (!payment_options.length) return; + + const { + id, + name, + code, + network, + symbol, + max_sendable, + min_sendable, + decimals, + fixed_fee, + variable_fee_percentage, + } = payment_options[0]; + + setCurrentPaymentOption({ + id, + name, + code, + network, + symbol, + min_sendable: min_sendable ? Number(min_sendable) : null, + max_sendable: max_sendable ? Number(max_sendable) : null, + decimals, + fixed_fee: Number(fixed_fee), + variable_fee_percentage: Number(variable_fee_percentage), + }); + }, [loading, payment_options, setCurrentPaymentOption]); + + if (contactsLoading) + return ( + + ); + + if (!keys) + return ; + + if (loading) + return ( + + ); + + if (!payment_options.length || !currentPaymentOption) + return ( + + ); + + return ; +}; diff --git a/src/views/contacts/PayButton.tsx b/src/views/contacts/PayButton.tsx new file mode 100644 index 00000000..b6bf99c8 --- /dev/null +++ b/src/views/contacts/PayButton.tsx @@ -0,0 +1,486 @@ +'use client'; + +import { ApolloError, useApolloClient } from '@apollo/client'; +import { ArrowUp, ArrowUpDown, Loader2 } from 'lucide-react'; +import { useTranslations } from 'next-intl'; +import { FC, useCallback, useEffect, useRef, useState } from 'react'; + +import { Button } from '@/components/ui/button-v2'; +import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer'; +import { useToast } from '@/components/ui/use-toast'; +import { + BroadcastLiquidTransactionDocument, + BroadcastLiquidTransactionMutation, + BroadcastLiquidTransactionMutationVariables, +} from '@/graphql/mutations/__generated__/broadcastLiquidTransaction.generated'; +import { + PayLightningAddressDocument, + PayLightningAddressMutation, + PayLightningAddressMutationVariables, +} from '@/graphql/mutations/__generated__/pay.generated'; +import { useGetPriceCurrentQuery } from '@/graphql/queries/__generated__/prices.generated'; +import { PaymentOptionCode } from '@/graphql/types'; +import { useSendMessage } from '@/hooks/message'; +import { useContactInfo } from '@/hooks/user'; +import { useWalletInfo } from '@/hooks/wallet'; +import { PaymentOption, useChat, useContactStore } from '@/stores/contacts'; +import { useKeyStore } from '@/stores/keys'; +import { toWithError } from '@/utils/async'; +import { cn } from '@/utils/cn'; +import { handleApolloError } from '@/utils/error'; +import { cryptoToUsd, formatFiat } from '@/utils/fiat'; +import { + numberWithoutPrecision, + numberWithPrecisionAndDecimals, +} from '@/utils/numbers'; +import { + CryptoWorkerMessage, + CryptoWorkerResponse, +} from '@/workers/crypto/types'; + +const formatNumber = (value: number, ticker: PaymentOptionCode) => { + const minimumFractionDigits = ticker === PaymentOptionCode.Usdt ? 2 : 0; + + return value.toLocaleString('en-US', { minimumFractionDigits }); +}; + +export const PayButton: FC<{ + currentPaymentOption: PaymentOption; +}> = ({ currentPaymentOption }) => { + const t = useTranslations(); + const { toast } = useToast(); + + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [satsFirst, setSatsFirst] = useState(false); + const [amountUSDInput, setAmountUSDInput] = useState(''); + const [amountSatsInput, setAmountSatsInput] = useState(''); + + useEffect(() => { + if (currentPaymentOption.name === 'Tether USD') { + setSatsFirst(false); + } + }, [currentPaymentOption.name]); + + const { data: priceData } = useGetPriceCurrentQuery({ + onError: err => { + const messages = handleApolloError(err); + + toast({ + variant: 'destructive', + title: 'Error getting asset prices.', + description: messages.join(', '), + }); + }, + }); + + const latestPrice = priceData?.prices.current.value; + + const workerRef = useRef(); + const client = useApolloClient(); + const keys = useKeyStore(s => s.keys); + + const currentContact = useContactStore(s => s.currentContact); + const setCurrentPaymentOption = useChat(s => s.setCurrentPaymentOption); + + const walletInfo = useWalletInfo(); + const currentAsset = walletInfo.getLiquidAssetByCode( + currentPaymentOption.code === PaymentOptionCode.Lightning + ? PaymentOptionCode.Btc + : currentPaymentOption.code + ); + + const { + protected_encryption_private_key, + contact: { payment_options, encryption_pubkey, money_address }, + loading: contactLoading, + } = useContactInfo(); + + const cbk = useCallback(() => { + setOpen(false); + setLoading(false); + }, []); + + const { sendMessage, loading: sendLoading } = useSendMessage(cbk); + + const isLoading = + loading || contactLoading || walletInfo.loading || sendLoading; + + const startPayment = async () => { + if (!currentContact?.address || !currentAsset) return; + + setLoading(true); + + const [result, error] = await toWithError( + client.mutate< + PayLightningAddressMutation, + PayLightningAddressMutationVariables + >({ + mutation: PayLightningAddressDocument, + variables: { + payInput: { + wallet_id: walletInfo.id, + }, + addressInput: { + address: currentContact.address, + amount: + currentAsset.asset_info.ticker === 'USDT' + ? Number(amountUSDInput) * 100_000_000 + : Number(amountSatsInput), + payment_option: { + code: currentPaymentOption.code, + network: currentPaymentOption.network, + }, + }, + }, + }) + ); + + if (error) { + const messages = handleApolloError(error as ApolloError); + + toast({ + variant: 'destructive', + title: 'Error Sending Money', + description: messages.join(', '), + }); + + setLoading(false); + return; + } + + if ( + !result.data?.pay.money_address || + !walletInfo.protected_mnemonic || + !keys || + !workerRef.current + ) { + setLoading(false); + return; + } + + const message: CryptoWorkerMessage = { + type: 'signPset', + payload: { + wallet_account_id: result.data.pay.money_address.wallet_account.id, + mnemonic: walletInfo.protected_mnemonic, + descriptor: result.data.pay.money_address.wallet_account.descriptor, + pset: result.data.pay.money_address.base_64, + keys, + }, + }; + + workerRef.current.postMessage(message); + }; + + useEffect(() => { + workerRef.current = new Worker( + new URL('../../workers/crypto/crypto.ts', import.meta.url) + ); + + workerRef.current.onmessage = async event => { + const message: CryptoWorkerResponse = event.data; + + switch (message.type) { + case 'signPset': + const [, error] = await toWithError( + client.mutate< + BroadcastLiquidTransactionMutation, + BroadcastLiquidTransactionMutationVariables + >({ + mutation: BroadcastLiquidTransactionDocument, + variables: { + input: { + wallet_account_id: message.payload.wallet_account_id, + signed_pset: message.payload.signedPset, + }, + }, + }) + ); + + if (error) { + const messages = handleApolloError(error as ApolloError); + + toast({ + variant: 'destructive', + title: 'Error Sending Money', + description: messages.join(', '), + }); + + setLoading(false); + return; + } + + if (!currentContact?.id || !keys || !money_address || !currentAsset) { + cbk(); + + toast({ + title: 'Money Sent!', + description: `Money has been sent to this contact.`, + }); + return; + } + + const fiatAmount = cryptoToUsd( + numberWithoutPrecision( + currentAsset.asset_info.ticker === 'USDT' + ? amountUSDInput + : amountSatsInput, + currentAsset.asset_info.precision + ) || '0', + currentAsset.asset_info.precision, + currentAsset.asset_info.ticker, + currentAsset.fiat_info.fiat_to_btc + ); + + const amount = `${formatNumber(Number(currentAsset.asset_info.ticker === 'USDT' ? amountUSDInput : amountSatsInput), currentPaymentOption.code)} ${currentAsset.asset_info.ticker === 'USDT' ? 'USDT' : 'sats'}`; + + toast({ + title: 'Money Sent!', + description: `You sent ${fiatAmount} in ${currentPaymentOption.name}.`, + }); + + sendMessage({ + contact_id: currentContact.id, + protectedPrivateKey: protected_encryption_private_key, + keys, + receiver_pubkey: encryption_pubkey, + receiver_money_address: money_address, + message: `${fiatAmount} (${amount})`, + }); + + break; + } + }; + + workerRef.current.onerror = error => { + console.error('Worker error:', error); + setLoading(false); + }; + + return () => { + if (workerRef.current) workerRef.current.terminate(); + }; + }, [ + client, + toast, + currentContact, + keys, + sendMessage, + encryption_pubkey, + money_address, + protected_encryption_private_key, + currentAsset, + amountUSDInput, + amountSatsInput, + currentPaymentOption, + cbk, + ]); + + return ( + { + if (isLoading) return; + + setOpen(open); + + if (!open) { + setTimeout(() => { + setSatsFirst(false); + setAmountUSDInput(''); + setAmountSatsInput(''); + }, 1000); + } + }} + > + + + + + +
+ {payment_options.map(p => ( + + ))} +
+ +
+ { + if (!latestPrice) return; + + const numberValue = Number(e.target.value); + const decimals = e.target.value.split('.')[1]; + const latestPricePerSat = latestPrice / 100_000_000; + + if (!e.target.value) { + setAmountUSDInput(''); + setAmountSatsInput(''); + return; + } + + if (numberValue < 0) return; + if (satsFirst && numberValue < 1) return; + if (satsFirst && e.target.value.includes('.')) return; + if (!satsFirst && decimals?.length > 2) return; + + if (satsFirst) { + setAmountSatsInput(numberValue.toFixed(0)); + setAmountUSDInput((latestPricePerSat * numberValue).toFixed(2)); + } else { + setAmountUSDInput(e.target.value); + setAmountSatsInput( + (numberValue / latestPricePerSat).toFixed(0) + ); + } + }} + disabled={!latestPrice || isLoading} + className="w-full bg-transparent text-center text-5xl font-medium focus:outline-none" + /> + + +
+ +
+

+ {currentPaymentOption.name !== 'Tether USD' + ? satsFirst + ? formatFiat(Number(amountUSDInput)) + ' USD' + : Number(amountSatsInput).toLocaleString('en-US') + ' sats' + : formatFiat(Number(amountUSDInput)).slice(1) + ' USDT'} +

+ + {currentPaymentOption.name !== 'Tether USD' ? ( + + ) : null} +
+ +
+
+

+ {t('App.Wallet.available')}:{' '} + {currentAsset + ? numberWithPrecisionAndDecimals( + currentAsset.balance || 0, + currentAsset.asset_info.precision || 0, + currentAsset.asset_info.ticker + ) + + (currentAsset.asset_info.ticker === 'USDT' + ? ' USDT' + : ' sats') + : '-'} +

+ +

{t('App.Wallet.fee')}: Unknown

+
+ + {currentPaymentOption.min_sendable || + currentPaymentOption.max_sendable ? ( +
+ {currentPaymentOption.min_sendable ? ( +

+ {t('App.Contacts.min')}:{' '} + {numberWithPrecisionAndDecimals( + currentPaymentOption.min_sendable, + 0 + ) + ' sats'} +

+ ) : null} + + {currentPaymentOption.max_sendable ? ( +

+ {t('App.Contacts.max')}:{' '} + {numberWithPrecisionAndDecimals( + currentPaymentOption.max_sendable, + 0 + ) + ' sats'} +

+ ) : null} +
+ ) : null} +
+ + +
+
+ ); +}; diff --git a/src/views/contacts/SendMessageBox.tsx b/src/views/contacts/SendMessageBox.tsx new file mode 100644 index 00000000..b7e41927 --- /dev/null +++ b/src/views/contacts/SendMessageBox.tsx @@ -0,0 +1,145 @@ +'use client'; + +import { Loader2, SendHorizonal } from 'lucide-react'; +import { useTranslations } from 'next-intl'; +import { FC, useCallback, useMemo, useState } from 'react'; +import { useLocalStorage } from 'usehooks-ts'; + +import { Button } from '@/components/ui/button-v2'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Skeleton } from '@/components/ui/skeleton'; +import { useToast } from '@/components/ui/use-toast'; +import { useGetWalletContactQuery } from '@/graphql/queries/__generated__/contacts.generated'; +import { useSendMessage } from '@/hooks/message'; +import { useContactStore } from '@/stores/contacts'; +import { useKeyStore } from '@/stores/keys'; +import { cn } from '@/utils/cn'; +import { LOCALSTORAGE_KEYS } from '@/utils/constants'; +import { handleApolloError } from '@/utils/error'; + +export const SendMessageBox: FC<{ contactsLoading: boolean }> = ({ + contactsLoading, +}) => { + const t = useTranslations(); + + const [message, setMessage] = useState(''); + + const cbk = useCallback(() => setMessage(''), []); + + const { sendMessage, loading } = useSendMessage(cbk); + + const { toast } = useToast(); + + const keys = useKeyStore(s => s.keys); + + const currentContact = useContactStore(s => s.currentContact); + + const [value] = useLocalStorage(LOCALSTORAGE_KEYS.currentWalletId, ''); + + const { + data, + loading: contactLoading, + error, + } = useGetWalletContactQuery({ + variables: { id: value, contact_id: currentContact?.id || '' }, + skip: !currentContact?.id, + onError: err => { + const messages = handleApolloError(err); + + toast({ + variant: 'destructive', + title: 'Error Getting Contact Info', + description: messages.join(', '), + }); + }, + }); + + const placeholder = useMemo(() => { + switch (true) { + case contactLoading || loading: + return t('App.Contacts.loading'); + case Boolean(error): + return t('Common.error'); + case !data?.wallets.find_one.contacts.find_one.encryption_pubkey: + return t('App.Contacts.no-support'); + case !keys: + return t('App.Contacts.locked'); + default: + return t('App.Contacts.message'); + } + }, [ + loading, + error, + data?.wallets.find_one.contacts.find_one.encryption_pubkey, + keys, + ]); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + if (!currentContact || !data?.wallets.find_one || !keys) return; + + sendMessage({ + contact_id: currentContact.id, + protectedPrivateKey: + data.wallets.find_one.secp256k1_key_pair + .protected_encryption_private_key, + keys, + receiver_pubkey: + data.wallets.find_one.contacts.find_one.encryption_pubkey, + receiver_money_address: + data.wallets.find_one.contacts.find_one.money_address, + message, + }); + }; + + const disabled = + !data?.wallets.find_one.contacts.find_one.encryption_pubkey || + !keys || + loading; + + if (contactsLoading) + return ; + + return ( +
+ + +
+ { + if (!loading) { + setMessage(e.target.value); + } + }} + placeholder={placeholder} + className={cn( + 'pr-[50px]', + loading && 'cursor-not-allowed opacity-50' + )} + disabled={ + !data?.wallets.find_one.contacts.find_one.encryption_pubkey || !keys + } + /> + + +
+
+ ); +}; diff --git a/src/views/contacts/boxes/PayMessageBox.tsx b/src/views/contacts/boxes/PayMessageBox.tsx deleted file mode 100644 index 427cc150..00000000 --- a/src/views/contacts/boxes/PayMessageBox.tsx +++ /dev/null @@ -1,473 +0,0 @@ -'use client'; - -import { ApolloError, useApolloClient } from '@apollo/client'; -import Big from 'big.js'; -import { HandCoins, Loader2 } from 'lucide-react'; -import { - ChangeEvent, - FC, - ReactNode, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; - -import { VaultButton } from '@/components/button/VaultButton'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { useToast } from '@/components/ui/use-toast'; -import { - BroadcastLiquidTransactionDocument, - BroadcastLiquidTransactionMutation, - BroadcastLiquidTransactionMutationVariables, -} from '@/graphql/mutations/__generated__/broadcastLiquidTransaction.generated'; -import { - PayLightningAddressDocument, - PayLightningAddressMutation, - PayLightningAddressMutationVariables, -} from '@/graphql/mutations/__generated__/pay.generated'; -import { PaymentOptionCode } from '@/graphql/types'; -import { useSendMessage } from '@/hooks/message'; -import { useContactInfo } from '@/hooks/user'; -import { useWalletInfo } from '@/hooks/wallet'; -import { PaymentOption, useChat, useContactStore } from '@/stores/contacts'; -import { useKeyStore } from '@/stores/keys'; -import { toWithError } from '@/utils/async'; -import { handleApolloError } from '@/utils/error'; -import { cryptoToUsd } from '@/utils/fiat'; -import { - numberWithoutPrecision, - numberWithPrecision, - numberWithPrecisionAndDecimals, -} from '@/utils/numbers'; -import { - CryptoWorkerMessage, - CryptoWorkerResponse, -} from '@/workers/crypto/types'; - -const formatNumber = (value: number, ticker: PaymentOptionCode) => { - const minimumFractionDigits = ticker === PaymentOptionCode.Usdt ? 2 : 0; - - return value.toLocaleString('en-US', { minimumFractionDigits }); -}; - -export const PayMessageBox: FC<{ - iconOptions?: ReactNode; - currentPaymentOption: PaymentOption; -}> = ({ currentPaymentOption, iconOptions }) => { - const workerRef = useRef(); - - const client = useApolloClient(); - - const { toast } = useToast(); - - const [loading, setLoading] = useState(false); - - const keys = useKeyStore(s => s.keys); - const currentContact = useContactStore(s => s.currentContact); - - const setCurrentPaymentOption = useChat(s => s.setCurrentPaymentOption); - - const walletInfo = useWalletInfo(); - - const [inputValue, setInputValue] = useState(''); - - useEffect(() => setInputValue(''), [currentContact]); - - const { - protected_encryption_private_key, - contact: { payment_options, encryption_pubkey, money_address }, - loading: contactLoading, - } = useContactInfo(); - - const currentAsset = walletInfo.getLiquidAssetByCode( - currentPaymentOption.code - ); - - const cbk = useCallback(() => { - setInputValue(''); - setLoading(false); - }, []); - - const { sendMessage, loading: sendLoading } = useSendMessage(cbk); - - const isLoading = - loading || contactLoading || walletInfo.loading || sendLoading; - - const handleChange = (e: ChangeEvent) => { - const fractionSplit = e.target.value.split('.'); - - const numberValue = - currentPaymentOption.code === PaymentOptionCode.Usdt - ? fractionSplit[1]?.length > 2 - ? Number(e.target.value.slice(0, fractionSplit[0].length + 3)) - : Number(e.target.value) - : Number(e.target.value); - - if (numberValue < 0) return; - - if ( - currentPaymentOption.code !== PaymentOptionCode.Usdt && - !Number.isInteger(numberValue) - ) - return; - - setInputValue(numberValue || ''); - }; - - const totalFee = useMemo(() => { - const { min_sendable, variable_fee_percentage, fixed_fee } = - currentPaymentOption; - - const inputAmount = Math.max(Number(min_sendable || 0), Number(inputValue)); - - const size = new Big(inputAmount); - - const feePercentage = variable_fee_percentage - ? new Big(variable_fee_percentage).div(100) - : new Big(0); - - const variableFee = size.times(feePercentage); - - const fee = fixed_fee ? variableFee.plus(fixed_fee) : variableFee; - - return fee.toNumber(); - }, [inputValue, currentPaymentOption]); - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - - setLoading(true); - - if (!currentContact?.address) { - setLoading(false); - return; - } - - const [result, error] = await toWithError( - client.mutate< - PayLightningAddressMutation, - PayLightningAddressMutationVariables - >({ - mutation: PayLightningAddressDocument, - variables: { - payInput: { - wallet_id: walletInfo.id, - }, - addressInput: { - address: currentContact.address, - amount: Number(inputValue) * 10 ** currentPaymentOption.decimals, - payment_option: { - code: currentPaymentOption.code, - network: currentPaymentOption.network, - }, - }, - }, - }) - ); - - if (error) { - const messages = handleApolloError(error as ApolloError); - - toast({ - variant: 'destructive', - title: 'Error Sending Money', - description: messages.join(', '), - }); - - setLoading(false); - return; - } - - if ( - !result.data?.pay.money_address || - !walletInfo.protected_mnemonic || - !keys - ) { - setLoading(false); - return; - } - - if (!workerRef.current) { - setLoading(false); - return; - } - - const message: CryptoWorkerMessage = { - type: 'signPset', - payload: { - wallet_account_id: result.data.pay.money_address.wallet_account.id, - mnemonic: walletInfo.protected_mnemonic, - descriptor: result.data.pay.money_address.wallet_account.descriptor, - pset: result.data.pay.money_address.base_64, - keys, - }, - }; - - workerRef.current.postMessage(message); - }; - - useEffect(() => { - workerRef.current = new Worker( - new URL('../../../workers/crypto/crypto.ts', import.meta.url) - ); - - workerRef.current.onmessage = async event => { - const message: CryptoWorkerResponse = event.data; - - switch (message.type) { - case 'signPset': - const [, error] = await toWithError( - client.mutate< - BroadcastLiquidTransactionMutation, - BroadcastLiquidTransactionMutationVariables - >({ - mutation: BroadcastLiquidTransactionDocument, - variables: { - input: { - wallet_account_id: message.payload.wallet_account_id, - signed_pset: message.payload.signedPset, - }, - }, - }) - ); - - if (error) { - const messages = handleApolloError(error as ApolloError); - - toast({ - variant: 'destructive', - title: 'Error Sending Money', - description: messages.join(', '), - }); - - setLoading(false); - return; - } - - toast({ - title: 'Money Sent!', - description: `Money has been sent to this contact.`, - }); - - if (!currentContact?.id || !keys || !money_address || !currentAsset) { - cbk(); - return; - } - - const fiatAmount = cryptoToUsd( - numberWithoutPrecision( - inputValue, - currentAsset.asset_info.precision - )?.toString() || '0', - currentAsset.asset_info.precision, - currentAsset.asset_info.ticker, - currentAsset.fiat_info.fiat_to_btc - ); - - const amount = `${formatNumber(Number(inputValue), currentPaymentOption.code)} ${currentAsset.asset_info.ticker}`; - - sendMessage({ - contact_id: currentContact.id, - protectedPrivateKey: protected_encryption_private_key, - keys, - receiver_pubkey: encryption_pubkey, - receiver_money_address: money_address, - message: `${fiatAmount} (${amount})`, - }); - - break; - } - }; - - workerRef.current.onerror = error => { - console.error('Worker error:', error); - setLoading(false); - }; - - return () => { - if (workerRef.current) workerRef.current.terminate(); - }; - }, [ - client, - toast, - currentContact, - keys, - sendMessage, - encryption_pubkey, - money_address, - protected_encryption_private_key, - currentAsset, - inputValue, - currentPaymentOption.code, - cbk, - ]); - - return ( - <> -
-
- {totalFee ? ( - {`Fee: ${numberWithPrecisionAndDecimals(totalFee, 0)} sat${totalFee === 1 ? '' : 's'}`} - ) : null} - {currentPaymentOption?.min_sendable ? ( - {`Min: ${numberWithPrecisionAndDecimals(currentPaymentOption.min_sendable, 0)} sat${currentPaymentOption.min_sendable === 1 ? '' : 's'}`} - ) : null} - {currentPaymentOption?.max_sendable ? ( - {`Max: ${numberWithPrecisionAndDecimals(currentPaymentOption.max_sendable, 0)} sat${currentPaymentOption.max_sendable === 1 ? '' : 's'}`} - ) : null} -
-
- {currentAsset ? ( - <> - - - {inputValue ? ( - - {cryptoToUsd( - numberWithoutPrecision( - inputValue, - currentAsset.asset_info.precision - )?.toString() || '0', - currentAsset.asset_info.precision, - currentAsset.asset_info.ticker, - currentAsset.fiat_info.fiat_to_btc - )} - - ) : null} - - ) : null} -
-
- -
-
- - - - {payment_options.length > 1 ? ( - - ) : null} -
- -
- {iconOptions} - - {!!keys ? ( - - ) : ( - - )} -
-
- - ); -}; diff --git a/src/views/contacts/boxes/SendMessageBox.tsx b/src/views/contacts/boxes/SendMessageBox.tsx deleted file mode 100644 index b36b007c..00000000 --- a/src/views/contacts/boxes/SendMessageBox.tsx +++ /dev/null @@ -1,143 +0,0 @@ -'use client'; - -import { CornerDownLeft, Loader2 } from 'lucide-react'; -import { FC, ReactNode, useCallback, useState } from 'react'; -import { useLocalStorage } from 'usehooks-ts'; - -import { VaultButton } from '@/components/button/VaultButton'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { useToast } from '@/components/ui/use-toast'; -import { useGetWalletContactQuery } from '@/graphql/queries/__generated__/contacts.generated'; -import { useSendMessage } from '@/hooks/message'; -import { useContactStore } from '@/stores/contacts'; -import { useKeyStore } from '@/stores/keys'; -import { LOCALSTORAGE_KEYS } from '@/utils/constants'; -import { handleApolloError } from '@/utils/error'; - -export const SendMessageBox: FC<{ iconOptions?: ReactNode }> = ({ - iconOptions, -}) => { - const [message, setMessage] = useState(''); - - const cbk = useCallback(() => setMessage(''), []); - - const { sendMessage, loading: sendLoading } = useSendMessage(cbk); - - const { toast } = useToast(); - - const keys = useKeyStore(s => s.keys); - - const currentContact = useContactStore(s => s.currentContact); - - const [value] = useLocalStorage(LOCALSTORAGE_KEYS.currentWalletId, ''); - - const { data, loading } = useGetWalletContactQuery({ - variables: { id: value, contact_id: currentContact?.id || '' }, - skip: !currentContact?.id, - onError: err => { - const messages = handleApolloError(err); - - toast({ - variant: 'destructive', - title: 'Error Getting Contact Info', - description: messages.join(', '), - }); - }, - }); - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - - if (sendLoading || !message) return; - - if (!data?.wallets.find_one.contacts.find_one.encryption_pubkey) { - toast({ - variant: 'default', - title: 'Unable to send message', - description: 'No encryption pubkey found for this contact.', - }); - - return; - } - - if (!keys) { - toast({ - variant: 'destructive', - title: 'Vault Locked', - description: 'Unlock your vault to send a message.', - }); - - return; - } - - if (!currentContact?.id) { - toast({ - variant: 'destructive', - title: 'No Contact Selected', - description: 'Select a contact to send them a message.', - }); - - return; - } - - sendMessage({ - contact_id: currentContact.id, - protectedPrivateKey: - data.wallets.find_one.secp256k1_key_pair - .protected_encryption_private_key, - keys, - receiver_pubkey: - data.wallets.find_one.contacts.find_one.encryption_pubkey, - receiver_money_address: - data.wallets.find_one.contacts.find_one.money_address, - message, - }); - }; - - const isLoading = loading || sendLoading; - - return ( -
- - setMessage(e.target.value)} - placeholder="Type your message here..." - className="min-h-12 resize-none border-0 p-3 shadow-none focus-visible:ring-0" - /> -
- {iconOptions} - - {!!keys ? ( - - ) : ( - - )} -
-
- ); -}; diff --git a/src/views/wallet/send/Default.tsx b/src/views/wallet/send/Default.tsx index ac3bda65..115731c5 100644 --- a/src/views/wallet/send/Default.tsx +++ b/src/views/wallet/send/Default.tsx @@ -355,29 +355,6 @@ export const Default: FC<{ setSendString(sendStringFormatted); } - if (type === 'bitcoin') { - const params = sendStringFormatted.split('?'); - - if (params.length > 1) { - const paramsDecoded = new URLSearchParams(params[1]); - const amount = Number(paramsDecoded.get('amount')); - - if (amount > 0) { - const amountInSats = amount * 100_000_000; - setAmountSatsInput(amountInSats.toFixed(0)); - setAmountUSDInput( - (latestPricePerSat * amountInSats).toFixed(2) - ); - } - - setSendString(params[0]); - } else { - setSendString(sendStringFormatted); - } - } else { - setSendString(sendStringFormatted); - } - setSendType(type); setView('confirm'); }} From 980905f0edb280d9c8ecbf3c0ff793c5c86a0073 Mon Sep 17 00:00:00 2001 From: secondl1ght Date: Thu, 14 Nov 2024 10:10:34 -0700 Subject: [PATCH 2/3] feat: show message nots across app --- src/app/(events)/(access)/(layout)/contacts/page.tsx | 5 +++++ .../(access)}/(layout)/dashboard/page.tsx | 0 src/app/{(app) => (events)/(access)}/(layout)/layout.tsx | 0 .../(access)}/(layout)/settings/2fa/page.tsx | 0 .../(access)}/(layout)/settings/appearance/page.tsx | 0 .../(access)}/(layout)/settings/language/page.tsx | 0 .../(access)}/(layout)/settings/page.tsx | 0 .../(access)}/(layout)/settings/passkeys/page.tsx | 0 .../(access)}/(layout)/settings/password/page.tsx | 0 .../{(app) => (events)/(access)}/(layout)/swaps/page.tsx | 0 .../(access)}/(layout)/transactions/[id]/page.tsx | 0 .../(access)}/(layout)/transactions/page.tsx | 0 .../(access)}/(layout)/wallet/receive/page.tsx | 0 .../(access)}/(layout)/wallet/send/page.tsx | 0 .../(access)}/(layout)/wallet/settings/page.tsx | 0 src/app/{(app) => (events)/(access)}/layout.tsx | 0 .../(access)}/setup/wallet/new/page.tsx | 0 .../{(app) => (events)/(access)}/setup/wallet/page.tsx | 0 .../(access)}/setup/wallet/restore/page.tsx | 0 .../(layout)/contacts/page.tsx => (events)/layout.tsx} | 9 ++++++--- 20 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 src/app/(events)/(access)/(layout)/contacts/page.tsx rename src/app/{(app) => (events)/(access)}/(layout)/dashboard/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/layout.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/settings/2fa/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/settings/appearance/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/settings/language/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/settings/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/settings/passkeys/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/settings/password/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/swaps/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/transactions/[id]/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/transactions/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/wallet/receive/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/wallet/send/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/(layout)/wallet/settings/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/layout.tsx (100%) rename src/app/{(app) => (events)/(access)}/setup/wallet/new/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/setup/wallet/page.tsx (100%) rename src/app/{(app) => (events)/(access)}/setup/wallet/restore/page.tsx (100%) rename src/app/{(app)/(layout)/contacts/page.tsx => (events)/layout.tsx} (78%) diff --git a/src/app/(events)/(access)/(layout)/contacts/page.tsx b/src/app/(events)/(access)/(layout)/contacts/page.tsx new file mode 100644 index 00000000..86cea3d2 --- /dev/null +++ b/src/app/(events)/(access)/(layout)/contacts/page.tsx @@ -0,0 +1,5 @@ +import { Contacts } from '@/views/contacts/Contacts'; + +export default function Page() { + return ; +} diff --git a/src/app/(app)/(layout)/dashboard/page.tsx b/src/app/(events)/(access)/(layout)/dashboard/page.tsx similarity index 100% rename from src/app/(app)/(layout)/dashboard/page.tsx rename to src/app/(events)/(access)/(layout)/dashboard/page.tsx diff --git a/src/app/(app)/(layout)/layout.tsx b/src/app/(events)/(access)/(layout)/layout.tsx similarity index 100% rename from src/app/(app)/(layout)/layout.tsx rename to src/app/(events)/(access)/(layout)/layout.tsx diff --git a/src/app/(app)/(layout)/settings/2fa/page.tsx b/src/app/(events)/(access)/(layout)/settings/2fa/page.tsx similarity index 100% rename from src/app/(app)/(layout)/settings/2fa/page.tsx rename to src/app/(events)/(access)/(layout)/settings/2fa/page.tsx diff --git a/src/app/(app)/(layout)/settings/appearance/page.tsx b/src/app/(events)/(access)/(layout)/settings/appearance/page.tsx similarity index 100% rename from src/app/(app)/(layout)/settings/appearance/page.tsx rename to src/app/(events)/(access)/(layout)/settings/appearance/page.tsx diff --git a/src/app/(app)/(layout)/settings/language/page.tsx b/src/app/(events)/(access)/(layout)/settings/language/page.tsx similarity index 100% rename from src/app/(app)/(layout)/settings/language/page.tsx rename to src/app/(events)/(access)/(layout)/settings/language/page.tsx diff --git a/src/app/(app)/(layout)/settings/page.tsx b/src/app/(events)/(access)/(layout)/settings/page.tsx similarity index 100% rename from src/app/(app)/(layout)/settings/page.tsx rename to src/app/(events)/(access)/(layout)/settings/page.tsx diff --git a/src/app/(app)/(layout)/settings/passkeys/page.tsx b/src/app/(events)/(access)/(layout)/settings/passkeys/page.tsx similarity index 100% rename from src/app/(app)/(layout)/settings/passkeys/page.tsx rename to src/app/(events)/(access)/(layout)/settings/passkeys/page.tsx diff --git a/src/app/(app)/(layout)/settings/password/page.tsx b/src/app/(events)/(access)/(layout)/settings/password/page.tsx similarity index 100% rename from src/app/(app)/(layout)/settings/password/page.tsx rename to src/app/(events)/(access)/(layout)/settings/password/page.tsx diff --git a/src/app/(app)/(layout)/swaps/page.tsx b/src/app/(events)/(access)/(layout)/swaps/page.tsx similarity index 100% rename from src/app/(app)/(layout)/swaps/page.tsx rename to src/app/(events)/(access)/(layout)/swaps/page.tsx diff --git a/src/app/(app)/(layout)/transactions/[id]/page.tsx b/src/app/(events)/(access)/(layout)/transactions/[id]/page.tsx similarity index 100% rename from src/app/(app)/(layout)/transactions/[id]/page.tsx rename to src/app/(events)/(access)/(layout)/transactions/[id]/page.tsx diff --git a/src/app/(app)/(layout)/transactions/page.tsx b/src/app/(events)/(access)/(layout)/transactions/page.tsx similarity index 100% rename from src/app/(app)/(layout)/transactions/page.tsx rename to src/app/(events)/(access)/(layout)/transactions/page.tsx diff --git a/src/app/(app)/(layout)/wallet/receive/page.tsx b/src/app/(events)/(access)/(layout)/wallet/receive/page.tsx similarity index 100% rename from src/app/(app)/(layout)/wallet/receive/page.tsx rename to src/app/(events)/(access)/(layout)/wallet/receive/page.tsx diff --git a/src/app/(app)/(layout)/wallet/send/page.tsx b/src/app/(events)/(access)/(layout)/wallet/send/page.tsx similarity index 100% rename from src/app/(app)/(layout)/wallet/send/page.tsx rename to src/app/(events)/(access)/(layout)/wallet/send/page.tsx diff --git a/src/app/(app)/(layout)/wallet/settings/page.tsx b/src/app/(events)/(access)/(layout)/wallet/settings/page.tsx similarity index 100% rename from src/app/(app)/(layout)/wallet/settings/page.tsx rename to src/app/(events)/(access)/(layout)/wallet/settings/page.tsx diff --git a/src/app/(app)/layout.tsx b/src/app/(events)/(access)/layout.tsx similarity index 100% rename from src/app/(app)/layout.tsx rename to src/app/(events)/(access)/layout.tsx diff --git a/src/app/(app)/setup/wallet/new/page.tsx b/src/app/(events)/(access)/setup/wallet/new/page.tsx similarity index 100% rename from src/app/(app)/setup/wallet/new/page.tsx rename to src/app/(events)/(access)/setup/wallet/new/page.tsx diff --git a/src/app/(app)/setup/wallet/page.tsx b/src/app/(events)/(access)/setup/wallet/page.tsx similarity index 100% rename from src/app/(app)/setup/wallet/page.tsx rename to src/app/(events)/(access)/setup/wallet/page.tsx diff --git a/src/app/(app)/setup/wallet/restore/page.tsx b/src/app/(events)/(access)/setup/wallet/restore/page.tsx similarity index 100% rename from src/app/(app)/setup/wallet/restore/page.tsx rename to src/app/(events)/(access)/setup/wallet/restore/page.tsx diff --git a/src/app/(app)/(layout)/contacts/page.tsx b/src/app/(events)/layout.tsx similarity index 78% rename from src/app/(app)/(layout)/contacts/page.tsx rename to src/app/(events)/layout.tsx index 7644a427..1d1c4ff5 100644 --- a/src/app/(app)/(layout)/contacts/page.tsx +++ b/src/app/(events)/layout.tsx @@ -1,9 +1,12 @@ import { cookies } from 'next/headers'; import { EventHandler } from '@/components/events/Events'; -import { Contacts } from '@/views/contacts/Contacts'; -export default function Page() { +export default function Layout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { const eventsUrl = process.env.EVENTS_URL; const cookieStore = cookies(); @@ -14,7 +17,7 @@ export default function Page() { {accessToken && eventsUrl ? ( ) : null} - + {children} ); } From 34e16b4e7d4cae5349b4046438467ab2071878b8 Mon Sep 17 00:00:00 2001 From: Anthony Potdevin <31413433+apotdevin@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:23:57 -0600 Subject: [PATCH 3/3] Update messages/es.json --- messages/es.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/messages/es.json b/messages/es.json index ce75216a..ebb0e0ca 100644 --- a/messages/es.json +++ b/messages/es.json @@ -1,21 +1,21 @@ { "App": { "Contacts": { - "add-contact": "Add a contact to start sending them messages or money.", - "address": "Address", - "create": "Create", - "input": "Input the MIBAN Code or Lightning Address from the new contact.", - "loading": "Loading...", - "locked": "Messages Locked", + "add-contact": "Agrega un contacto para empezar a enviarle mensajes o dinero.", + "address": "Dirección", + "create": "Crear", + "input": "Introduce el código MIBAN o la dirección Lightning del nuevo contacto.", + "loading": "Cargando...", + "locked": "Mensajes bloqueados", "max": "Max", - "message": "Message", + "message": "Mensaje", "min": "Min", - "new-contact": "New Contact", - "no-pay": "Pay Not Supported", - "no-support": "Messages Not Supported", - "none": "No contacts found.", - "pay": "Pay", - "search": "Search" + "new-contact": "Nuevo contacto", + "no-pay": "Pago no soportado", + "no-support": "Mensajes no soportados", + "none": "No se encontraron contactos.", + "pay": "Pagar", + "search": "Buscar" }, "Dashboard": { "asset": "Un activo en tu billetera.",