diff --git a/apps/pay/app/[username]/layout.tsx b/apps/pay/app/[username]/layout.tsx new file mode 100644 index 0000000000..aabc381686 --- /dev/null +++ b/apps/pay/app/[username]/layout.tsx @@ -0,0 +1,19 @@ +import React from "react" + +import UsernameLayoutContainer from "@/components/Layouts/UsernameLayout" + +export default function UsernameLayout({ + children, + params, +}: { + children: React.ReactNode + params: { + username: string + } +}) { + return ( + + {children} + + ) +} diff --git a/apps/pay/app/[username]/page.tsx b/apps/pay/app/[username]/page.tsx new file mode 100644 index 0000000000..afe6bbd62d --- /dev/null +++ b/apps/pay/app/[username]/page.tsx @@ -0,0 +1,172 @@ +"use client" +import Link from "next/link" +import React from "react" +import Container from "react-bootstrap/Container" +import Image from "react-bootstrap/Image" + +import Head from "next/head" + +import { gql } from "@apollo/client" + +import { useSearchParams } from "next/navigation" + +import ParsePayment from "../../components/ParsePOSPayment" +import PinToHomescreen from "../../components/PinToHomescreen" + +import CurrencyDropdown from "../../components/Currency/currency-dropdown" + +import { useAccountDefaultWalletsQuery } from "../../lib/graphql/generated" + +import reducer, { ACTIONS } from "../reducer" + +import styles from "./username.module.css" + +import LoadingComponent from "@/components/Loading" +import { extractSearchParams } from "@/utils/utils" + +gql` + query accountDefaultWallets($username: Username!) { + accountDefaultWallet(username: $username) { + __typename + id + walletCurrency + } + } +` + +type Props = { + params: { + username: string + } +} + +function updateCurrencyAndReload(newDisplayCurrency: string): void { + localStorage.setItem("display", newDisplayCurrency) + + const currentURL = new URL(window.location.toString()) + const searchParams = new URLSearchParams(window.location.search) + searchParams.set("display", newDisplayCurrency) + currentURL.search = searchParams.toString() + + window.history.pushState({}, "", currentURL.toString()) + setTimeout(() => { + window.location.reload() + }, 100) +} + +function ReceivePayment({ params }: Props) { + const searchParams = useSearchParams() + const { memo } = extractSearchParams(searchParams) + + const { username } = params + + let accountUsername: string + if (!username) { + accountUsername = "" + } else { + accountUsername = username.toString() + } + + const manifestParams = new URLSearchParams() + if (memo) { + manifestParams.set("memo", memo.toString()) + } + + const { + data, + error: usernameError, + loading: usernameLoading, + } = useAccountDefaultWalletsQuery({ + variables: { username: accountUsername }, + skip: !accountUsername, + }) + + const [state, dispatch] = React.useReducer(reducer, { + currentAmount: "", + createdInvoice: false, + walletCurrency: data?.accountDefaultWallet.walletCurrency, + username: accountUsername, + pinnedToHomeScreenModalVisible: false, + }) + + React.useEffect(() => { + if (state.walletCurrency === data?.accountDefaultWallet.walletCurrency) { + return + } + dispatch({ + type: ACTIONS.UPDATE_WALLET_CURRENCY, + payload: data?.accountDefaultWallet.walletCurrency, + }) + dispatch({ type: ACTIONS.UPDATE_USERNAME, payload: username }) + }, [state, username, data]) + + return username ? ( + + + + + {usernameError ? ( +
+

{`${usernameError.message}.`}

+

Please check the username in your browser URL and try again.

+ localStorage.removeItem("username")}> + Back + +
+ ) : ( + <> + +
+ {state.createdInvoice && ( + + )} +

{`Pay ${username}`}

+
+ +
+
+ {data && !usernameLoading && accountUsername && state ? ( + + ) : ( + + )} + + )} +
+ ) : null +} + +export default ReceivePayment diff --git a/apps/pay/pages/[username]/print.tsx b/apps/pay/app/[username]/print/page.tsx similarity index 96% rename from apps/pay/pages/[username]/print.tsx rename to apps/pay/app/[username]/print/page.tsx index cfb742a49f..265a6a849d 100644 --- a/apps/pay/pages/[username]/print.tsx +++ b/apps/pay/app/[username]/print/page.tsx @@ -1,18 +1,22 @@ -import Row from "react-bootstrap/Row" +"use client" import Col from "react-bootstrap/Col" +import Row from "react-bootstrap/Row" import Card from "react-bootstrap/Card" import Container from "react-bootstrap/Container" import ReactToPrint from "react-to-print" import { bech32 } from "bech32" import { QRCode } from "react-qrcode-logo" import { useRef } from "react" -import { useRouter } from "next/router" -export default function Print() { +export default function Print({ + params, +}: { + params: { + username: string + } +}) { + const { username } = params const componentRef = useRef(null) - - const router = useRouter() - const username = router.query.username as string const url = new URL(window.location.href) const unencodedLnurl = `${url.protocol}//${url.host}/.well-known/lnurlp/${username}` const lnurl = bech32.encode( diff --git a/apps/pay/pages/_user.module.css b/apps/pay/app/[username]/username.module.css similarity index 100% rename from apps/pay/pages/_user.module.css rename to apps/pay/app/[username]/username.module.css diff --git a/apps/pay/pages/index.css b/apps/pay/app/globals.css similarity index 87% rename from apps/pay/pages/index.css rename to apps/pay/app/globals.css index 56826b7c71..3a62b27ad1 100644 --- a/apps/pay/pages/index.css +++ b/apps/pay/app/globals.css @@ -1,17 +1,13 @@ -@import url("https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@200;300;400;600;700&display=swap"); +@tailwind base; +@tailwind components; +@tailwind utilities; body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", - "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; -} - .error { color: red; margin: 20px auto; diff --git a/apps/pay/pages/_app.tsx b/apps/pay/app/layout.tsx similarity index 52% rename from apps/pay/pages/_app.tsx rename to apps/pay/app/layout.tsx index 51564264b1..4c8cf83a53 100644 --- a/apps/pay/pages/_app.tsx +++ b/apps/pay/app/layout.tsx @@ -1,29 +1,28 @@ -// Disable no-unassigned-import rule because we are importing css files -// eslint-disable-next-line -import "bootstrap/dist/css/bootstrap.min.css" -// eslint-disable-next-line -import "./index.css" -import Script from "next/script" -import { NextPage } from "next" -import dynamic from "next/dynamic" +import type { Metadata } from "next" +import { Inter_Tight } from "next/font/google" + +// eslint-disable-next-line import/no-unassigned-import +import "./globals.css" + +// eslint-disable-next-line import/no-unassigned-import +import "bootstrap/dist/css/bootstrap.css" + import Head from "next/head" -import { useRouter } from "next/router" +import Script from "next/script" + +import { ApolloWrapper } from "@/components/apollo-wrapper" +import { APP_DESCRIPTION } from "@/config/config" -import AppLayout from "../components/Layouts/AppLayout" -import { APP_DESCRIPTION } from "../config/config" +const inter = Inter_Tight({ subsets: ["latin"] }) -const GraphQLProvider = dynamic(() => import("../lib/graphql"), { ssr: false }) +export const metadata: Metadata = { + title: "Blink Cash Register", + description: "Blink official lightning network node", +} -export default function Layout({ - Component, - pageProps, -}: { - Component: NextPage - pageProps: Record -}) { - const { username } = useRouter().query +export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - <> + @@ -44,11 +43,9 @@ export default function Layout({ gtag('config', 'UA-181044262-1'); `} - - - - - - + + {children} + + ) } diff --git a/apps/pay/pages/index.tsx b/apps/pay/app/page.tsx similarity index 78% rename from apps/pay/pages/index.tsx rename to apps/pay/app/page.tsx index 72aa16c44a..c93726f14f 100644 --- a/apps/pay/pages/index.tsx +++ b/apps/pay/app/page.tsx @@ -1,4 +1,5 @@ -import React from "react" +"use client" +import React, { Suspense, useEffect } from "react" import Card from "react-bootstrap/Card" import Col from "react-bootstrap/Col" import Container from "react-bootstrap/Container" @@ -7,7 +8,7 @@ import ListGroup from "react-bootstrap/ListGroup" import Row from "react-bootstrap/Row" import { gql, useQuery } from "@apollo/client" -import { useRouter } from "next/router" +import { useRouter } from "next/navigation" import CurrencyDropdown from "../components/Currency/currency-dropdown" import { getClientSideGqlConfig } from "../config/config" @@ -25,24 +26,22 @@ function Home() { ? `https://mempool.space/signet/lightning/node/` : `https://mempool.space/lightning/node/` const { loading, error, data } = useQuery(GET_NODE_STATS) - const [selectedDisplayCurrency, setSelectedDisplayCurrency] = React.useState( - localStorage.getItem("display") ?? "USD", - ) + const [selectedDisplayCurrency, setSelectedDisplayCurrency] = + React.useState("USD") + + useEffect(() => { + const displayCurrency = localStorage.getItem("display") + if (displayCurrency) { + setSelectedDisplayCurrency(displayCurrency) + } + }, []) const router = useRouter() const [username, setUsername] = React.useState("") const handleSubmit = (event: React.FormEvent) => { event.preventDefault() - - router.push( - { - pathname: username, - query: { display: selectedDisplayCurrency }, - }, - undefined, - { shallow: true }, - ) + router.push(`${username}?display=${selectedDisplayCurrency}`, { scroll: true }) } return ( @@ -109,16 +108,18 @@ function Home() { - { - if (newDisplayCurrency) { - localStorage.setItem("display", newDisplayCurrency) - setSelectedDisplayCurrency(newDisplayCurrency) - } - }} - /> + + { + if (newDisplayCurrency) { + localStorage.setItem("display", newDisplayCurrency) + setSelectedDisplayCurrency(newDisplayCurrency) + } + }} + /> + diff --git a/apps/pay/pages/_reducer.tsx b/apps/pay/app/reducer.tsx similarity index 100% rename from apps/pay/pages/_reducer.tsx rename to apps/pay/app/reducer.tsx diff --git a/apps/pay/app/setuppwa/page.tsx b/apps/pay/app/setuppwa/page.tsx new file mode 100644 index 0000000000..d321e4a9d2 --- /dev/null +++ b/apps/pay/app/setuppwa/page.tsx @@ -0,0 +1,89 @@ +"use client" +import { useRouter } from "next/navigation" +import React, { Suspense, useEffect, useState } from "react" + +import CurrencyDropdown from "../../components/Currency/currency-dropdown" + +const SetupPwa = () => { + const router = useRouter() + const [username, setUsername] = useState("") + const [usernameFromLocal, setUsernameFromLocal] = useState(null) + const [displayCurrencyFromLocal, setDisplayCurrencyFromLocal] = useState( + null, + ) + + useEffect(() => { + const localUsername = localStorage.getItem("username") + const localDisplayCurrency = localStorage.getItem("display") + setUsernameFromLocal(localUsername) + setDisplayCurrencyFromLocal(localDisplayCurrency) + }, []) + + const [selectedDisplayCurrency, setSelectedDisplayCurrency] = useState("USD") + + useEffect(() => { + if (usernameFromLocal && displayCurrencyFromLocal) { + router.push(`${usernameFromLocal}?display=${displayCurrencyFromLocal}`) + } + }, [displayCurrencyFromLocal, usernameFromLocal]) + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault() + + if (!usernameFromLocal) { + localStorage.setItem("username", username) + setUsernameFromLocal(username) + } + + if (!displayCurrencyFromLocal) { + localStorage.setItem("display", selectedDisplayCurrency) + setDisplayCurrencyFromLocal(selectedDisplayCurrency) + } + + router.push(`${username}?display=${selectedDisplayCurrency}`) + } + + if (!usernameFromLocal) { + return ( +
+
+

Welcome to Blink POS application.

+ + ) => + setUsername(event.target.value) + } + placeholder="username" + required + /> + + + { + setSelectedDisplayCurrency(newDisplayCurrency) + }} + /> + + +
+
+ ) + } + return ( +
+
+
+ ) +} + +export default SetupPwa diff --git a/apps/pay/components/Currency/currency-dropdown.tsx b/apps/pay/components/Currency/currency-dropdown.tsx index edaaa6056d..8e321033dc 100644 --- a/apps/pay/components/Currency/currency-dropdown.tsx +++ b/apps/pay/components/Currency/currency-dropdown.tsx @@ -1,6 +1,7 @@ +"use client" import React, { useEffect } from "react" -import { useRouter } from "next/router" +import { useSearchParams } from "next/navigation" import { useCurrencyListQuery } from "../../lib/graphql/generated" @@ -15,19 +16,20 @@ export default function CurrencyDropdown({ style?: React.CSSProperties showOnlyFlag?: boolean }) { - const router = useRouter() + const searchParams = useSearchParams() + const display = searchParams?.get("display") + const { data: currencyData } = useCurrencyListQuery() - const [selectedDisplayCurrency, setSelectedDisplayCurrency] = React.useState( - router.query.display && typeof router.query.display === "string" - ? router.query.display - : localStorage.getItem("display") ?? "USD", - ) + const [selectedDisplayCurrency, setSelectedDisplayCurrency] = React.useState("USD") const [isDropDownOpen, setIsDropDownOpen] = React.useState(false) useEffect(() => { - if (router.query?.display && typeof router.query.display === "string") { - setSelectedDisplayCurrency(router.query.display) - } + const newDisplay = + display && typeof display === "string" + ? display + : localStorage.getItem("display") ?? "USD" + setSelectedDisplayCurrency(newDisplay) + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) diff --git a/apps/pay/components/Layouts/AppLayout.tsx b/apps/pay/components/Layouts/AppLayout.tsx deleted file mode 100644 index 50191d023d..0000000000 --- a/apps/pay/components/Layouts/AppLayout.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import copy from "copy-to-clipboard" -import Link from "next/link" -import { useRouter } from "next/router" -import React from "react" -import { Image, OverlayTrigger, Tooltip } from "react-bootstrap" - -import { getClientSidePayDomain } from "../../config/config" - -import styles from "./app-layout.module.css" - -type Props = { - children: React.ReactPortal | React.ReactNode - username: string | string[] | undefined -} - -const AppLayout = ({ children, username }: Props) => { - const router = useRouter() - const { memo } = useRouter().query - const [openSideBar, setOpenSideBar] = React.useState(false) - const [copied, setCopied] = React.useState(false) - const lightningAddr = username - ? `${username?.toString().toLowerCase()}@${getClientSidePayDomain()}` - : "" - - const cashRegisterLink = username ? `/${username}` : "#" - const payCodeLink = username ? `/${username}/print?memo=${memo}` : "#" - - const copyToClipboard = () => { - copy(lightningAddr) - setCopied(true) - setTimeout(() => { - setCopied(false) - }, 2000) - } - - const closeSideBar = () => { - setOpenSideBar(false) - } - - const navigateHome = () => { - let pathname = "/" - if (username) pathname = `/${username}` - router.push( - { - pathname, - }, - undefined, - { shallow: true }, - ) - setTimeout(() => { - router.reload() // Force a reload after a short delay to allow location href to update - }, 200) - } - - return ( -
- -
-
- {children} - -
-
- ) -} - -export default AppLayout diff --git a/apps/pay/components/Layouts/UsernameLayout.tsx b/apps/pay/components/Layouts/UsernameLayout.tsx new file mode 100644 index 0000000000..2e36bdba16 --- /dev/null +++ b/apps/pay/components/Layouts/UsernameLayout.tsx @@ -0,0 +1,190 @@ +"use client" + +import Link from "next/link" +import copy from "copy-to-clipboard" +import { useRouter, useSearchParams } from "next/navigation" +import React, { useEffect, useState } from "react" +import { Image, OverlayTrigger, Tooltip } from "react-bootstrap" + +import { getClientSidePayDomain } from "../../config/config" + +import styles from "./app-layout.module.css" + +type Props = { + children: React.ReactPortal | React.ReactNode + username: string +} + +const UsernameLayoutContainer = ({ children, username }: Props) => { + const router = useRouter() + const searchParams = useSearchParams() + const memo = searchParams.get("memo") + const [openSideBar, setOpenSideBar] = React.useState(false) + const [copied, setCopied] = React.useState(false) + const [isClient, setIsClient] = useState(false) + + useEffect(() => { + setIsClient(true) + }, []) + + const lightningAddr = username + ? `${username?.toString().toLowerCase()}@${getClientSidePayDomain()}` + : "" + + const cashRegisterLink = username ? `/${username}` : "#" + const payCodeLink = username ? `/${username}/print?memo=${memo}` : "#" + const copyToClipboard = () => { + copy(lightningAddr) + setCopied(true) + setTimeout(() => { + setCopied(false) + }, 2000) + } + + const closeSideBar = () => { + setOpenSideBar(false) + } + + const navigateHome = () => { + let pathname = "/" + if (username) pathname = `/${username}` + router.push(pathname) + setTimeout(() => { + window.location.reload() + }, 200) + } + + return ( +
+ +
+
+ {children} + +
+
+ ) +} + +export default UsernameLayoutContainer diff --git a/apps/pay/components/Loading/index.tsx b/apps/pay/components/Loading/index.tsx new file mode 100644 index 0000000000..0254085261 --- /dev/null +++ b/apps/pay/components/Loading/index.tsx @@ -0,0 +1,13 @@ +import React from "react" + +import styles from "./loading-component.module.css" + +const LoadingComponent = () => { + return ( +
+
+
+ ) +} + +export default LoadingComponent diff --git a/apps/pay/components/Loading/loading-component.module.css b/apps/pay/components/Loading/loading-component.module.css new file mode 100644 index 0000000000..8b883c50f8 --- /dev/null +++ b/apps/pay/components/Loading/loading-component.module.css @@ -0,0 +1,36 @@ +.loading { + display: flex; + justify-content: center; + align-items: center; + font-size: 1.2rem; + font-weight: 600; + color: green; + padding: 30% 1rem; + margin-top: 2rem; + outline: 1px dashed rgba(0, 128, 0, 0.35); +} + +@media (min-width: 760px) { + .loading { + padding: 1rem 0; + outline: none; + } +} + +.loader { + border: 8px solid #f4570e; + border-top: 5px solid rgb(242, 170, 83); + border-radius: 50%; + width: 100px; + height: 100px; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/apps/pay/components/Memo/index.tsx b/apps/pay/components/Memo/index.tsx index 8c9bd4379b..78279368e2 100644 --- a/apps/pay/components/Memo/index.tsx +++ b/apps/pay/components/Memo/index.tsx @@ -1,8 +1,9 @@ -import { useRouter } from "next/router" import React from "react" import { Modal } from "react-bootstrap" -import { ACTIONS, ACTION_TYPE } from "../../pages/_reducer" +import { useSearchParams } from "next/navigation" + +import { ACTIONS, ACTION_TYPE } from "../../app/reducer" import styles from "./memo.module.css" @@ -12,36 +13,38 @@ interface Props { } const Memo = ({ state, dispatch }: Props) => { - const router = useRouter() - const { username, amount, sats, unit, memo, display } = router.query + const searchParams = useSearchParams() + const amount = searchParams.get("amount") || "0" + const sats = searchParams.get("sats") || "0" + const display = searchParams.get("display") || "USD" + const memo = searchParams.get("memo") || "" + + const unit = searchParams.get("unit") const [openModal, setOpenModal] = React.useState(false) - const [note, setNote] = React.useState(memo?.toString() || "") + const [currentMemo, setCurrentMemo] = React.useState(memo?.toString() || "") const handleSetMemo = () => { if (unit === "SAT" || unit === "CENT") { - router.push( - { - pathname: `${username}`, - query: { - amount: amount, - sats: sats, - unit: unit, - memo: note, - display, - }, - }, - undefined, - { shallow: true }, - ) + const params = new URLSearchParams({ + amount, + sats, + unit, + memo: currentMemo, + display, + }) + + const currentUrl = new URL(window.location.toString()) + currentUrl.search = params.toString() + window.history.pushState({}, "", currentUrl.toString()) } else { - router.push( - { - pathname: `${username}`, - query: { memo: note, display }, - }, - undefined, - { shallow: true }, - ) + const params = new URLSearchParams({ + memo: currentMemo, + display, + }) + + const currentUrl = new URL(window.location.toString()) + currentUrl.search = params.toString() + window.history.pushState({}, "", currentUrl.toString()) } handleClose() } @@ -116,10 +119,10 @@ const Memo = ({ state, dispatch }: Props) => { ) => { - setNote(e.target.value) + setCurrentMemo(e.target.value) dispatch({ type: ACTIONS.ADD_MEMO, payload: e.target.value }) }} type="text" diff --git a/apps/pay/components/ParsePOSPayment/Digit-Button.tsx b/apps/pay/components/ParsePOSPayment/Digit-Button.tsx index 32d91548bc..4ffbb2bb26 100644 --- a/apps/pay/components/ParsePOSPayment/Digit-Button.tsx +++ b/apps/pay/components/ParsePOSPayment/Digit-Button.tsx @@ -1,6 +1,6 @@ import React from "react" -import { ACTIONS, ACTION_TYPE } from "../../pages/_reducer" +import { ACTIONS, ACTION_TYPE } from "../../app/reducer" interface Props { digit: string diff --git a/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx b/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx index 8ba2f355dd..a1aad52146 100644 --- a/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx +++ b/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ // TODO: remove eslint-disable, the logic likely needs to be reworked import copy from "copy-to-clipboard" -import { useRouter } from "next/router" +import { useParams, useSearchParams } from "next/navigation" import React, { useCallback } from "react" import Image from "react-bootstrap/Image" import OverlayTrigger from "react-bootstrap/OverlayTrigger" @@ -13,11 +13,13 @@ import { USD_INVOICE_EXPIRE_INTERVAL, getClientSidePayDomain } from "../../confi import useCreateInvoice from "../../hooks/use-Create-Invoice" import { LnInvoiceObject } from "../../lib/graphql/index.types.d" import useSatPrice from "../../lib/use-sat-price" -import { ACTION_TYPE } from "../../pages/_reducer" +import { ACTION_TYPE } from "../../app/reducer" import PaymentOutcome from "../PaymentOutcome" import { Share } from "../Share" -import { safeAmount } from "../../utils/utils" +import { extractSearchParams, safeAmount } from "../../utils/utils" + +import LoadingComponent from "../Loading" import styles from "./parse-payment.module.css" @@ -32,9 +34,11 @@ const USD_MAX_INVOICE_TIME = 5 // minutes const PROGRESS_BAR_MAX_WIDTH = 100 // percent function ReceiveInvoice({ recipientWalletCurrency, walletId, state, dispatch }: Props) { - const deviceDetails = window.navigator.userAgent - const router = useRouter() - const { username, amount, unit, sats, memo } = router.query + const deviceDetails = window.navigator?.userAgent + const searchParams = useSearchParams() + const { username } = useParams() + const query = extractSearchParams(searchParams) + const { amount, unit, sats, memo } = query const { usdToSats, satsToUsd } = useSatPrice() @@ -109,11 +113,11 @@ function ReceiveInvoice({ recipientWalletCurrency, walletId, state, dispatch }: ) const paymentAmount = React.useMemo(() => { - if (!router.query.sats || typeof router.query.sats !== "string") { + if (!query.sats || typeof query.sats !== "string") { alert("No sats amount provided") return } - let amt = safeAmount(router.query.sats) + let amt = safeAmount(query.sats) if (recipientWalletCurrency === "USD") { const usdAmount = satsToUsd(Number(amt)) if (isNaN(usdAmount)) return @@ -137,11 +141,11 @@ function ReceiveInvoice({ recipientWalletCurrency, walletId, state, dispatch }: let amt = paymentAmount if (recipientWalletCurrency === "USD") { - if (!router.query.sats || typeof router.query.sats !== "string") { + if (!query.sats || typeof query.sats !== "string") { alert("No sats amount provided") return } else { - const usdAmount = satsToUsd(Number(router.query.sats)) + const usdAmount = satsToUsd(Number(query.sats)) if (isNaN(usdAmount)) return const cents = parseFloat(usdAmount.toFixed(2)) * 100 amt = cents.toFixed() @@ -175,7 +179,7 @@ function ReceiveInvoice({ recipientWalletCurrency, walletId, state, dispatch }: const isMobileDevice = useCallback(() => { const mobileDevice = /android|iPhone|iPod|kindle|HMSCore|windows phone|ipad/i - if (window.navigator.maxTouchPoints > 1 || mobileDevice.test(deviceDetails)) { + if (window.navigator?.maxTouchPoints > 1 || mobileDevice.test(deviceDetails)) { return true } return false @@ -235,11 +239,7 @@ function ReceiveInvoice({ recipientWalletCurrency, walletId, state, dispatch }: } if (loading || invoiceStatus === "loading" || !invoice?.paymentRequest) { - return ( -
-
-
- ) + return } return ( diff --git a/apps/pay/components/ParsePOSPayment/index.tsx b/apps/pay/components/ParsePOSPayment/index.tsx index f0c6366cde..99020c7911 100644 --- a/apps/pay/components/ParsePOSPayment/index.tsx +++ b/apps/pay/components/ParsePOSPayment/index.tsx @@ -1,6 +1,5 @@ -import { ParsedUrlQuery } from "querystring" - -import { useRouter } from "next/router" +"use client" +import { useRouter, useSearchParams } from "next/navigation" import React, { useEffect } from "react" import Container from "react-bootstrap/Container" import Image from "react-bootstrap/Image" @@ -8,12 +7,12 @@ import Image from "react-bootstrap/Image" import CurrencyInput, { formatValue } from "react-currency-input-field" import useRealtimePrice from "../../lib/use-realtime-price" -import { ACTION_TYPE, ACTIONS } from "../../pages/_reducer" +import { ACTION_TYPE, ACTIONS } from "../../app/reducer" import { formatOperand, - parseDisplayCurrency, safeAmount, getLocaleConfig, + extractSearchParams, } from "../../utils/utils" import Memo from "../Memo" @@ -26,14 +25,18 @@ import styles from "./parse-payment.module.css" import ReceiveInvoice from "./Receive-Invoice" function isRunningStandalone() { + if (typeof window === "undefined") { + return false + } return window.matchMedia("(display-mode: standalone)").matches } interface Props { - defaultWalletCurrency?: string - walletId?: string + defaultWalletCurrency: string + walletId: string dispatch: React.Dispatch state: React.ComponentState + username: string } interface UpdateAmount { @@ -56,10 +59,18 @@ const defaultCurrencyMetadata: Currency = { __typename: "Currency", } -function ParsePayment({ defaultWalletCurrency, walletId, dispatch, state }: Props) { +function ParsePayment({ + defaultWalletCurrency, + walletId, + dispatch, + state, + username, +}: Props) { const router = useRouter() - const { username, amount, sats, unit, memo, currency } = router.query - const { display } = parseDisplayCurrency(router.query) + const searchParams = useSearchParams() + const { amount, sats, unit, memo } = extractSearchParams(searchParams) + + const display = searchParams?.get("display") ?? localStorage.getItem("display") ?? "USD" const { currencyToSats, satsToCurrency, hasLoaded } = useRealtimePrice(display) const { currencyList } = useDisplayCurrency() const [valueInFiat, setValueInFiat] = React.useState(0) @@ -70,25 +81,9 @@ function ParsePayment({ defaultWalletCurrency, walletId, dispatch, state }: Prop defaultCurrencyMetadata, ) const [numOfChanges, setNumOfChanges] = React.useState(0) - + const language = typeof navigator !== "undefined" ? navigator?.language : "en" const prevUnit = React.useRef(AmountUnit.Cent) - if (!currency) { - const queryString = window.location.search - const searchParams = new URLSearchParams(queryString) - searchParams.set("currency", defaultWalletCurrency ?? "BTC") - const newQueryString = searchParams.toString() - window.history.pushState(null, "", "?" + newQueryString) - } - - if (!amount) { - const queryString = window.location.search - const searchParams = new URLSearchParams(queryString) - searchParams.set("amount", "0") - const newQueryString = searchParams.toString() - window.history.pushState(null, "", "?" + newQueryString) - } - // onload // set all query params on first load, even if they are not passed useEffect(() => { @@ -96,10 +91,10 @@ function ParsePayment({ defaultWalletCurrency, walletId, dispatch, state }: Prop const initialAmount = safeAmount(amount).toString() const initialSats = safeAmount(sats).toString() const initialDisplay = display ?? localStorage.getItem("display") ?? "USD" - const initialUsername = router.query.username - const initialQuery = { ...router.query } + const initialUsername = username + const initialQuery = extractSearchParams(searchParams) delete initialQuery?.currency - const newQuery: ParsedUrlQuery = { + const newQuery = { amount: initialAmount, sats: initialSats, unit: initialUnit, @@ -108,21 +103,23 @@ function ParsePayment({ defaultWalletCurrency, walletId, dispatch, state }: Prop username: initialUsername, } if (initialQuery !== newQuery) { - router.push( - { - pathname: `${username}`, - query: { - amount: initialAmount, - sats: initialSats, - unit: initialUnit, - memo: memo ?? "", - display: initialDisplay, - }, - }, - undefined, - { shallow: true }, - ) + const params = new URLSearchParams({ + amount: initialAmount, + sats: initialSats, + unit: initialUnit, + memo: memo ?? "", + display: initialDisplay, + currency: defaultWalletCurrency, + }) + const newUrl = new URL(window.location.toString()) + newUrl.pathname = `/${username}` + newUrl.search = params.toString() + + router.replace(newUrl.toString(), { + scroll: true, + }) } + // this only runs once // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -136,7 +133,7 @@ function ParsePayment({ defaultWalletCurrency, walletId, dispatch, state }: Prop } else if (sats) { return { shouldUpdate: true, - value: sats.toString(), + value: sats?.toString(), } } } else { @@ -152,21 +149,19 @@ function ParsePayment({ defaultWalletCurrency, walletId, dispatch, state }: Prop const toggleCurrency = () => { const newUnit = unit === AmountUnit.Sat ? AmountUnit.Cent : AmountUnit.Sat prevUnit.current = (unit as AmountUnit) || AmountUnit.Cent - router.push( - { - pathname: `${username}`, - query: { - currency: defaultWalletCurrency, - unit: newUnit, - memo, - display, - amount, - sats, - }, - }, - undefined, - { shallow: true }, - ) + const params = new URLSearchParams({ + currency: defaultWalletCurrency, + unit: newUnit, + memo, + display, + amount, + sats, + }) + + const newUrl = new URL(window.location.toString()) + newUrl.pathname = `/${username}` + newUrl.search = params.toString() + router.replace(newUrl.toString()) } // Update Params From Current Amount @@ -190,9 +185,10 @@ function ParsePayment({ defaultWalletCurrency, walletId, dispatch, state }: Prop : safeAmt.toFixed(currencyMetadata.fractionDigits) } if (isNaN(Number(amt))) return + const formattedValue = formatValue({ value: amt, - intlConfig: { locale: navigator.language, currency: display }, + intlConfig: { locale: language, currency: display }, }) localStorage.setItem("formattedFiatValue", formattedValue) setValueInFiat(amt) @@ -216,15 +212,13 @@ function ParsePayment({ defaultWalletCurrency, walletId, dispatch, state }: Prop memo, display, } - if (router.query !== newQuery && !skipRouterPush) { - router.push( - { - pathname: `${username}`, - query: newQuery, - }, - undefined, - { shallow: true }, - ) + + const initialQuery = extractSearchParams(searchParams) + if (initialQuery !== newQuery && !skipRouterPush) { + const newUrl = new URL(window.location.toString()) + newUrl.pathname = `/${username}` + newUrl.search = new URLSearchParams(newQuery).toString() + router.replace(newUrl.toString()) } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -323,7 +317,7 @@ function ParsePayment({ defaultWalletCurrency, walletId, dispatch, state }: Prop fontWeight: 600, }} value={!amount ? 0 : valueInFiat} - intlConfig={{ locale: navigator.language, currency: display }} + intlConfig={{ locale: language, currency: display }} readOnly={true} /> @@ -388,8 +382,7 @@ function ParsePayment({ defaultWalletCurrency, walletId, dispatch, state }: Prop dispatch={dispatch} disabled={unit === AmountUnit.Sat} displayValue={ - getLocaleConfig({ locale: navigator.language, currency: display }) - .decimalSeparator + getLocaleConfig({ locale: language, currency: display }).decimalSeparator } /> ) : ( diff --git a/apps/pay/components/PaymentOutcome/index.tsx b/apps/pay/components/PaymentOutcome/index.tsx index 5094d76517..9c1fda2b8d 100644 --- a/apps/pay/components/PaymentOutcome/index.tsx +++ b/apps/pay/components/PaymentOutcome/index.tsx @@ -1,4 +1,5 @@ -import { useRouter } from "next/router" +"use client" +import { useParams, useSearchParams } from "next/navigation" import React, { useRef } from "react" import Image from "react-bootstrap/Image" @@ -6,13 +7,15 @@ import { useReactToPrint } from "react-to-print" import { gql } from "@apollo/client" -import { ACTIONS, ACTION_TYPE } from "../../pages/_reducer" +import { ACTIONS, ACTION_TYPE } from "../../app/reducer" import { useLnInvoicePaymentStatusSubscription } from "../../lib/graphql/generated" import styles from "./payment-outcome.module.css" import Receipt from "./receipt" +import { extractSearchParams } from "@/utils/utils" + interface Props { paymentRequest: string paymentAmount: string | string[] | undefined @@ -33,8 +36,9 @@ gql` ` function PaymentOutcome({ paymentRequest, paymentAmount, dispatch }: Props) { - const router = useRouter() - const { amount, sats, username, memo } = router.query + const searchParams = useSearchParams() + const { username } = useParams() + const { amount, sats, memo } = extractSearchParams(searchParams) const componentRef = useRef(null) const printReceipt = useReactToPrint({ diff --git a/apps/pay/components/PaymentOutcome/receipt.tsx b/apps/pay/components/PaymentOutcome/receipt.tsx index cb7e93fdb0..0fbff9b03a 100644 --- a/apps/pay/components/PaymentOutcome/receipt.tsx +++ b/apps/pay/components/PaymentOutcome/receipt.tsx @@ -1,3 +1,4 @@ +"use client" import React from "react" import Image from "react-bootstrap/Image" diff --git a/apps/pay/components/PinToHomescreen/index.tsx b/apps/pay/components/PinToHomescreen/index.tsx index 6ff26beae8..534f39f103 100644 --- a/apps/pay/components/PinToHomescreen/index.tsx +++ b/apps/pay/components/PinToHomescreen/index.tsx @@ -2,7 +2,7 @@ import React from "react" import { Image } from "react-bootstrap" import { getOS } from "../../lib/download" -import { ACTIONS, ACTION_TYPE } from "../../pages/_reducer" +import { ACTIONS, ACTION_TYPE } from "../../app/reducer" import Modal from "../CustomModal/modal" import { @@ -23,7 +23,7 @@ const browser = (function () { //@ts-ignore const test = function (regexp) { if (typeof window !== "undefined") { - return regexp.test(window.navigator.userAgent) + return regexp.test(window.navigator?.userAgent) } } switch (true) { @@ -62,7 +62,7 @@ const PinToHomescreen = ({ pinnedToHomeScreenModalVisible, dispatch }: Props) => }) } - const ua = window.navigator.userAgent + const ua = window.navigator?.userAgent const iOS = !!ua.match(/iPad/i) || !!ua.match(/iPhone/i) const webkit = !!ua.match(/WebKit/i) const iOSSafari = iOS && webkit && !ua.match(/CriOS/i) diff --git a/apps/pay/components/Share/share-controller.tsx b/apps/pay/components/Share/share-controller.tsx index a63b522f4c..907de22335 100644 --- a/apps/pay/components/Share/share-controller.tsx +++ b/apps/pay/components/Share/share-controller.tsx @@ -25,12 +25,12 @@ const ShareController: FC = ({ getImage && getImage() onInteraction && onInteraction() - if (!navigator.canShare) { + if (!navigator?.canShare) { return onNonNativeShare && onNonNativeShare() } try { - await navigator.share(shareData) + await navigator?.share(shareData) onSuccess && onSuccess() } catch (err) { onError && onError(err) diff --git a/apps/pay/components/Share/share-pop-up.tsx b/apps/pay/components/Share/share-pop-up.tsx index fb12eae74c..5449cb205c 100644 --- a/apps/pay/components/Share/share-pop-up.tsx +++ b/apps/pay/components/Share/share-pop-up.tsx @@ -32,7 +32,7 @@ const SharePopup: FC = ({ const data = await fetch(image) const blob = await data.blob() - await navigator.clipboard.write([ + await navigator?.clipboard.write([ new ClipboardItem({ [blob.type]: blob, }), diff --git a/apps/pay/components/apollo-wrapper.tsx b/apps/pay/components/apollo-wrapper.tsx new file mode 100644 index 0000000000..d64d5bc68f --- /dev/null +++ b/apps/pay/components/apollo-wrapper.tsx @@ -0,0 +1,100 @@ +"use client" +import { ApolloLink, HttpLink, split } from "@apollo/client" +import { + ApolloNextAppProvider, + NextSSRInMemoryCache, + NextSSRApolloClient, + SSRMultipartLink, +} from "@apollo/experimental-nextjs-app-support/ssr" + +import { RetryLink } from "@apollo/client/link/retry" +import { onError } from "@apollo/client/link/error" +import { getMainDefinition } from "@apollo/client/utilities" +import { GraphQLWsLink } from "@apollo/client/link/subscriptions" +import { createClient } from "graphql-ws" + +import { getClientSideGqlConfig } from "@/config/config" + +function makeClient() { + const httpLink = new HttpLink({ + uri: getClientSideGqlConfig().coreGqlUrl, + fetchOptions: { cache: "no-store" }, + }) + + const wsLink = new GraphQLWsLink( + createClient({ + url: getClientSideGqlConfig().coreGqlWebSocketUrl, + retryAttempts: 12, + connectionParams: {}, + shouldRetry: (errOrCloseEvent) => { + console.warn({ errOrCloseEvent }, "entering shouldRetry function for websocket") + // TODO: understand how the backend is closing the connection + // for instance during a new version rollout or k8s upgrade + // + // in the meantime: + // returning true instead of the default 'Any non-`CloseEvent`' + // to force createClient to attempt a reconnection + return true + }, + // Voluntary not using: webSocketImpl: WebSocket + // seems react native already have an implement of the websocket? + // + // TODO: implement keepAlive and reconnection? + // https://github.com/enisdenjo/graphql-ws/blob/master/docs/interfaces/client.ClientOptions.md#keepalive + }), + ) + + const errorLink = onError(({ graphQLErrors, networkError }) => { + if (graphQLErrors) + graphQLErrors.forEach(({ message, locations, path }) => { + if (message === "PersistedQueryNotFound") { + console.log(`[GraphQL info]: Message: ${message}, Path: ${path}}`, { + locations, + }) + } else { + console.warn(`[GraphQL error]: Message: ${message}, Path: ${path}}`, { + locations, + }) + } + }) + + if (networkError) { + console.log(`[Network error]: ${networkError}`) + } + }) + + const retryLink = new RetryLink({ + attempts: { + max: 5, + }, + }) + + const link = split( + ({ query }) => { + const definition = getMainDefinition(query) + return ( + definition.kind === "OperationDefinition" && + definition.operation === "subscription" + ) + }, + wsLink, + ApolloLink.from([errorLink, retryLink, httpLink]), + ) + + return new NextSSRApolloClient({ + cache: new NextSSRInMemoryCache(), + link: + typeof window === "undefined" + ? ApolloLink.from([ + new SSRMultipartLink({ + stripDefer: true, + }), + link, + ]) + : link, + }) +} + +export function ApolloWrapper({ children }: React.PropsWithChildren) { + return {children} +} diff --git a/apps/pay/components/formatted-input.tsx b/apps/pay/components/formatted-input.tsx deleted file mode 100644 index 0b4822a195..0000000000 --- a/apps/pay/components/formatted-input.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from "react" - -const formatter = new Intl.NumberFormat("en-US", { - maximumFractionDigits: 2, -}) - -const parseInputValue = (inputValue: string) => { - // TODO: Consider other currency amount formats here - const numberValue = Number(inputValue.replace(/[^0-9.]/g, "")) - const inputValueIncomplete = inputValue.match(/(\.[1-9]?0+|\.)$/) - const formattedValue = - // Allaw fixing invalid input and typing the decimal part at the end - Number.isNaN(numberValue) || inputValueIncomplete - ? inputValue - : formatter.format(numberValue) - return { - numberValue, - formattedValue, - } -} - -function FormattedInput({ - value, - onValueChange, -}: { - value: string - onValueChange: ({ - numberValue, - formattedValue, - }: { - numberValue: number - formattedValue: string - }) => void -}) { - const [input, setInput] = React.useState(parseInputValue(value)) - - const handleOnChange = (e: React.ChangeEvent) => { - // Block more than 2 decmial numbers or points in the input - if (e.target.value.match(/(\.[0-9]{3,}$|\..*\.)/)) return - - const parsedInputValue = parseInputValue(e.target.value) - - setInput(parsedInputValue) - onValueChange(parsedInputValue) - } - - return -} - -export default React.memo(FormattedInput) diff --git a/apps/pay/components/generate-invoice.tsx b/apps/pay/components/generate-invoice.tsx deleted file mode 100644 index e4eb7dfbdd..0000000000 --- a/apps/pay/components/generate-invoice.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import React, { useEffect, useRef } from "react" - -import { useRouter } from "next/router" - -import useCreateInvoice from "../hooks/use-Create-Invoice" - -import useSatPrice from "../lib/use-sat-price" - -import Invoice from "./invoice" - -const INVOICE_STALE_CHECK_INTERVAL = 2 * 60 * 1000 -const INVOICE_EXPIRE_INTERVAL = 60 * 60 * 1000 - -function GenerateInvoice({ - recipientWalletId, - recipientWalletCurrency, - amountInBase, - regenerate, - currency, -}: { - recipientWalletId: string - recipientWalletCurrency: string - amountInBase: number - regenerate: () => void - currency: string -}) { - const { createInvoice, data, loading, errorsMessage, invoiceStatus, setInvoiceStatus } = - useCreateInvoice({ recipientWalletCurrency }) - const timerIds = useRef([]) - const router = useRouter() - const { satsToUsd } = useSatPrice() - - const clearAllTimers = () => { - timerIds.current.forEach((timerId) => clearTimeout(timerId)) - } - useEffect(() => { - let amt = amountInBase - if (recipientWalletCurrency === "USD") { - if (!router.query.sats || typeof router.query.sats !== "string") { - alert("No sats amount provided") - return - } else { - const usdAmount = satsToUsd(Number(router.query.sats)) - if (isNaN(usdAmount)) return - const cents = parseFloat(usdAmount.toFixed(2)) * 100 - amt = Number(cents.toFixed()) - } - } - if (amt === null) return - - createInvoice({ - variables: { - input: { - recipientWalletId, - amount: amt, - }, - }, - }) - if (currency !== "SATS" || recipientWalletCurrency === "USD") { - timerIds.current.push( - window.setTimeout( - () => setInvoiceStatus("need-update"), - INVOICE_STALE_CHECK_INTERVAL, - ), - ) - } - timerIds.current.push( - window.setTimeout(() => setInvoiceStatus("expired"), INVOICE_EXPIRE_INTERVAL), - ) - return clearAllTimers - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [recipientWalletId, amountInBase, currency, createInvoice]) - - const errorString: string | null = errorsMessage || null - let invoice - - if (data) { - if ("lnInvoiceCreateOnBehalfOfRecipient" in data) { - const { lnInvoiceCreateOnBehalfOfRecipient: invoiceData } = data - if (invoiceData.invoice) { - invoice = invoiceData.invoice - } - } - if ("lnUsdInvoiceCreateOnBehalfOfRecipient" in data) { - const { lnUsdInvoiceCreateOnBehalfOfRecipient: invoiceData } = data - if (invoiceData.invoice) { - invoice = invoiceData.invoice - } - } - } - - if (errorString) { - return
{errorString}
- } - - if (loading) { - return
Creating Invoice...
- } - - if (!invoice) return null - - if (invoiceStatus === "expired") { - return ( -
- Invoice Expired...{" "} - - Generate New Invoice - -
- ) - } - - return ( - <> - {invoiceStatus === "need-update" && ( -
- Stale Price...{" "} - - Regenerate Invoice - -
- )} - - - ) -} - -export default GenerateInvoice diff --git a/apps/pay/components/invoice.tsx b/apps/pay/components/invoice.tsx deleted file mode 100644 index 0d329354e8..0000000000 --- a/apps/pay/components/invoice.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import copy from "copy-to-clipboard" -import { useState } from "react" -import { Card } from "react-bootstrap" -import OverlayTrigger from "react-bootstrap/OverlayTrigger" -import Tooltip from "react-bootstrap/Tooltip" -import Lottie from "react-lottie" -import { QRCode } from "react-qrcode-logo" - -import { useLnInvoicePaymentStatusSubscription } from "../lib/graphql/generated" - -import animationData from "./success-animation.json" - -export default function Invoice({ - paymentRequest, - onPaymentSuccess, -}: { - paymentRequest: string - onPaymentSuccess?: () => void -}) { - const [showCopied, setShowCopied] = useState(false) - - const { loading, data, error } = useLnInvoicePaymentStatusSubscription({ - variables: { - input: { paymentRequest }, - }, - onSubscriptionData: ({ subscriptionData }) => { - if (subscriptionData?.data?.lnInvoicePaymentStatus?.status === "PAID") { - onPaymentSuccess && onPaymentSuccess() - } - }, - }) - - const copyInvoice = () => { - copy(paymentRequest) - setShowCopied(true) - setTimeout(() => { - setShowCopied(false) - }, 3000) - } - - if (error) { - console.error(error) - return
{error.message}
- } - - if (loading) { - return ( - - Scan using a Lightning-supported wallet - - Copied!} - > -
- -
-
-

- Click on the QR code to copy
or{" "} - Open with wallet -

-

Waiting for payment confirmation...

-
- ) - } - - if (data) { - const { errors, status } = data.lnInvoicePaymentStatus - if (errors.length > 0) { - console.error(errors) - return
{errors[0].message}
- } - if (status === "PAID") { - return ( -
- -
- ) - } - } - - return
Something went wrong
-} diff --git a/apps/pay/components/receive-amount.tsx b/apps/pay/components/receive-amount.tsx deleted file mode 100644 index 50fdb6fe17..0000000000 --- a/apps/pay/components/receive-amount.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { ParsedUrlQuery } from "querystring" - -import React, { useState, useEffect } from "react" -import { useDebouncedCallback } from "use-debounce" -import { useRouter } from "next/router" - -import useSatPrice from "../lib/use-sat-price" - -import { parseDisplayCurrency } from "../utils/utils" - -import FormattedInput from "./formatted-input" -import GenerateInvoice from "./generate-invoice" - -const usdFormatter = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - maximumFractionDigits: 2, -}) - -const satsFormatter = new Intl.NumberFormat("en-US", { - maximumFractionDigits: 0, -}) - -export default function ReceiveAmount({ - recipientWalletId, - recipientWalletCurrency, -}: { - recipientWalletId: string - recipientWalletCurrency: string -}) { - const router = useRouter() - const { satsToUsd, usdToSats } = useSatPrice() - const { amount, currency } = parseQueryAmount(router.query) // USD or SATs - const { display } = parseDisplayCurrency(router.query) - - function toggleCurrency() { - const newCurrency = currency === "SATS" ? "USD" : "SATS" - const newAmount = - newCurrency === "SATS" ? Math.round(usdToSats(amount)) : satsToUsd(amount) - - router.push( - getUpdatedURL(router.query, { currency: newCurrency, amount: newAmount, display }), - ) - } - - const handleAmountUpdate = useDebouncedCallback(({ numberValue }) => { - router.push(getUpdatedURL(router.query, { amount: numberValue })) - }, 1000) - - const getSatsForInvoice = React.useCallback(() => { - return Math.round(currency === "SATS" ? amount : Math.round(usdToSats(amount))) - }, [amount, currency, usdToSats]) - const [satsForInvoice, setSatsForInvoice] = useState(() => getSatsForInvoice()) - - const getCentsForInvoice = React.useCallback(() => { - return Math.round(currency === "USD" ? amount * 100 : satsToUsd(amount) * 100) - }, [amount, currency, satsToUsd]) - const [centsForInvoice, setCentsForInvoice] = useState(() => getCentsForInvoice()) - - function triggerRegenerateUsdInvoice() { - setCentsForInvoice(0) - setTimeout(() => setCentsForInvoice(getCentsForInvoice())) - } - - function triggerRegenerateBtcInvoice() { - setSatsForInvoice(0) - setTimeout(() => setSatsForInvoice(getSatsForInvoice())) - } - - const convertedValue = - currency === "SATS" - ? usdFormatter.format(satsToUsd(amount)) - : satsFormatter.format(satsForInvoice) + " sats" - - useEffect(() => { - const newSats = getSatsForInvoice() - if (newSats !== satsForInvoice) setSatsForInvoice(newSats) - const newCents = getCentsForInvoice() - if (newCents !== centsForInvoice) setCentsForInvoice(newCents) - }, [getSatsForInvoice, satsForInvoice, centsForInvoice, getCentsForInvoice]) - - const amountInBase = - recipientWalletCurrency === "USD" ? centsForInvoice : satsForInvoice - const triggerRegenerateInvoice = - recipientWalletCurrency === "USD" - ? triggerRegenerateUsdInvoice - : triggerRegenerateBtcInvoice - return ( - <> -
-
{currency === "SATS" ? "sats" : "$"}
-
- -
-
- ⇅ -
-
-
≈ {convertedValue}
- - {amountInBase > 0 && ( - - )} - - ) -} - -function parseQueryAmount(query: ParsedUrlQuery) { - const currency = query.currency as string | null - - return { - amount: Number(query.amount) || 0, - currency: currency?.toUpperCase() || "USD", - } -} - -function getUpdatedURL( - query: ParsedUrlQuery, - update: Record, -): string { - const { username, ...params } = query - - const newEntries = Object.entries(params) - const stringEntries = (newEntries || []).map(([k, v]) => [k, v?.toString()]) - const qs = new URLSearchParams(Object.fromEntries(stringEntries)) - - Object.entries(update).forEach(([k, v]) => { - qs.set(k, v.toString()) - }) - - return `/${username}?${qs.toString()}` -} diff --git a/apps/pay/components/receive-no-amount.tsx b/apps/pay/components/receive-no-amount.tsx deleted file mode 100644 index 0d26ca9f5c..0000000000 --- a/apps/pay/components/receive-no-amount.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useEffect } from "react" -import { gql, useMutation } from "@apollo/client" - -import Invoice from "./invoice" - -type OperationError = { - message: string -} - -type LnInvoiceObject = { - paymentRequest: string -} - -const LN_NOAMOUNT_INVOICE_CREATE_ON_BEHALF_OF_RECIPIENT = gql` - mutation lnNoAmountInvoiceCreateOnBehalfOfRecipient($walletId: WalletId!) { - mutationData: lnNoAmountInvoiceCreateOnBehalfOfRecipient( - input: { recipientWalletId: $walletId } - ) { - errors { - message - } - invoice { - paymentRequest - } - } - } -` - -export default function ReceiveNoAmount({ - recipientWalletId, - onSetAmountClick, -}: { - recipientWalletId: string - onSetAmountClick: () => void -}) { - const [createInvoice, { loading, error, data }] = useMutation<{ - mutationData: { - errors: OperationError[] - invoice?: LnInvoiceObject - } - }>(LN_NOAMOUNT_INVOICE_CREATE_ON_BEHALF_OF_RECIPIENT, { onError: console.error }) - - useEffect(() => { - createInvoice({ - variables: { walletId: recipientWalletId }, - }) - }, [createInvoice, recipientWalletId]) - - if (error) { - return
{error.message}
- } - - let invoice - - if (data) { - const invoiceData = data.mutationData - - if (invoiceData.errors?.length > 0) { - return
{invoiceData.errors[0].message}
- } - - invoice = invoiceData.invoice - } - - return ( - <> - {loading &&
Loading...
} - - - - {invoice && } - - ) -} diff --git a/apps/pay/config/config.ts b/apps/pay/config/config.ts index 7e4ba9edbd..3112d5498a 100644 --- a/apps/pay/config/config.ts +++ b/apps/pay/config/config.ts @@ -1,29 +1,30 @@ import { env } from "../env" + export const USD_INVOICE_EXPIRE_INTERVAL = 60 * 5 export const MAX_INPUT_VALUE_LENGTH = 14 export const APP_DESCRIPTION = "Blink official lightning network node" // TODO get rid of this by removing the use of build time env vars in the client export const getClientSideGqlConfig = () => { - const hostname = new URL(window.location.href).hostname + let hostname: string | null = null + let coreGqlUrl: string | null = null + let coreGqlWebSocketUrl: string | null = null - let coreGqlUrl + if (typeof window !== "undefined") { + hostname = new URL(window.location.href).hostname + } - // Allow overriding the coreGqlUrl for local development, otherwise use the default in the URL - if (env.NEXT_PUBLIC_CORE_GQL_URL || typeof window === "undefined") { + if (env.NEXT_PUBLIC_CORE_GQL_URL) { coreGqlUrl = env.NEXT_PUBLIC_CORE_GQL_URL - } else { + } else if (hostname) { const hostPartsApi = hostname.split(".") hostPartsApi[0] = "api" coreGqlUrl = `https://${hostPartsApi.join(".")}/graphql` } - let coreGqlWebSocketUrl - - // Allow overriding the coreGqlWebSocketUrl for local development, otherwise use the default in the URL - if (env.NEXT_PUBLIC_CORE_GQL_WEB_SOCKET_URL || typeof window === "undefined") { + if (env.NEXT_PUBLIC_CORE_GQL_WEB_SOCKET_URL) { coreGqlWebSocketUrl = env.NEXT_PUBLIC_CORE_GQL_WEB_SOCKET_URL - } else { + } else if (hostname) { const hostPartsWs = hostname.split(".") hostPartsWs[0] = "ws" coreGqlWebSocketUrl = `wss://${hostPartsWs.join(".")}/graphql` @@ -36,9 +37,12 @@ export const getClientSideGqlConfig = () => { } export const getClientSidePayDomain = () => { - if (env.NEXT_PUBLIC_PAY_DOMAIN || typeof window === "undefined") { + if (env.NEXT_PUBLIC_PAY_DOMAIN) { return env.NEXT_PUBLIC_PAY_DOMAIN + } else if (typeof window !== "undefined") { + // Return the last two parts of the hostname (e.g. "blink.sv") + return new URL(window.location.href).hostname.split(".").slice(-2).join(".") } else { - return new URL(window.location.href).hostname.split(".").slice(-2).join(".") // Return the last two parts of the hostname (e.g. "blink.sv") + return "" } } diff --git a/apps/pay/lib/download.ts b/apps/pay/lib/download.ts index 1177ae50e6..6f30d3bff5 100644 --- a/apps/pay/lib/download.ts +++ b/apps/pay/lib/download.ts @@ -1,6 +1,12 @@ export const getOS = () => { - // @ts-expect-error: opera browser property is not a standard thing - const userAgent = navigator.userAgent || navigator.vendor || window.opera + let userAgent + + if (typeof window !== "undefined") { + // @ts-expect-error: opera browser property is not a standard thing + userAgent = navigator?.userAgent || navigator?.vendor || window.opera + } else { + return undefined + } // Windows Phone must come first because its UA also contains "Android" if (/windows phone/i.test(userAgent)) { diff --git a/apps/pay/lib/graphql.tsx b/apps/pay/lib/graphql.tsx deleted file mode 100644 index d9dcf3d724..0000000000 --- a/apps/pay/lib/graphql.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import * as React from "react" -import { - ApolloProvider, - ApolloClient, - InMemoryCache, - split, - HttpLink, - ApolloLink, -} from "@apollo/client" -import { getMainDefinition } from "@apollo/client/utilities" -import { GraphQLWsLink } from "@apollo/client/link/subscriptions" -import { RetryLink } from "@apollo/client/link/retry" -import { onError } from "@apollo/client/link/error" - -import { createClient } from "graphql-ws" - -import { getClientSideGqlConfig } from "../config/config" - -const httpLink = new HttpLink({ - uri: getClientSideGqlConfig().coreGqlUrl, -}) - -const wsLink = new GraphQLWsLink( - createClient({ - url: getClientSideGqlConfig().coreGqlWebSocketUrl, - retryAttempts: 12, - connectionParams: {}, - shouldRetry: (errOrCloseEvent) => { - console.warn({ errOrCloseEvent }, "entering shouldRetry function for websocket") - // TODO: understand how the backend is closing the connection - // for instance during a new version rollout or k8s upgrade - // - // in the meantime: - // returning true instead of the default 'Any non-`CloseEvent`' - // to force createClient to attempt a reconnection - return true - }, - // Voluntary not using: webSocketImpl: WebSocket - // seems react native already have an implement of the websocket? - // - // TODO: implement keepAlive and reconnection? - // https://github.com/enisdenjo/graphql-ws/blob/master/docs/interfaces/client.ClientOptions.md#keepalive - }), -) - -const errorLink = onError(({ graphQLErrors, networkError }) => { - // graphqlErrors should be managed locally - if (graphQLErrors) - graphQLErrors.forEach(({ message, locations, path }) => { - if (message === "PersistedQueryNotFound") { - console.log(`[GraphQL info]: Message: ${message}, Path: ${path}}`, { - locations, - }) - } else { - console.warn(`[GraphQL error]: Message: ${message}, Path: ${path}}`, { - locations, - }) - } - }) - - // only network error are managed globally - if (networkError) { - console.log(`[Network error]: ${networkError}`) - } -}) - -const retryLink = new RetryLink({ - attempts: { - max: 5, - }, -}) - -const link = split( - ({ query }) => { - const definition = getMainDefinition(query) - return ( - definition.kind === "OperationDefinition" && definition.operation === "subscription" - ) - }, - wsLink, - ApolloLink.from([errorLink, retryLink, httpLink]), -) - -const client = new ApolloClient({ - link, - cache: new InMemoryCache(), -}) - -export default ({ children }: { children: React.ReactNode }) => ( - {children} -) diff --git a/apps/pay/package.json b/apps/pay/package.json index c83b4342c9..b3db2a8013 100644 --- a/apps/pay/package.json +++ b/apps/pay/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@apollo/client": "^3.7.12", + "@apollo/experimental-nextjs-app-support": "^0.8.0", "@galoymoney/client": "^0.2.2", "@t3-oss/env-nextjs": "^0.6.1", "bech32": "^2.0.0", @@ -26,7 +27,7 @@ "ioredis": "^5.3.1", "lnurl-pay": "^1.0.1", "lodash.debounce": "^4.0.8", - "next": "^13.4.19", + "next": "^14.1.0", "react": "^18.2.0", "react-bootstrap": "^1.6.4", "react-currency-input-field": "^3.6.10", @@ -34,6 +35,7 @@ "react-lottie": "^1.2.3", "react-qrcode-logo": "^2.9.0", "react-to-print": "^2.14.12", + "ssr": "link:@apollo/experimental-nextjs-app-support/ssr", "use-debounce": "^8.0.4", "use-react-screenshot": "^3.0.0", "zod": "^3.22.4" @@ -59,7 +61,7 @@ "@typescript-eslint/parser": "^5.51.0", "cypress": "^13.6.1", "eslint": "^8.52.0", - "eslint-config-next": "14.0.4", + "eslint-config-next": "14.1.0", "eslint-config-react-app": "^7.0.1", "eslint-plugin-flowtype": "^8.0.3", "eslint-plugin-import": "^2.26.0", diff --git a/apps/pay/pages/[username].tsx b/apps/pay/pages/[username].tsx deleted file mode 100644 index b7038a8be2..0000000000 --- a/apps/pay/pages/[username].tsx +++ /dev/null @@ -1,169 +0,0 @@ -import Link from "next/link" -import { useRouter } from "next/router" -import React from "react" -import Container from "react-bootstrap/Container" -import Image from "react-bootstrap/Image" - -import Head from "next/head" - -import { gql } from "@apollo/client" - -import ParsePayment from "../components/ParsePOSPayment" -import PinToHomescreen from "../components/PinToHomescreen" - -import CurrencyDropdown from "../components/Currency/currency-dropdown" - -import { useAccountDefaultWalletsQuery } from "../lib/graphql/generated" - -import reducer, { ACTIONS } from "./_reducer" -import styles from "./_user.module.css" - -gql` - query accountDefaultWallets($username: Username!) { - accountDefaultWallet(username: $username) { - __typename - id - walletCurrency - } - } -` - -function ReceivePayment() { - const router = useRouter() - const { username, memo, display } = router.query - const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) - const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) - - let accountUsername: string - if (!username) { - accountUsername = "" - } else { - accountUsername = username.toString() - } - - if (!display) { - const displayFromLocal = localStorage.getItem("display") ?? "USD" - const queryString = window.location.search - const searchParams = new URLSearchParams(queryString) - searchParams.set("display", displayFromLocal) - const newQueryString = searchParams.toString() - window.history.pushState(null, "", "?" + newQueryString) - } - - const manifestParams = new URLSearchParams() - if (memo) { - manifestParams.set("memo", memo.toString()) - } - - const { data, error: usernameError } = useAccountDefaultWalletsQuery({ - variables: { username: accountUsername }, - skip: !accountUsername, - }) - - const [state, dispatch] = React.useReducer(reducer, { - currentAmount: "", - createdInvoice: false, - walletCurrency: data?.accountDefaultWallet.walletCurrency || "USD", - username: accountUsername, - pinnedToHomeScreenModalVisible: false, - }) - - React.useEffect(() => { - if (state.walletCurrency === data?.accountDefaultWallet.walletCurrency) { - return - } - dispatch({ - type: ACTIONS.UPDATE_WALLET_CURRENCY, - payload: data?.accountDefaultWallet.walletCurrency, - }) - dispatch({ type: ACTIONS.UPDATE_USERNAME, payload: username }) - }, [state, username, data]) - - return ( - <> - {router.query.username ? ( - - - - - {usernameError ? ( -
-

