diff --git a/package-lock.json b/package-lock.json index a4ebd854..5ad8b07d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", - "@radix-ui/react-tooltip": "^1.2.7", + "@radix-ui/react-tooltip": "^1.2.8", "@reown/appkit": "^1.6.8", "@reown/appkit-adapter-ethers5": "^1.6.8", "@tanstack/react-query": "^4.36.1", @@ -2969,19 +2969,19 @@ } }, "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz", - "integrity": "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.2", + "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", @@ -3003,9 +3003,9 @@ } }, "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", - "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": { @@ -3062,12 +3062,12 @@ } }, "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", - "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.2", + "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", @@ -3107,9 +3107,9 @@ } }, "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", - "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", @@ -3163,9 +3163,9 @@ } }, "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", - "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", diff --git a/package.json b/package.json index 8d197e2d..0916e525 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", - "@radix-ui/react-tooltip": "^1.2.7", + "@radix-ui/react-tooltip": "^1.2.8", "@reown/appkit": "^1.6.8", "@reown/appkit-adapter-ethers5": "^1.6.8", "@tanstack/react-query": "^4.36.1", diff --git a/src/app/i/[id]/page.tsx b/src/app/i/[id]/page.tsx index ea37a11d..e66e99b0 100644 --- a/src/app/i/[id]/page.tsx +++ b/src/app/i/[id]/page.tsx @@ -7,7 +7,7 @@ import { api } from "@/trpc/server"; import { ArrowLeft } from "lucide-react"; import type { Metadata } from "next"; import Link from "next/link"; -import { notFound } from "next/navigation"; +import { notFound, redirect } from "next/navigation"; export const metadata: Metadata = { title: "Invoice Me | EasyInvoice", @@ -22,11 +22,16 @@ export default async function InvoiceMePage({ // TODO solve unauthenticated access // TODO solve not found error like the subscription plan page const invoiceMeLink = await api.invoiceMe.getById.query(params.id); + const currentUser = await api.auth.getSessionInfo.query(); if (!invoiceMeLink) { notFound(); } + if (currentUser.user && currentUser.user.id === invoiceMeLink.user.id) { + redirect("/dashboard"); + } + const invoiceCount = await getInvoiceCount(invoiceMeLink.user.id); return ( @@ -54,6 +59,15 @@ export default async function InvoiceMePage({ clientEmail: invoiceMeLink.user.email ?? "", userId: invoiceMeLink.user.id, }} + currentUser={ + currentUser.user + ? { + id: currentUser.user.id, + name: currentUser.user.name ?? "", + email: currentUser.user.email ?? "", + } + : undefined + } invoiceCount={invoiceCount} /> diff --git a/src/components/batch-payout.tsx b/src/components/batch-payout.tsx index 35f95fc7..817f6a59 100644 --- a/src/components/batch-payout.tsx +++ b/src/components/batch-payout.tsx @@ -49,6 +49,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table/table"; + import { PAYOUT_CURRENCIES, type PayoutCurrency, @@ -56,6 +57,8 @@ import { getPaymentCurrenciesForPayout, } from "@/lib/constants/currencies"; import { handleBatchPayment } from "@/lib/helpers/batch-payment"; + +import { useSwitchNetwork } from "@/lib/hooks/use-switch-network"; import { payoutSchema } from "@/lib/schemas/payment"; import { api } from "@/trpc/react"; import { z } from "zod"; @@ -74,6 +77,7 @@ export type BatchPaymentFormValues = z.infer; export function BatchPayout() { const { mutateAsync: batchPay } = api.payment.batchPay.useMutation(); + const { switchToPaymentNetwork } = useSwitchNetwork(); const [paymentStatus, setPaymentStatus] = useState< "idle" | "processing" | "success" | "error" @@ -211,6 +215,8 @@ export function BatchPayout() { return; } + await switchToPaymentNetwork(data.payouts[0].paymentCurrency); + try { const ethersProvider = new ethers.providers.Web3Provider(walletProvider); diff --git a/src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx b/src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx index b5976c5f..218f71d2 100644 --- a/src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx +++ b/src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx @@ -20,11 +20,14 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; + import { RECURRING_PAYMENT_CURRENCIES, formatCurrencyLabel, } from "@/lib/constants/currencies"; + import { useCreateRecurringPayment } from "@/lib/hooks/use-create-recurring-payment"; +import { useSwitchNetwork } from "@/lib/hooks/use-switch-network"; import { paymentApiSchema } from "@/lib/schemas/payment"; import { RecurrenceFrequency } from "@/server/db/schema"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -51,6 +54,8 @@ type RecurringPaymentFormValues = z.infer; export function CreateRecurringPaymentForm() { const router = useRouter(); + const { switchToPaymentNetwork } = useSwitchNetwork(); + const { address, isConnected } = useAppKitAccount(); const { open } = useAppKit(); const { walletProvider } = useAppKitProvider("eip155"); @@ -91,6 +96,8 @@ export function CreateRecurringPaymentForm() { return; } + await switchToPaymentNetwork(data.invoiceCurrency); + const recurringPaymentCurrency = data.invoiceCurrency; const recurringPaymentBody = { payee: data.payee, diff --git a/src/components/dashboard/invoices-received.tsx b/src/components/dashboard/invoices-received.tsx index 4cc09d7c..a900adb3 100644 --- a/src/components/dashboard/invoices-received.tsx +++ b/src/components/dashboard/invoices-received.tsx @@ -10,18 +10,18 @@ import { TableHeader, TableRow, } from "@/components/ui/table/table"; -import { ID_TO_APPKIT_NETWORK, NETWORK_TO_ID } from "@/lib/constants/chains"; +import { NETWORK_TO_ID } from "@/lib/constants/chains"; import { handleBatchPayment } from "@/lib/helpers/batch-payment"; import { calculateTotalsByCurrency, formatCurrencyTotals, } from "@/lib/helpers/currency"; +import { useSwitchNetwork } from "@/lib/hooks/use-switch-network"; import type { Request } from "@/server/db/schema"; import { api } from "@/trpc/react"; import { useAppKit, useAppKitAccount, - useAppKitNetwork, useAppKitProvider, } from "@reown/appkit/react"; import { ethers } from "ethers"; @@ -73,7 +73,7 @@ export const InvoicesReceived = ({ const { open } = useAppKit(); const { isConnected, address } = useAppKitAccount(); const { walletProvider } = useAppKitProvider("eip155"); - const { chainId, switchNetwork } = useAppKitNetwork(); + const { switchToChainId } = useSwitchNetwork(); const { data: invoices } = api.invoice.getAllIssuedToMe.useQuery(undefined, { initialData: initialReceivedInvoices, @@ -134,27 +134,12 @@ export const InvoicesReceived = ({ return; } - const targetChain = - NETWORK_TO_ID[lastSelectedNetwork as keyof typeof NETWORK_TO_ID]; - - if (targetChain !== chainId) { - const targetAppkitNetwork = - ID_TO_APPKIT_NETWORK[targetChain as keyof typeof ID_TO_APPKIT_NETWORK]; + try { + const targetChainId = + NETWORK_TO_ID[lastSelectedNetwork as keyof typeof NETWORK_TO_ID]; - toast("Switching to network", { - description: `Switching to ${targetAppkitNetwork.name} network`, - }); + await switchToChainId(targetChainId); - try { - switchNetwork(targetAppkitNetwork); - } catch (_) { - toast("Error switching network"); - setIsPayingInvoices(false); - return; - } - } - - try { const ethersProvider = new ethers.providers.Web3Provider( walletProvider as ethers.providers.ExternalProvider, ); diff --git a/src/components/direct-payout.tsx b/src/components/direct-payout.tsx index c3c90362..f0f95cdd 100644 --- a/src/components/direct-payout.tsx +++ b/src/components/direct-payout.tsx @@ -38,12 +38,15 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; + import { PAYOUT_CURRENCIES, type PayoutCurrency, formatCurrencyLabel, getPaymentCurrenciesForPayout, } from "@/lib/constants/currencies"; + +import { useSwitchNetwork } from "@/lib/hooks/use-switch-network"; import { paymentApiSchema } from "@/lib/schemas/payment"; import { api } from "@/trpc/react"; import type { z } from "zod"; @@ -57,6 +60,7 @@ export type DirectPaymentFormValues = z.infer; export function DirectPayment() { const { mutateAsync: pay } = api.payment.pay.useMutation(); + const { switchToPaymentNetwork } = useSwitchNetwork(); const [paymentStatus, setPaymentStatus] = useState< "idle" | "processing" | "success" | "error" @@ -118,6 +122,7 @@ export function DirectPayment() { return; } + await switchToPaymentNetwork(data.paymentCurrency); setPaymentStatus("processing"); try { diff --git a/src/components/invoice-creator.tsx b/src/components/invoice-creator.tsx index 141b729e..44fb227c 100644 --- a/src/components/invoice-creator.tsx +++ b/src/components/invoice-creator.tsx @@ -37,12 +37,19 @@ export function InvoiceCreator({ const router = useRouter(); const isInvoiceMe = !!recipientDetails?.userId; const utils = api.useUtils(); - const { mutate: createInvoice, isLoading } = isInvoiceMe + + const { mutateAsync: createInvoice, isLoading } = isInvoiceMe ? api.invoice.createFromInvoiceMe.useMutation({ - onSuccess: () => { - toast.success("Invoice created successfully", { - description: "You can safely close this page now", - }); + onSuccess: async () => { + if (!currentUser) { + toast.success("Invoice created successfully", { + description: "You can safely close this page now", + }); + return; + } + toast.success("Invoice created successfully"); + await utils.invoice.getAll.invalidate(); + router.push("/dashboard"); }, onError: (error) => { toast.error("Failed to create invoice", { diff --git a/src/components/invoice-form.tsx b/src/components/invoice-form.tsx index c6d8ae93..4385cb18 100644 --- a/src/components/invoice-form.tsx +++ b/src/components/invoice-form.tsx @@ -90,7 +90,7 @@ const checkPaymentDetailsApproval = ( interface InvoiceFormProps { currentUser: User; form: UseFormReturn; - onSubmit: (data: InvoiceFormValues) => void; + onSubmit: (data: InvoiceFormValues) => Promise; isLoading: boolean; recipientDetails?: { clientName: string; @@ -1037,7 +1037,7 @@ export function InvoiceForm({