From 5ccaea7917b191424f5db322bbee3209e14a3f60 Mon Sep 17 00:00:00 2001 From: Bogdan Crisan Date: Thu, 19 Dec 2024 04:11:13 +0100 Subject: [PATCH] [ECO-2587] Add market metadata on market page (#463) Co-authored-by: Matt <90358481+xbtmatt@users.noreply.github.com> --- cfg/cspell-frontend-dictionary.txt | 1 + .../src/components/FormattedNumber.tsx | 5 +- .../frontend/src/components/button/index.tsx | 28 +++- .../components/main-info/BondingProgress.tsx | 115 ++++++++----- .../components/main-info/MainInfo.tsx | 153 +++++++++++++----- .../pages/metadata/MetadataPage.tsx | 13 +- .../svg/icons/BondingCurveArrow.tsx | 19 +++ .../src/components/svg/icons/Planet.tsx | 15 +- .../src/components/svg/icons/Telegram.tsx | 19 +++ .../svg/icons/TelegramOutlineIcon.tsx | 19 +++ src/typescript/frontend/src/utils/emoji.tsx | 9 +- .../contract-apis/market-metadata.ts | 2 +- 12 files changed, 302 insertions(+), 96 deletions(-) create mode 100644 src/typescript/frontend/src/components/svg/icons/BondingCurveArrow.tsx create mode 100644 src/typescript/frontend/src/components/svg/icons/Telegram.tsx create mode 100644 src/typescript/frontend/src/components/svg/icons/TelegramOutlineIcon.tsx diff --git a/cfg/cspell-frontend-dictionary.txt b/cfg/cspell-frontend-dictionary.txt index d105d041e..dcaf19d61 100644 --- a/cfg/cspell-frontend-dictionary.txt +++ b/cfg/cspell-frontend-dictionary.txt @@ -54,3 +54,4 @@ testid ADBEEF bytea nominalize +dexscreener diff --git a/src/typescript/frontend/src/components/FormattedNumber.tsx b/src/typescript/frontend/src/components/FormattedNumber.tsx index d7ff246aa..369bce203 100644 --- a/src/typescript/frontend/src/components/FormattedNumber.tsx +++ b/src/typescript/frontend/src/components/FormattedNumber.tsx @@ -75,7 +75,10 @@ export const FormattedNumber = ({ const num = nominalize ? toNominal(value as bigint) : Number(value); const format = style === "fixed" || Math.abs(num) >= 1 - ? { maximumFractionDigits: decimals } + ? { + maximumFractionDigits: decimals, + minimumFractionDigits: style === "fixed" ? decimals : undefined, + } : { maximumSignificantDigits: decimals }; const formatter = new Intl.NumberFormat("en-US", format); return formatter.format(num); diff --git a/src/typescript/frontend/src/components/button/index.tsx b/src/typescript/frontend/src/components/button/index.tsx index e36dfe26f..665faf521 100644 --- a/src/typescript/frontend/src/components/button/index.tsx +++ b/src/typescript/frontend/src/components/button/index.tsx @@ -56,7 +56,33 @@ const Button = ({ })} {!isScramble ? ( - `{ ${children} }` + + + {"{ "} + + + {children} + + + {" }"} + + ) : ( { const { t } = translationFunction(); - const { theme } = useThemeContext(); const marketEmojis = data.symbolEmojis; const stateEvents = useEventStore((s) => s.getMarket(marketEmojis)?.stateEvents ?? []); @@ -33,51 +30,85 @@ const BondingProgress = ({ data }: MainInfoProps) => { }, [stateEvents]); return ( -
-
-
+
+ {/* + 3.26 is calculated like this: + + The aspect ratio of a bonding curve arrow is 115/30 aka 23/6. + + There are 7 of them. + + So the aspect ratio of the container element is 115/30*7 aka 161/6. + + We know that the rocket emoji's width and height is 175% of the container height. + + We want to add padding to the container to include the half part of the rocket emoji that overflows on the left, in order to properly center the container within its container. + + The padding should be 50% of the rocket's width, but we cannot use the rocket width as a unit in CSS. + + But we know that the rocket width is 175% of the container height. + + But we cannot specify the left padding in height percentage, but only in width percentage. + + But we know that the container's width is 161/6 times the container's height. + + So we can do 100 / (161/6) * 1.75 / 2 which gives us ~3.26. + + Who knew CSS could be this hard... + */} +
+
+ + 15 ? "econiaBlue" : "darkGray"} + /> + 30 ? "econiaBlue" : "darkGray"} + /> + 45 ? "econiaBlue" : "darkGray"} + /> + 60 ? "econiaBlue" : "darkGray"} + /> + 75 ? "econiaBlue" : "darkGray"} + /> + 90 ? "econiaBlue" : "darkGray"} + /> + = 100 ? "econiaBlue" : "darkGray"} + /> +
+
+
+
{t("Bonding progress:")}
- {bondingProgress >= 100 ? ( - e.emoji).join("") }, - }} - > - - - ) : ( -
- -
- )}
); }; diff --git a/src/typescript/frontend/src/components/pages/emojicoin/components/main-info/MainInfo.tsx b/src/typescript/frontend/src/components/pages/emojicoin/components/main-info/MainInfo.tsx index 2fd4d88b4..d53509d9a 100644 --- a/src/typescript/frontend/src/components/pages/emojicoin/components/main-info/MainInfo.tsx +++ b/src/typescript/frontend/src/components/pages/emojicoin/components/main-info/MainInfo.tsx @@ -11,13 +11,43 @@ import { useMatchBreakpoints } from "@hooks/index"; import { Emoji } from "utils/emoji"; import Link from "next/link"; import { toExplorerLink } from "lib/utils/explorer-link"; -import { emoji } from "utils"; -import { motion } from "framer-motion"; -import { truncateAddress } from "@sdk/utils"; import { FormattedNumber } from "components/FormattedNumber"; +import Button from "components/button"; +import { Planet, TwitterOutlineIcon } from "components/svg"; +import TelegramOutlineIcon from "@icons/TelegramOutlineIcon"; +import { motion } from "framer-motion"; +import { MarketProperties } from "@/contract-apis"; +import { useAptos } from "context/wallet-context/AptosContextProvider"; +import type { Colors } from "theme/types"; const statsTextClasses = "uppercase ellipses font-forma text-[24px]"; +const LinkButton = ({ + name, + link, + icon, +}: { + name: string; + link: string | undefined; + icon?: (color: keyof Colors) => React.ReactNode; +}) => { + const button = ( + + ); + return link ? ( + + {button} + + ) : ( + button + ); +}; + const MainInfo = ({ data }: MainInfoProps) => { const { t } = translationFunction(); const { theme } = useThemeContext(); @@ -43,14 +73,67 @@ const MainInfo = ({ data }: MainInfoProps) => { } }, [stateEvents]); - const { isMobile } = useMatchBreakpoints(); + const { isMobile, isTablet } = useMatchBreakpoints(); const explorerLink = toExplorerLink({ linkType: "coin", value: `${data.marketView.metadata.marketAddress}::coin_factory::Emojicoin`, }); - const [copied, setCopied] = useState(false); + const borderStyle = "border-solid border-[1px] border-dark-gray rounded-[3px] p-[1em]"; + + const [marketProperties, setMarketProperties] = useState>(); + + const { aptos } = useAptos(); + + useEffect(() => { + MarketProperties.view({ + aptos, + market: data.marketAddress, + }) + .then((r) => r.vec.at(0) ?? null) + .then((r) => { + if (r) { + const newFields = new Map(); + (r as { data: { key: string; value: string }[] }).data.forEach(({ key, value }) => { + newFields.set(key, value); + }); + setMarketProperties(newFields); + } + }) + .catch((e) => console.error("Could not get market metadata.", e)); + /* eslint-disable react-hooks/exhaustive-deps */ + }, [data.marketAddress]); + + const dexscreenerButton = ; + + const telegramButton = ( + ( + + )} + /> + ); + + const twitterButton = ( + ( + + )} + /> + ); + + const websiteButton = ( + } + /> + ); return (
{ }} >
-
+
-
- - {truncateAddress(data.marketView.metadata.marketAddress)} - - { - navigator.clipboard.writeText(data.marketView.metadata.marketAddress); - if (!copied) { - setCopied(true); - setTimeout(() => setCopied(false), 3000); - } - }} - > - - -
-
+
{t("Market Cap:")}
@@ -141,10 +202,26 @@ const MainInfo = ({ data }: MainInfoProps) => {
- -
- +
+
+
+ { + navigator.clipboard.writeText(data.marketView.metadata.marketAddress); + }} + whileTap={{ scaleX: 0.96, scaleY: 0.98 }} + transition={{ ease: "linear", duration: 0.05 }} + > + + + {dexscreenerButton} + {twitterButton} + {telegramButton} + {websiteButton}
+
diff --git a/src/typescript/frontend/src/components/pages/metadata/MetadataPage.tsx b/src/typescript/frontend/src/components/pages/metadata/MetadataPage.tsx index 773b60728..46491ee73 100644 --- a/src/typescript/frontend/src/components/pages/metadata/MetadataPage.tsx +++ b/src/typescript/frontend/src/components/pages/metadata/MetadataPage.tsx @@ -133,7 +133,7 @@ const MetadataPage = () => { if (pasted) { return; } - MarketProperties.submit({ + MarketProperties.view({ aptos, market: marketAddress, }) @@ -244,15 +244,16 @@ const MetadataPage = () => { if (!isSubmitEnabled) { return; } - const filledFields = fields.entries().filter(([_, value]) => value !== ""); - const builderLambda = () => - SetMarketProperties.builder({ + const filledFields = Array.from(fields.entries().filter(([_, value]) => value !== "")); + const builderLambda = () => { + return SetMarketProperties.builder({ aptosConfig: aptos.config, admin: account!.address, market: marketAddress, - keys: Array.from(filledFields.map(([key, _]) => key)), - values: Array.from(filledFields.map(([_, value]) => value)), + keys: filledFields.map(([key, _]) => key), + values: filledFields.map(([_, value]) => value), }); + }; const res = await submit(builderLambda); if (!res || res.error) { console.error(res); diff --git a/src/typescript/frontend/src/components/svg/icons/BondingCurveArrow.tsx b/src/typescript/frontend/src/components/svg/icons/BondingCurveArrow.tsx new file mode 100644 index 000000000..3dac57e93 --- /dev/null +++ b/src/typescript/frontend/src/components/svg/icons/BondingCurveArrow.tsx @@ -0,0 +1,19 @@ +"use client"; + +import React from "react"; +import Svg from "components/svg/Svg"; +import { type SvgProps } from "../types"; +import { darkColors } from "theme"; + +const Icon: React.FC = ({ color = "darkGray", ...props }) => { + return ( + + + + ); +}; + +export default Icon; diff --git a/src/typescript/frontend/src/components/svg/icons/Planet.tsx b/src/typescript/frontend/src/components/svg/icons/Planet.tsx index b709359d2..3f942b43c 100644 --- a/src/typescript/frontend/src/components/svg/icons/Planet.tsx +++ b/src/typescript/frontend/src/components/svg/icons/Planet.tsx @@ -1,13 +1,18 @@ +"use client"; + import React from "react"; -import { type SVGProps } from "react"; +import type { SvgProps } from "../types"; +import Svg from "../Svg"; +import { useThemeContext } from "context"; -const Planet = (props: SVGProps) => { +const Planet: React.FC = ({ color = "econiaBlue", ...props }) => { + const { theme } = useThemeContext(); return ( - + @@ -15,7 +20,7 @@ const Planet = (props: SVGProps) => { - + ); }; diff --git a/src/typescript/frontend/src/components/svg/icons/Telegram.tsx b/src/typescript/frontend/src/components/svg/icons/Telegram.tsx new file mode 100644 index 000000000..f13678a8e --- /dev/null +++ b/src/typescript/frontend/src/components/svg/icons/Telegram.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import Svg from "components/svg/Svg"; +import { type SvgProps } from "../types"; +import { useThemeContext } from "context/theme-context"; + +const Icon: React.FC = ({ color = "black", ...props }) => { + const { theme } = useThemeContext(); + return ( + + + + + ); +}; + +export default Icon; diff --git a/src/typescript/frontend/src/components/svg/icons/TelegramOutlineIcon.tsx b/src/typescript/frontend/src/components/svg/icons/TelegramOutlineIcon.tsx new file mode 100644 index 000000000..3aa99291b --- /dev/null +++ b/src/typescript/frontend/src/components/svg/icons/TelegramOutlineIcon.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import Svg from "components/svg/Svg"; +import { type SvgProps } from "../types"; +import { useThemeContext } from "context/theme-context"; + +const Icon: React.FC = ({ color = "black", ...props }) => { + const { theme } = useThemeContext(); + return ( + + + + + ); +}; + +export default Icon; diff --git a/src/typescript/frontend/src/utils/emoji.tsx b/src/typescript/frontend/src/utils/emoji.tsx index 29b0ae280..64a91d8b3 100644 --- a/src/typescript/frontend/src/utils/emoji.tsx +++ b/src/typescript/frontend/src/utils/emoji.tsx @@ -10,6 +10,7 @@ declare global { size?: string; native?: string; key?: string; + set?: string; }; } } @@ -17,19 +18,23 @@ declare global { export const Emoji = ({ emojis, + set = undefined, + size = "1em", ...props }: Omit, HTMLSpanElement>, "children"> & { emojis: AnyEmojiData[] | string; + set?: string; + size?: string; }) => { let data: React.ReactNode[] = []; if (typeof emojis === "string") { const emojisInString = getEmojisInString(emojis); data = emojisInString.map((e, i) => ( - + )); } else { data = emojis.map((e, i) => ( - + )); } return {data}; diff --git a/src/typescript/sdk/src/emojicoin_dot_fun/contract-apis/market-metadata.ts b/src/typescript/sdk/src/emojicoin_dot_fun/contract-apis/market-metadata.ts index 3b5a31631..a199c72aa 100644 --- a/src/typescript/sdk/src/emojicoin_dot_fun/contract-apis/market-metadata.ts +++ b/src/typescript/sdk/src/emojicoin_dot_fun/contract-apis/market-metadata.ts @@ -565,7 +565,7 @@ export class MarketProperties extends ViewFunctionPayloadBuilder<[Option