{`${usernameError.message}.`}

-

Please check the username in your browser URL and try again.

- localStorage.removeItem("username")} - > - Back - -
- ) : ( - <> - -
- {state.createdInvoice && ( - - )} -

{`Pay ${username}`}

-
- { - localStorage.setItem("display", newDisplayCurrency) - router.push( - { - query: { ...router.query, display: newDisplayCurrency }, - }, - undefined, - { shallow: true }, - ) - setTimeout(() => { - // hard reload to re-calculate currency - // in a future PR we can manage state globally for selected display currency - window.location.reload() - }, 100) - }} - /> -
-
- {/* {memo &&

{`Memo: ${memo}`}

} */} - - - - )} -
- ) : null} - - ) -} - -export default ReceivePayment diff --git a/apps/pay/pages/setuppwa/index.tsx b/apps/pay/pages/setuppwa/index.tsx deleted file mode 100644 index 35011cd29d..0000000000 --- a/apps/pay/pages/setuppwa/index.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { useRouter } from "next/router" -import React from "react" - -import CurrencyDropdown from "../../components/Currency/currency-dropdown" - -const SetupPwa = () => { - const router = useRouter() - const [username, setUsername] = React.useState("") - const username_from_local = localStorage.getItem("username") - const display_currency_from_local = localStorage.getItem("display") - const [selectedDisplayCurrency, setSelectedDisplayCurrency] = React.useState("USD") - - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault() - - if (!username_from_local) { - localStorage.setItem("username", username) - } - - if (!display_currency_from_local) { - localStorage.setItem("display", selectedDisplayCurrency) - } - - router.push( - { - pathname: `${username}`, - query: { display: selectedDisplayCurrency }, - }, - undefined, - { shallow: true }, - ) - } - - React.useEffect(() => { - if (username_from_local) { - router.push( - { - pathname: `${username_from_local}`, - query: { display: display_currency_from_local }, - }, - undefined, - { shallow: true }, - ) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [username_from_local]) - - if (!username_from_local) { - return ( -
-
) => handleSubmit(event)} - > -

Welcome to Blink POS application.

- - ) => - setUsername(event.target.value) - } - placeholder="username" - required - /> - - { - if (newDisplayCurrency) { - setSelectedDisplayCurrency(newDisplayCurrency) - } - }} - /> - - -
- ) - } - return ( -
-
-
- ) -} - -export default SetupPwa diff --git a/apps/pay/tsconfig.json b/apps/pay/tsconfig.json index 2b5cff7b42..8013931202 100644 --- a/apps/pay/tsconfig.json +++ b/apps/pay/tsconfig.json @@ -24,7 +24,10 @@ { "name": "next" } - ] + ], + "paths": { + "@/*": ["./*"] + } }, "include": [ "**/*", diff --git a/apps/pay/utils/utils.ts b/apps/pay/utils/utils.ts index 1341029810..941b859384 100644 --- a/apps/pay/utils/utils.ts +++ b/apps/pay/utils/utils.ts @@ -1,6 +1,9 @@ -import { ParsedUrlQuery } from "querystring" import { URL } from "url" +import { ParsedUrlQuery } from "querystring" + +import { ReadonlyURLSearchParams } from "next/navigation" + export const usdFormatter = new Intl.NumberFormat("en-US", { // style: "currency", // currency: "USD", @@ -53,7 +56,7 @@ export const getOriginalRequestInfo = (request: Request) => { } } -export function safeAmount(amount: number | string | string[] | undefined) { +export function safeAmount(amount: number | string | string[] | undefined | null) { try { if (isNaN(Number(amount))) return 0 const theSafeAmount = ( @@ -120,3 +123,7 @@ export const getLocaleConfig = (intlConfig?: IntlConfig): LocaleConfig => { return prev }, defaultConfig) } + +export const extractSearchParams = (searchParams: ReadonlyURLSearchParams) => { + return searchParams ? Object.fromEntries(searchParams.entries()) : {} +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d485357c00..4fcf29ba61 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -571,9 +571,12 @@ importers: '@apollo/client': specifier: ^3.7.12 version: 3.8.8(graphql-ws@5.14.2)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0) + '@apollo/experimental-nextjs-app-support': + specifier: ^0.8.0 + version: 0.8.0(@apollo/client@3.8.8)(next@14.1.0)(react@18.2.0) '@galoymoney/client': specifier: ^0.2.2 - version: 0.2.6(@bitcoinerlab/secp256k1@1.0.5)(bitcoinjs-lib@5.0.5)(bolt11@1.4.1)(lnurl-pay@1.0.1)(url@0.11.3) + version: 0.2.6(@bitcoinerlab/secp256k1@1.1.1)(bitcoinjs-lib@5.0.5)(bolt11@1.4.1)(lnurl-pay@1.0.1)(url@0.11.3) '@t3-oss/env-nextjs': specifier: ^0.6.1 version: 0.6.1(typescript@5.2.2)(zod@3.22.4) @@ -611,8 +614,8 @@ importers: specifier: ^4.0.8 version: 4.0.8 next: - specifier: ^13.4.19 - version: 13.5.6(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) + specifier: ^14.1.0 + version: 14.1.0(@babel/core@7.23.9)(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0) react: specifier: ^18.2.0 version: 18.2.0 @@ -634,6 +637,9 @@ importers: react-to-print: specifier: ^2.14.12 version: 2.14.15(react-dom@18.2.0)(react@18.2.0) + ssr: + specifier: link:@apollo/experimental-nextjs-app-support/ssr + version: link:@apollo/experimental-nextjs-app-support/ssr use-debounce: specifier: ^8.0.4 version: 8.0.4(react@18.2.0) @@ -705,8 +711,8 @@ importers: specifier: ^8.52.0 version: 8.54.0 eslint-config-next: - specifier: 14.0.4 - version: 14.0.4(eslint@8.54.0)(typescript@5.2.2) + specifier: 14.1.0 + version: 14.1.0(eslint@8.54.0)(typescript@5.2.2) eslint-config-react-app: specifier: ^7.0.1 version: 7.0.1(@babel/plugin-syntax-flow@7.23.3)(@babel/plugin-transform-react-jsx@7.23.4)(eslint@8.54.0)(typescript@5.2.2) @@ -715,7 +721,7 @@ importers: version: 8.0.3(@babel/plugin-syntax-flow@7.23.3)(@babel/plugin-transform-react-jsx@7.23.4)(eslint@8.54.0) eslint-plugin-import: specifier: ^2.26.0 - version: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint@8.54.0) + version: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) eslint-plugin-jest: specifier: ^27.2.1 version: 27.6.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint@8.54.0)(typescript@5.2.2) @@ -1578,6 +1584,21 @@ packages: ts-invariant: 0.10.3 dev: false + /@apollo/experimental-nextjs-app-support@0.8.0(@apollo/client@3.8.8)(next@14.1.0)(react@18.2.0): + resolution: {integrity: sha512-uyNIkOkew0T6ukC8ycbWBeTu8gtDSD5i+NVGEHU0DIEQaToFHObYcvIxaQ/8hvWzgvnpNU/KMsApfGXA9Xkpyw==} + peerDependencies: + '@apollo/client': ^3.9.0 + next: ^13.4.1 || ^14.0.0 + react: ^18 + dependencies: + '@apollo/client': 3.8.8(graphql-ws@5.14.2)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0) + next: 14.1.0(@babel/core@7.23.9)(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + server-only: 0.0.1 + superjson: 1.13.3 + ts-invariant: 0.10.3 + dev: false + /@apollo/protobufjs@1.2.7: resolution: {integrity: sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==} hasBin: true @@ -5323,8 +5344,8 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@bitcoinerlab/secp256k1@1.0.5: - resolution: {integrity: sha512-8gT+ukTCFN2rTxn4hD9Jq3k+UJwcprgYjfK/SQUSLgznXoIgsBnlPuARMkyyuEjycQK9VvnPiejKdszVTflh+w==} + /@bitcoinerlab/secp256k1@1.1.1: + resolution: {integrity: sha512-uhjW51WfVLpnHN7+G0saDcM/k9IqcyTbZ+bDgLF3AX8V/a3KXSE9vn7UPBrcdU72tp0J4YPR7BHp2m7MLAZ/1Q==} dependencies: '@noble/hashes': 1.3.3 '@noble/secp256k1': 1.7.1 @@ -6103,7 +6124,7 @@ packages: /@floating-ui/utils@0.1.6: resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} - /@galoymoney/client@0.2.6(@bitcoinerlab/secp256k1@1.0.5)(bitcoinjs-lib@5.0.5)(bolt11@1.4.1)(lnurl-pay@1.0.1)(url@0.11.3): + /@galoymoney/client@0.2.6(@bitcoinerlab/secp256k1@1.1.1)(bitcoinjs-lib@5.0.5)(bolt11@1.4.1)(lnurl-pay@1.0.1)(url@0.11.3): resolution: {integrity: sha512-HCYMB39EO1/2RJ6Dz9ZJkRDCKd5b3IyaajfR2db4i4hQZ6i4z1nuEFT/04ERu27Mlrxrb0qjHfYmAPTRR20xdg==} peerDependencies: '@bitcoinerlab/secp256k1': ^1.0.5 @@ -6112,7 +6133,7 @@ packages: lnurl-pay: ^1.0.1 url: '>=0.11.0' dependencies: - '@bitcoinerlab/secp256k1': 1.0.5 + '@bitcoinerlab/secp256k1': 1.1.1 bitcoinjs-lib: 5.0.5 bolt11: 1.4.1 lnurl-pay: 1.0.1 @@ -7960,7 +7981,7 @@ packages: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 dependencies: graphql: 16.8.1 - tslib: 2.6.2 + tslib: 2.5.3 dev: true /@graphql-tools/optimize@2.0.0(graphql@16.8.1): @@ -8112,7 +8133,7 @@ packages: '@ardatan/relay-compiler': 12.0.0(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) graphql: 16.8.1 - tslib: 2.6.2 + tslib: 2.5.3 transitivePeerDependencies: - encoding - supports-color @@ -8127,7 +8148,7 @@ packages: '@ardatan/relay-compiler': 12.0.0(graphql@16.8.1) '@graphql-tools/utils': 10.0.11(graphql@16.8.1) graphql: 16.8.1 - tslib: 2.6.2 + tslib: 2.5.3 transitivePeerDependencies: - encoding - supports-color @@ -8332,7 +8353,7 @@ packages: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 dependencies: graphql: 16.8.1 - tslib: 2.6.2 + tslib: 2.4.1 /@graphql-tools/utils@9.2.1(graphql@16.8.1): resolution: {integrity: sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==} @@ -9159,10 +9180,6 @@ packages: tar-fs: 2.1.1 dev: true - /@next/env@13.5.6: - resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==} - dev: false - /@next/env@14.0.1: resolution: {integrity: sha512-Ms8ZswqY65/YfcjrlcIwMPD7Rg/dVjdLapMcSHG26W6O67EJDF435ShW4H4LXi1xKO1oRc97tLXUpx8jpLe86A==} dev: false @@ -9193,15 +9210,6 @@ packages: glob: 10.3.10 dev: true - /@next/swc-darwin-arm64@13.5.6: - resolution: {integrity: sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - /@next/swc-darwin-arm64@14.0.1: resolution: {integrity: sha512-JyxnGCS4qT67hdOKQ0CkgFTp+PXub5W1wsGvIq98TNbF3YEIN7iDekYhYsZzc8Ov0pWEsghQt+tANdidITCLaw==} engines: {node: '>= 10'} @@ -9229,15 +9237,6 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@13.5.6: - resolution: {integrity: sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - /@next/swc-darwin-x64@14.0.1: resolution: {integrity: sha512-625Z7bb5AyIzswF9hvfZWa+HTwFZw+Jn3lOBNZB87lUS0iuCYDHqk3ujuHCkiyPtSC0xFBtYDLcrZ11mF/ap3w==} engines: {node: '>= 10'} @@ -9265,15 +9264,6 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@13.5.6: - resolution: {integrity: sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-arm64-gnu@14.0.1: resolution: {integrity: sha512-iVpn3KG3DprFXzVHM09kvb//4CNNXBQ9NB/pTm8LO+vnnnaObnzFdS5KM+w1okwa32xH0g8EvZIhoB3fI3mS1g==} engines: {node: '>= 10'} @@ -9301,15 +9291,6 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@13.5.6: - resolution: {integrity: sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-arm64-musl@14.0.1: resolution: {integrity: sha512-mVsGyMxTLWZXyD5sen6kGOTYVOO67lZjLApIj/JsTEEohDDt1im2nkspzfV5MvhfS7diDw6Rp/xvAQaWZTv1Ww==} engines: {node: '>= 10'} @@ -9337,15 +9318,6 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@13.5.6: - resolution: {integrity: sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-x64-gnu@14.0.1: resolution: {integrity: sha512-wMqf90uDWN001NqCM/auRl3+qVVeKfjJdT9XW+RMIOf+rhUzadmYJu++tp2y+hUbb6GTRhT+VjQzcgg/QTD9NQ==} engines: {node: '>= 10'} @@ -9373,15 +9345,6 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@13.5.6: - resolution: {integrity: sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-x64-musl@14.0.1: resolution: {integrity: sha512-ol1X1e24w4j4QwdeNjfX0f+Nza25n+ymY0T2frTyalVczUmzkVD7QGgPTZMHfR1aLrO69hBs0G3QBYaj22J5GQ==} engines: {node: '>= 10'} @@ -9409,15 +9372,6 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@13.5.6: - resolution: {integrity: sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-arm64-msvc@14.0.1: resolution: {integrity: sha512-WEmTEeWs6yRUEnUlahTgvZteh5RJc4sEjCQIodJlZZ5/VJwVP8p2L7l6VhzQhT4h7KvLx/Ed4UViBdne6zpIsw==} engines: {node: '>= 10'} @@ -9445,15 +9399,6 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@13.5.6: - resolution: {integrity: sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-ia32-msvc@14.0.1: resolution: {integrity: sha512-oFpHphN4ygAgZUKjzga7SoH2VGbEJXZa/KL8bHCAwCjDWle6R1SpiGOdUdA8EJ9YsG1TYWpzY6FTbUA+iAJeww==} engines: {node: '>= 10'} @@ -9481,15 +9426,6 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@13.5.6: - resolution: {integrity: sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-x64-msvc@14.0.1: resolution: {integrity: sha512-FFp3nOJ/5qSpeWT0BZQ+YE1pSMk4IMpkME/1DwKBwhg4mJLB9L+6EXuJi4JEwaJdl5iN+UUlmUD3IsR1kx5fAg==} engines: {node: '>= 10'} @@ -11786,6 +11722,10 @@ packages: resolution: {integrity: sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==} dev: true + /@rushstack/eslint-patch@1.7.2: + resolution: {integrity: sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==} + dev: true + /@scure/base@1.1.3: resolution: {integrity: sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==} dev: false @@ -14799,27 +14739,6 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.19.1(eslint@8.54.0)(typescript@5.2.2): - resolution: {integrity: sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 6.19.1 - '@typescript-eslint/types': 6.19.1 - '@typescript-eslint/typescript-estree': 6.19.1(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.19.1 - debug: 4.3.4(supports-color@5.5.0) - eslint: 8.54.0 - typescript: 5.2.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3): resolution: {integrity: sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -17079,7 +16998,7 @@ packages: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} dependencies: pascal-case: 3.1.2 - tslib: 2.6.2 + tslib: 2.4.1 dev: true /camelcase-css@2.0.1: @@ -17097,6 +17016,7 @@ packages: /caniuse-lite@1.0.30001561: resolution: {integrity: sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==} + dev: true /caniuse-lite@1.0.30001568: resolution: {integrity: sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==} @@ -17107,12 +17027,17 @@ packages: /caniuse-lite@1.0.30001585: resolution: {integrity: sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==} + dev: true + + /caniuse-lite@1.0.30001587: + resolution: {integrity: sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==} + dev: false /capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.4.1 upper-case-first: 2.0.2 dev: true @@ -17217,7 +17142,7 @@ packages: path-case: 3.0.4 sentence-case: 3.0.4 snake-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.4.1 dev: true /char-regex@1.0.2: @@ -17622,7 +17547,7 @@ packages: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.4.1 upper-case: 2.0.2 dev: true @@ -18541,7 +18466,7 @@ packages: dependencies: debug: 4.3.4(supports-color@5.5.0) is-url: 1.2.4 - postcss: 8.4.32 + postcss: 8.4.35 postcss-values-parser: 2.0.1 transitivePeerDependencies: - supports-color @@ -18733,7 +18658,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.4.1 /dotenv-expand@10.0.0: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} @@ -19213,7 +19138,7 @@ packages: - supports-color dev: true - /eslint-config-next@14.0.4(eslint@8.54.0)(typescript@5.2.2): + /eslint-config-next@14.0.4(eslint@8.56.0)(typescript@5.3.3): resolution: {integrity: sha512-9/xbOHEQOmQtqvQ1UsTQZpnA7SlDMBtuKJ//S4JnoyK3oGLhILKXdBgu/UO7lQo/2xOykQULS1qQ6p2+EpHgAQ==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 @@ -19224,22 +19149,22 @@ packages: dependencies: '@next/eslint-plugin-next': 14.0.4 '@rushstack/eslint-patch': 1.5.1 - '@typescript-eslint/parser': 6.19.1(eslint@8.54.0)(typescript@5.2.2) - eslint: 8.54.0 + '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.54.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) - eslint-plugin-jsx-a11y: 6.8.0(eslint@8.54.0) - eslint-plugin-react: 7.33.2(eslint@8.54.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.54.0) - typescript: 5.2.2 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + eslint-plugin-jsx-a11y: 6.8.0(eslint@8.56.0) + eslint-plugin-react: 7.33.2(eslint@8.56.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.56.0) + typescript: 5.3.3 transitivePeerDependencies: - eslint-import-resolver-webpack - supports-color dev: true - /eslint-config-next@14.0.4(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-9/xbOHEQOmQtqvQ1UsTQZpnA7SlDMBtuKJ//S4JnoyK3oGLhILKXdBgu/UO7lQo/2xOykQULS1qQ6p2+EpHgAQ==} + /eslint-config-next@14.1.0(eslint@8.54.0)(typescript@5.2.2): + resolution: {integrity: sha512-SBX2ed7DoRFXC6CQSLc/SbLY9Ut6HxNB2wPTcoIWjUMd7aF7O/SIE7111L8FdZ9TXsNV4pulUDnfthpyPtbFUg==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 typescript: '>=3.3.1' @@ -19247,17 +19172,17 @@ packages: typescript: optional: true dependencies: - '@next/eslint-plugin-next': 14.0.4 - '@rushstack/eslint-patch': 1.5.1 - '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3) - eslint: 8.56.0 + '@next/eslint-plugin-next': 14.1.0 + '@rushstack/eslint-patch': 1.7.2 + '@typescript-eslint/parser': 5.62.0(eslint@8.54.0)(typescript@5.2.2) + eslint: 8.54.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - eslint-plugin-jsx-a11y: 6.8.0(eslint@8.56.0) - eslint-plugin-react: 7.33.2(eslint@8.56.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.56.0) - typescript: 5.3.3 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.54.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) + eslint-plugin-jsx-a11y: 6.8.0(eslint@8.54.0) + eslint-plugin-react: 7.33.2(eslint@8.54.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.54.0) + typescript: 5.2.2 transitivePeerDependencies: - eslint-import-resolver-webpack - supports-color @@ -19316,7 +19241,7 @@ packages: confusing-browser-globals: 1.0.11 eslint: 8.54.0 eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.23.3)(@babel/plugin-transform-react-jsx@7.23.4)(eslint@8.54.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint@8.54.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint@8.54.0)(typescript@5.2.2) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.54.0) eslint-plugin-react: 7.33.2(eslint@8.54.0) @@ -19342,7 +19267,7 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.52.0): + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.54.0): resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -19351,9 +19276,9 @@ packages: dependencies: debug: 4.3.4(supports-color@5.5.0) enhanced-resolve: 5.15.0 - eslint: 8.52.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.52.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint@8.52.0) + eslint: 8.54.0 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) fast-glob: 3.3.2 get-tsconfig: 4.7.2 is-core-module: 2.13.1 @@ -19365,7 +19290,7 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.54.0): + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.52.0): resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -19374,9 +19299,9 @@ packages: dependencies: debug: 4.3.4(supports-color@5.5.0) enhanced-resolve: 5.15.0 - eslint: 8.54.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) + eslint: 8.52.0 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.52.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint@8.52.0) fast-glob: 3.3.2 get-tsconfig: 4.7.2 is-core-module: 2.13.1 @@ -19434,7 +19359,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@8.54.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -19459,6 +19384,7 @@ packages: debug: 3.2.7(supports-color@8.1.1) eslint: 8.54.0 eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.54.0) transitivePeerDependencies: - supports-color dev: true @@ -19522,36 +19448,6 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0): - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - dependencies: - '@typescript-eslint/parser': 6.19.1(eslint@8.54.0)(typescript@5.2.2) - debug: 3.2.7(supports-color@8.1.1) - eslint: 8.54.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.54.0) - transitivePeerDependencies: - - supports-color - dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} @@ -19656,7 +19552,7 @@ packages: string-natural-compare: 3.0.1 dev: true - /eslint-plugin-import@2.29.0(@typescript-eslint/parser@5.62.0)(eslint@8.54.0): + /eslint-plugin-import@2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0): resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==} engines: {node: '>=4'} peerDependencies: @@ -19675,7 +19571,7 @@ packages: doctrine: 2.1.0 eslint: 8.54.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@8.54.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -19726,41 +19622,6 @@ packages: - supports-color dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0): - resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - dependencies: - '@typescript-eslint/parser': 6.19.1(eslint@8.54.0)(typescript@5.2.2) - array-includes: 3.1.7 - array.prototype.findlastindex: 1.2.3 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7(supports-color@8.1.1) - doctrine: 2.1.0 - eslint: 8.54.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) - hasown: 2.0.0 - is-core-module: 2.13.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.7 - object.groupby: 1.0.1 - object.values: 1.1.7 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} @@ -22380,7 +22241,7 @@ packages: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} dependencies: capital-case: 1.0.4 - tslib: 2.6.2 + tslib: 2.4.1 dev: true /help-me@5.0.0: @@ -23076,7 +22937,7 @@ packages: /is-lower-case@2.0.2: resolution: {integrity: sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==} dependencies: - tslib: 2.6.2 + tslib: 2.4.1 dev: true /is-map@2.0.2: @@ -23253,7 +23114,7 @@ packages: /is-upper-case@2.0.2: resolution: {integrity: sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==} dependencies: - tslib: 2.6.2 + tslib: 2.4.1 dev: true /is-url-superb@4.0.0: @@ -24883,13 +24744,13 @@ packages: /lower-case-first@2.0.2: resolution: {integrity: sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==} dependencies: - tslib: 2.6.2 + tslib: 2.4.1 dev: true /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.6.2 + tslib: 2.4.1 /lowercase-keys@1.0.0: resolution: {integrity: sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==} @@ -25704,45 +25565,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /next@13.5.6(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==} - engines: {node: '>=16.14.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - react: ^18.2.0 - react-dom: ^18.2.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - sass: - optional: true - dependencies: - '@next/env': 13.5.6 - '@swc/helpers': 0.5.2 - busboy: 1.6.0 - caniuse-lite: 1.0.30001561 - postcss: 8.4.31 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.1(@babel/core@7.23.9)(react@18.2.0) - watchpack: 2.4.0 - optionalDependencies: - '@next/swc-darwin-arm64': 13.5.6 - '@next/swc-darwin-x64': 13.5.6 - '@next/swc-linux-arm64-gnu': 13.5.6 - '@next/swc-linux-arm64-musl': 13.5.6 - '@next/swc-linux-x64-gnu': 13.5.6 - '@next/swc-linux-x64-musl': 13.5.6 - '@next/swc-win32-arm64-msvc': 13.5.6 - '@next/swc-win32-ia32-msvc': 13.5.6 - '@next/swc-win32-x64-msvc': 13.5.6 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: false - /next@14.0.1(@babel/core@7.23.9)(@opentelemetry/api@1.7.0)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-s4YaLpE4b0gmb3ggtmpmV+wt+lPRuGtANzojMQ2+gmBpgX9w5fTbjsy6dXByBuENsdCX5pukZH/GxdFgO62+pA==} engines: {node: '>=18.17.0'} @@ -25843,7 +25665,7 @@ packages: '@opentelemetry/api': 1.7.0 '@swc/helpers': 0.5.2 busboy: 1.6.0 - caniuse-lite: 1.0.30001585 + caniuse-lite: 1.0.30001587 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.2.0 @@ -25868,7 +25690,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.6.2 + tslib: 2.4.1 /node-abi@3.54.0: resolution: {integrity: sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==} @@ -26473,7 +26295,7 @@ packages: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} dependencies: dot-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.4.1 dev: true /parent-module@1.0.1: @@ -26540,7 +26362,7 @@ packages: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.4.1 dev: true /path-browserify@1.0.1: @@ -26551,7 +26373,7 @@ packages: resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} dependencies: dot-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.4.1 dev: true /path-exists@3.0.0: @@ -27119,6 +26941,15 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postcss@8.4.35: + resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -28338,7 +28169,7 @@ packages: adjust-sourcemap-loader: 4.0.0 convert-source-map: 1.9.0 loader-utils: 2.0.4 - postcss: 8.4.32 + postcss: 8.4.31 source-map: 0.6.1 dev: true @@ -28655,7 +28486,7 @@ packages: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.4.1 upper-case-first: 2.0.2 dev: true @@ -29028,7 +28859,7 @@ packages: /sponge-case@1.0.1: resolution: {integrity: sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==} dependencies: - tslib: 2.6.2 + tslib: 2.4.1 dev: true /sprintf-js@1.0.3: @@ -29536,7 +29367,7 @@ packages: /swap-case@2.0.2: resolution: {integrity: sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==} dependencies: - tslib: 2.6.2 + tslib: 2.4.1 dev: true /swc-loader@0.2.3(@swc/core@1.3.105)(webpack@5.89.0): @@ -29975,7 +29806,7 @@ packages: /title-case@3.0.3: resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} dependencies: - tslib: 2.6.2 + tslib: 2.4.1 dev: true /tmp@0.0.33: @@ -30645,13 +30476,13 @@ packages: /upper-case-first@2.0.2: resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} dependencies: - tslib: 2.6.2 + tslib: 2.4.1 dev: true /upper-case@2.0.2: resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} dependencies: - tslib: 2.6.2 + tslib: 2.4.1 dev: true /uri-js@4.4.1: