From 0db8d6b81ad71928b9796c024b650db48d8fab15 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 19 Sep 2024 14:54:19 -0700 Subject: [PATCH] "see more" payment options --- apps/crepe/package.json | 1 + .../checkout/components/payment-method.tsx | 142 +++++++++------ .../checkout/components/search-palette.tsx | 168 ++++++++++++++++++ apps/crepe/tailwind.config.ts | 2 +- package-lock.json | 92 ++++++---- 5 files changed, 315 insertions(+), 90 deletions(-) create mode 100644 apps/crepe/src/app/checkout/components/search-palette.tsx diff --git a/apps/crepe/package.json b/apps/crepe/package.json index 4cf118da5..84fe4c5cd 100644 --- a/apps/crepe/package.json +++ b/apps/crepe/package.json @@ -9,6 +9,7 @@ "@heroicons/react": "^2.1.5", "@neynar/nodejs-sdk": "^1.12.0", "@rainbow-me/rainbowkit": "^1.1.1", + "@tailwindcss/forms": "^0.5.9", "@tanstack/react-query": "^5.51.11", "@trpc/client": "^11.0.0-next-beta.318", "@trpc/react-query": "^11.0.0-next-beta.318", diff --git a/apps/crepe/src/app/checkout/components/payment-method.tsx b/apps/crepe/src/app/checkout/components/payment-method.tsx index b5fc807eb..a2cad701d 100644 --- a/apps/crepe/src/app/checkout/components/payment-method.tsx +++ b/apps/crepe/src/app/checkout/components/payment-method.tsx @@ -25,6 +25,7 @@ import { usePaymentInfo } from "../payment-info-context"; import { trpc } from "../trpc"; import { Notification } from "./notification"; import RightPane from "./right-pane"; +import { SearchPaletteItem, SearchPaletteWithGroups } from "./search-palette"; import { PaymentMethodsSkeleton } from "./skeletons"; import { formatAmountDecimals, truncateAddress } from "@/src/utils/format"; @@ -129,7 +130,7 @@ export default function PaymentMethods() {
{(ensName || address) && (

- Pay with {ensName || truncateAddress(address!)}: + Pay with {ensName || truncateAddress(address!)}

)} -
- -
{/* TODO: show this notification using a hook */} Promise; }) { + const [showSearchPalette, setShowSearchPalette] = useState(false); + + const visibleOptions = tokenBalances.slice(0, 3); + return ( -
    - {isLoading ? ( - - ) : ( - tokenBalances.map((tokenBalance, index) => ( -
  • - -
  • - )) +
    +
      + {isLoading ? ( + + ) : ( + visibleOptions.map((tokenBalance, index) => ( +
    • + +
    • + )) + )} +
    +
    + +
    + {showSearchPalette && ( + + tokenBalanceToSearchPaletteItem(tokenBalance, handleTransfer) + )} + open={showSearchPalette} + onClose={() => setShowSearchPalette(false)} + /> )} -
+ ); } -function PaymentOption({ +function tokenBalanceToSearchPaletteItem( + tokenBalance: TokenBalance, + handleTransfer: (tokenBalance: TokenBalance) => Promise +): SearchPaletteItem { + return { + filterString: `${tokenBalance.token.symbol} on ${getChainName( + tokenBalance.token.chainId + )}`, + group: getChainName(tokenBalance.token.chainId), + content: , + onClick: () => { + handleTransfer(tokenBalance); + }, + }; +} + +function PaymentOptionButton({ tokenBalance, onClick, }: { tokenBalance: TokenBalance; onClick: (tokenBalance: TokenBalance) => Promise; }) { + const handleClick = async () => { + await onClick(tokenBalance); + }; + + return ( + + ); +} + +function PaymentOption({ tokenBalance }: { tokenBalance: TokenBalance }) { const formattedBalance = formatAmountDecimals( BigInt(tokenBalance.tokenBalance), tokenBalance.token @@ -196,48 +242,38 @@ function PaymentOption({ ); const chainName = getChainName(tokenBalance.token.chainId); - const handleClick = async () => { - await onClick(tokenBalance); - }; - return ( - + ); } diff --git a/apps/crepe/src/app/checkout/components/search-palette.tsx b/apps/crepe/src/app/checkout/components/search-palette.tsx new file mode 100644 index 000000000..9a709cdbf --- /dev/null +++ b/apps/crepe/src/app/checkout/components/search-palette.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { + Combobox, + ComboboxInput, + ComboboxOption, + ComboboxOptions, + Dialog, + DialogBackdrop, + DialogPanel, +} from "@headlessui/react"; +import { MagnifyingGlassIcon } from "@heroicons/react/20/solid"; +import { useState } from "react"; + +export type SearchPaletteItem = { + filterString: string; + group: string; + content: React.ReactNode; + onClick: () => void; +}; + +export function SearchPaletteWithGroups({ + items, + open, + onClose, +}: { + items: SearchPaletteItem[]; + open: boolean; + onClose: () => void; +}) { + const [query, setQuery] = useState(""); + + const filteredItems = + query === "" + ? [] + : items.filter((item) => { + return item.filterString.toLowerCase().includes(query.toLowerCase()); + }); + + const groups = items.reduce>( + (groups, item) => { + return { + ...groups, + [item.group]: [...(groups[item.group] || []), item], + }; + }, + {} + ); + + const filteredGroups = filteredItems.reduce< + Record + >((groups, item) => { + return { + ...groups, + [item.group]: [...(groups[item.group] || []), item], + }; + }, {}); + + return ( + { + onClose(); + setQuery(""); + }} + > + + +
+ + { + if (item) item.onClick(); + }} + > +
+
+ + {/* Query is empty. Show all groups */} + {query === "" && ( + + {Object.entries(groups).map(([group, items]) => ( + + ))} + + )} + + {/* Query is not empty and there are results. Show filtered groups */} + {filteredItems.length > 0 && ( + + {Object.entries(filteredGroups).map(([group, items]) => ( + + ))} + + )} + + {/* Query is not empty and there are no results. Show no results message */} + {query !== "" && filteredItems.length === 0 && ( +
+

+ No results found +

+

+ This address does not have sufficient balance for this search + result. +

+
+ )} +
+
+
+
+ ); +} + +function SearchGroup({ + group, + items, +}: { + group: string; + items: SearchPaletteItem[]; +}) { + return ( +
  • +

    + {group} +

    +
      + {items.map((item, index) => ( + + {item.content} + + ))} +
    +
  • + ); +} diff --git a/apps/crepe/tailwind.config.ts b/apps/crepe/tailwind.config.ts index f30784aa2..c399206ec 100644 --- a/apps/crepe/tailwind.config.ts +++ b/apps/crepe/tailwind.config.ts @@ -32,6 +32,6 @@ const config: Config = { }, }, }, - plugins: [], + plugins: [require("@tailwindcss/forms")], }; export default config; diff --git a/package-lock.json b/package-lock.json index d6500f16c..55af08ee5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "@heroicons/react": "^2.1.5", "@neynar/nodejs-sdk": "^1.12.0", "@rainbow-me/rainbowkit": "^1.1.1", + "@tailwindcss/forms": "^0.5.9", "@tanstack/react-query": "^5.51.11", "@trpc/client": "^11.0.0-next-beta.318", "@trpc/react-query": "^11.0.0-next-beta.318", @@ -691,42 +692,6 @@ "node": ">= 6" } }, - "apps/crepe/node_modules/tailwindcss": { - "version": "3.4.12", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz", - "integrity": "sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.0", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.0", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, "apps/crepe/node_modules/uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", @@ -13766,6 +13731,17 @@ "node": ">=10" } }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.9.tgz", + "integrity": "sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20" + } + }, "node_modules/@tanstack/query-core": { "version": "5.55.4", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.55.4.tgz", @@ -28999,6 +28975,14 @@ "node": ">=4" } }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -34400,6 +34384,42 @@ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, + "node_modules/tailwindcss": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz", + "integrity": "sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",