diff --git a/package.json b/package.json index 5a4708f..8b556f3 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,11 @@ }, "dependencies": { "@fortawesome/fontawesome-svg-core": "6.5.1", + "@fortawesome/free-brands-svg-icons": "6.5.1", "@fortawesome/free-solid-svg-icons": "6.5.1", - "@fortawesome/react-fontawesome": "0.2.2", "@multiversx/sdk-core": "14.2.6", - "@multiversx/sdk-dapp": "^5.x", - "@multiversx/sdk-dapp-ui": "^0.x", + "@multiversx/sdk-dapp": "5.1.3", + "@multiversx/sdk-dapp-ui": "0.0.27", "@multiversx/sdk-dapp-utils": "2.0.2", "@solidjs/router": "0.13.6", "axios": "^1.10.0", @@ -26,11 +26,19 @@ "immer": "10.1.1", "moment": "2.29.4", "solid-fa": "0.2.0", - "solid-js": "1.8.11" + "solid-js": "1.8.11", + "vite-plugin-svgr": "^4.5.0" }, "devDependencies": { + "@tailwindcss/cli": "4.0.17", + "@tailwindcss/postcss": "4.1.3", + "@tailwindcss/vite": "4.1.11", + "@testing-library/jest-dom": "^6.4.6", + "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "6.7.0", "@typescript-eslint/parser": "6.7.0", + "@vitejs/plugin-basic-ssl": "^1.1.0", + "autoprefixer": "^10.4.19", "eslint": "8.50.0", "eslint-config-prettier": "9.0.0", "eslint-config-standard": "17.1.0", @@ -40,21 +48,19 @@ "eslint-plugin-node": "11.1.0", "eslint-plugin-prettier": "5.0.0", "eslint-plugin-promise": "6.1.1", - "@testing-library/jest-dom": "^6.4.6", - "@types/jest": "^29.5.13", - "@vitejs/plugin-basic-ssl": "^1.1.0", - "autoprefixer": "^10.4.19", "jest": "^29.7.0", "postcss": "^8.4.39", + "prettier": "3.2.5", + "react": "^19.1.1", + "react-dom": "^19.1.1", "solid-devtools": "^0.30.1", - "tailwindcss": "^3.4.4", + "tailwindcss": "4.0.15", "typescript": "^5.3.3", "vite": "^5.0.11", "vite-plugin-externalize-deps": "^0.8.0", "vite-plugin-node-polyfills": "^0.22.0", "vite-plugin-solid": "^2.10.2", "vite-plugin-solid-svg": "^0.8.1", - "vite-tsconfig-paths": "^4.3.2", - "prettier": "3.2.5" + "vite-tsconfig-paths": "^4.3.2" } } diff --git a/postcss.config.js b/postcss.config.js index 33ad091..8ed13da 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,6 @@ module.exports = { plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} + '@tailwindcss/postcss': {}, + autoprefixer: {} + } +}; diff --git a/public/dark-theme-bg.png b/public/dark-theme-bg.png new file mode 100644 index 0000000..63b49d2 Binary files /dev/null and b/public/dark-theme-bg.png differ diff --git a/public/light-theme-bg.png b/public/light-theme-bg.png new file mode 100644 index 0000000..ed4efe2 Binary files /dev/null and b/public/light-theme-bg.png differ diff --git a/public/vibe-theme-bg.png b/public/vibe-theme-bg.png new file mode 100644 index 0000000..5142029 Binary files /dev/null and b/public/vibe-theme-bg.png differ diff --git a/src/assets/fonts/Satoshi-Bold.woff2 b/src/assets/fonts/Satoshi-Bold.woff2 new file mode 100644 index 0000000..0a8db7a Binary files /dev/null and b/src/assets/fonts/Satoshi-Bold.woff2 differ diff --git a/src/assets/fonts/Satoshi-Medium.woff2 b/src/assets/fonts/Satoshi-Medium.woff2 new file mode 100644 index 0000000..ffd0ac9 Binary files /dev/null and b/src/assets/fonts/Satoshi-Medium.woff2 differ diff --git a/src/assets/fonts/Satoshi-Regular.woff2 b/src/assets/fonts/Satoshi-Regular.woff2 new file mode 100644 index 0000000..81c40ab Binary files /dev/null and b/src/assets/fonts/Satoshi-Regular.woff2 differ diff --git a/src/assets/img/arc-logo.svg b/src/assets/img/arc-logo.svg new file mode 100644 index 0000000..8278277 --- /dev/null +++ b/src/assets/img/arc-logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/img/background.svg b/src/assets/img/background.svg new file mode 100644 index 0000000..76e0a6a --- /dev/null +++ b/src/assets/img/background.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/batch-tx.svg b/src/assets/img/batch-tx.svg new file mode 100644 index 0000000..748e6c6 --- /dev/null +++ b/src/assets/img/batch-tx.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/brave-logo.svg b/src/assets/img/brave-logo.svg new file mode 100644 index 0000000..c325a78 --- /dev/null +++ b/src/assets/img/brave-logo.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/bright-light-icon.svg b/src/assets/img/bright-light-icon.svg new file mode 100644 index 0000000..8107e91 --- /dev/null +++ b/src/assets/img/bright-light-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/chrome-logo.svg b/src/assets/img/chrome-logo.svg new file mode 100644 index 0000000..247b9e6 --- /dev/null +++ b/src/assets/img/chrome-logo.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/circles.svg b/src/assets/img/circles.svg new file mode 100644 index 0000000..4027d04 --- /dev/null +++ b/src/assets/img/circles.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/expand-up-down.svg b/src/assets/img/expand-up-down.svg new file mode 100644 index 0000000..56451d3 --- /dev/null +++ b/src/assets/img/expand-up-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/extension-image.png b/src/assets/img/extension-image.png new file mode 100644 index 0000000..d459da1 Binary files /dev/null and b/src/assets/img/extension-image.png differ diff --git a/src/assets/img/firefox-logo.svg b/src/assets/img/firefox-logo.svg new file mode 100644 index 0000000..e0fac39 --- /dev/null +++ b/src/assets/img/firefox-logo.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/heart copy.svg b/src/assets/img/heart copy.svg new file mode 100644 index 0000000..5811a38 --- /dev/null +++ b/src/assets/img/heart copy.svg @@ -0,0 +1,9 @@ + + + + heart + Created with Sketch. + + + + \ No newline at end of file diff --git a/src/assets/img/ledger-icon.svg b/src/assets/img/ledger-icon.svg new file mode 100644 index 0000000..a2a1d0b --- /dev/null +++ b/src/assets/img/ledger-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/img/metamask-icon.svg b/src/assets/img/metamask-icon.svg new file mode 100644 index 0000000..77b6b3c --- /dev/null +++ b/src/assets/img/metamask-icon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/multiversx-logo copy.svg b/src/assets/img/multiversx-logo copy.svg new file mode 100644 index 0000000..8ae687d --- /dev/null +++ b/src/assets/img/multiversx-logo copy.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/img/passkey-icon.svg b/src/assets/img/passkey-icon.svg new file mode 100644 index 0000000..2262c20 --- /dev/null +++ b/src/assets/img/passkey-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/img/ping-pong-abi.svg b/src/assets/img/ping-pong-abi.svg new file mode 100644 index 0000000..68fe13d --- /dev/null +++ b/src/assets/img/ping-pong-abi.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/ping-pong-backend.svg b/src/assets/img/ping-pong-backend.svg new file mode 100644 index 0000000..d2acc65 --- /dev/null +++ b/src/assets/img/ping-pong-backend.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/teal-lab-icon.svg b/src/assets/img/teal-lab-icon.svg new file mode 100644 index 0000000..7fe0a03 --- /dev/null +++ b/src/assets/img/teal-lab-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/template-logo.svg b/src/assets/img/template-logo.svg new file mode 100644 index 0000000..c73a330 --- /dev/null +++ b/src/assets/img/template-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/vibe-mode-icon.svg b/src/assets/img/vibe-mode-icon.svg new file mode 100644 index 0000000..17f2c6a --- /dev/null +++ b/src/assets/img/vibe-mode-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/wallet-brave-logo.svg b/src/assets/img/wallet-brave-logo.svg new file mode 100644 index 0000000..222b8cd --- /dev/null +++ b/src/assets/img/wallet-brave-logo.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/wallet-chrome-logo.svg b/src/assets/img/wallet-chrome-logo.svg new file mode 100644 index 0000000..47e432d --- /dev/null +++ b/src/assets/img/wallet-chrome-logo.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/wallet-firefox-logo.svg b/src/assets/img/wallet-firefox-logo.svg new file mode 100644 index 0000000..8819cd5 --- /dev/null +++ b/src/assets/img/wallet-firefox-logo.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/web-wallet-icon.svg b/src/assets/img/web-wallet-icon.svg new file mode 100644 index 0000000..4abf0c4 --- /dev/null +++ b/src/assets/img/web-wallet-icon.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/x-logo.svg b/src/assets/img/x-logo.svg new file mode 100644 index 0000000..79f7141 --- /dev/null +++ b/src/assets/img/x-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/x.svg b/src/assets/img/x.svg new file mode 100644 index 0000000..77aeac4 --- /dev/null +++ b/src/assets/img/x.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/xportal-icon.svg b/src/assets/img/xportal-icon.svg new file mode 100644 index 0000000..fdea7ff --- /dev/null +++ b/src/assets/img/xportal-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/Button.tsx b/src/components/Button.tsx deleted file mode 100644 index 31a7349..0000000 --- a/src/components/Button.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { IPropsWithChildren, IPropsWithClass } from 'types'; - -interface ButtonType extends IPropsWithClass, IPropsWithChildren { - onClick?: (e: MouseEvent) => void; - disabled?: boolean; - dataCy?: string; - id?: string; - type?: 'button' | 'submit' | 'reset'; -} - -export const Button = ({ - children, - onClick, - disabled = false, - type = 'button', - id, - class: - className = 'flex items-center justify-center rounded-lg px-3 py-2 text-center hover:no-underline my-0 bg-blue-600 text-white hover:bg-blue-700 mr-0 disabled:bg-gray-200 disabled:text-black disabled:cursor-not-allowed', - ...otherProps -}: ButtonType) => { - return ( - - ); -}; diff --git a/src/components/Card.tsx b/src/components/Card.tsx deleted file mode 100644 index 2775c66..0000000 --- a/src/components/Card.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; -import Fa from 'solid-fa'; -import { IPropsWithChildren, IPropsWithClass } from 'types'; - -interface CardType extends IPropsWithChildren, IPropsWithClass { - title: string; - description?: string; - reference: string; - anchor?: string; -} - -export const Card = (props: CardType) => { - const { title, children, description, reference, anchor } = props; - - return ( -
-

- {title} - -

- {description &&

{description}

} - {children} -
- ); -}; diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx new file mode 100644 index 0000000..a4289ef --- /dev/null +++ b/src/components/Card/Card.tsx @@ -0,0 +1,40 @@ +import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; +import Fa from 'solid-fa'; +import { IPropsWithChildren, IPropsWithClass } from 'types'; + +// prettier-ignore +const styles = { + cardContainer: 'card-container flex flex-col gap-4 flex-1 rounded-xl bg-primary transition-all duration-200 ease-out p-6 lg:p-10 justify-center border border-secondary', + cardTitle: 'card-title flex justify-between items-center text-2xl font-medium group text-primary transition-all duration-200 ease-out', + cardRef: 'card-ref text-link hover:text-primary transition-all duration-200 ease-out flex items-center', + cardRefIcon: 'max-w-3.5 max-h-3.5', + cardDescription: 'card-description text-secondary transition-all duration-200 ease-out mb-6 text-lg font-medium' +} satisfies Record; + +interface CardType extends IPropsWithChildren, IPropsWithClass { + title: string; + description?: string; + reference: string; + anchor?: string; +} + +export const Card = ({ + title, + children, + description, + reference, + anchor, + 'data-testid': dataTestId +}: CardType) => ( +
+

+ {title} + + + +

+ + {description &&

{description}

} + {children} +
+); diff --git a/src/components/Card/index.ts b/src/components/Card/index.ts new file mode 100644 index 0000000..ca0b060 --- /dev/null +++ b/src/components/Card/index.ts @@ -0,0 +1 @@ +export * from './Card'; diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx new file mode 100644 index 0000000..a756136 --- /dev/null +++ b/src/components/Footer/Footer.tsx @@ -0,0 +1,42 @@ +import { faHeart } from '@fortawesome/free-solid-svg-icons'; +import moment from 'moment'; + +import Fa from 'solid-fa'; +import { getState, networkSelector } from 'lib'; +import { version } from '../../../package.json'; + +// prettier-ignore +const styles = { + footer: 'footer mx-auto w-full max-w-prose py-4 text-center', + footerContainer: 'footer-container flex flex-col gap-2 font-medium items-center justify-center text-sm text-[#989898]', + footerDescription: 'footer-description flex items-center justify-center gap-1 text-sm text-neutral-500 gap-1', + footerDescriptionNetwork: 'footer-description-network capitalize', + footerHeartIcon: 'text-red-500' +} satisfies Record; + +export const Footer = () => { + const network = networkSelector(getState()); + const currentYear = moment().year(); + + return ( +
+
+
+ + {network.id} Build + + + {version} +
+ +
+ Made with + + + + by the MultiversX team, {currentYear} +
+
+
+ ); +}; diff --git a/src/components/Footer/index.ts b/src/components/Footer/index.ts new file mode 100644 index 0000000..ddcc5a9 --- /dev/null +++ b/src/components/Footer/index.ts @@ -0,0 +1 @@ +export * from './Footer'; diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx new file mode 100644 index 0000000..a170ff4 --- /dev/null +++ b/src/components/Header/Header.tsx @@ -0,0 +1,190 @@ +import { faGithub } from '@fortawesome/free-brands-svg-icons'; +import { + faBell, + faCreditCard, + faPowerOff, + IconDefinition +} from '@fortawesome/free-solid-svg-icons'; + +import { useNavigate } from '@solidjs/router'; +import Fa from 'solid-fa'; +import { Logo, Tooltip } from 'components'; +import { GITHUB_REPO_URL } from 'config'; +import { + ACCOUNTS_ENDPOINT, + getAccountProvider, + MvxDataWithExplorerLink, + NotificationsFeedManager, + getAccount, + getIsLoggedIn, + networkSelector, + UnlockPanelManager, + getState, + MvxButton +} from 'lib'; +import { RouteNamesEnum } from 'localConstants'; + +import { ThemeTooltip } from './components'; + +// prettier-ignore +const styles = { + header: 'header flex items-center justify-between px-4 h-16 md:h-20 md:px-10', + headerLogo: 'header-logo cursor-pointer transition-opacity duration-200 hover:opacity-75', + headerNavigation: 'header-navigation flex items-center gap-2 lg:gap-4', + headerNavigationButtons: 'header-navigation-buttons flex gap-2 lg:gap-4', + headerNavigationButton: 'header-navigation-button flex justify-center items-center w-8 lg:w-10 h-8 lg:h-10 rounded-xl cursor-pointer relative after:rounded-xl after:absolute after:bg-btn-variant hover:after:bg-btn-hover after:transition-all after:duration-200 after:ease-out after:left-0 after:right-0 after:top-0 after:bottom-0 after:pointer-events-none hover:after:opacity-100', + headerNavigationButtonIcon: 'header-navigation-button-icon flex justify-center relative text-xs lg:text-base z-1 items-center text-tertiary', + headerNavigationTooltip: 'header-navigation-tooltip p-1 leading-none whitespace-nowrap text-tertiary', + headerNavigationNetwork: 'header-navigation-network h-8 border border-secondary rounded-xl lg:h-10 relative w-22 flex items-center justify-center leading-none capitalize text-tertiary before:absolute before:rounded-full before:w-2 before:lg:w-2.5 before:h-2 before:lg:h-2.5 before:bg-btn-primary before:z-2 before:-top-0.25 before:lg:-top-0.5 before:-left-0.25 before:lg:-left-0.5 after:absolute after:bg-primary after:rounded-lg after:opacity-40 after:left-0 after:right-0 after:top-0 after:bottom-0 after:pointer-events-none', + headerNavigationNetworkLabel: 'header-navigation-network-label relative z-1', + headerNavigationConnect: 'header-navigation-connect h-8 lg:h-10', + headerNavigationAddress: 'header-navigation-address h-8 lg:h-10 w-8 lg:w-full justify-center text-xs rounded-xl lg:text-base lg:pr-4 lg:pl-5 max-w-100 flex relative lg:border lg:border-secondary lg:rounded-full items-center gap-3 after:absolute after:bg-btn-tertiary after:rounded-xl lg:after:rounded-full after:opacity-40 after:left-0 after:right-0 after:top-0 after:bottom-0 after:pointer-events-none', + headerNavigationAddressWallet: 'header-navigation-address-wallet relative z-1 text-accent hidden lg:flex!', + headerNavigationAddressExplorer: 'header-navigation-address-explorer min-w-0 relative z-1 hidden lg:block!', + headerNavigationAddressLogout: 'header-navigation-address-logout text-tertiary cursor-pointer relative z-1 transition-all duration-200 ease-out hover:text-accent', + trimmedAddress: 'text-primary' +} satisfies Record; + +interface HeaderBrowseButtonType { + handleClick: (event: MouseEvent) => void; + icon: IconDefinition; + isVisible: boolean; + label: string; +} + +export const Header = () => { + const network = networkSelector(getState()); + const address = getAccount()?.address; + + const isLoggedIn = getIsLoggedIn(); + const provider = getAccountProvider(); + const navigate = useNavigate(); + const unlockPanelManager = UnlockPanelManager.init({ + loginHandler: () => { + navigate(RouteNamesEnum.dashboard); + } + }); + + const handleOpenUnlockPanel = () => { + unlockPanelManager.openUnlockPanel(); + }; + + const handleLogout = async (event: MouseEvent) => { + event.preventDefault(); + await provider.logout(); + navigate(RouteNamesEnum.home); + }; + + const handleGitHubBrowsing = (event: MouseEvent) => { + event.preventDefault(); + window.open(GITHUB_REPO_URL); + }; + + const handleNotificationsBrowsing = (event: MouseEvent) => { + event.preventDefault(); + NotificationsFeedManager.getInstance().openNotificationsFeed(); + }; + + const headerBrowseButtons: HeaderBrowseButtonType[] = [ + { + label: 'GitHub', + handleClick: handleGitHubBrowsing, + icon: faGithub as IconDefinition, + isVisible: true + }, + { + label: 'Notifications', + handleClick: handleNotificationsBrowsing, + icon: faBell, + isVisible: isLoggedIn + } + ]; + + const handleLogoClick = (event: MouseEvent) => { + event.preventDefault(); + navigate(isLoggedIn ? RouteNamesEnum.dashboard : RouteNamesEnum.home); + }; + + return ( +
+
+ +
+ + +
+ ); +}; diff --git a/src/components/Header/components/ThemeTooltip/ThemeTooltip.tsx b/src/components/Header/components/ThemeTooltip/ThemeTooltip.tsx new file mode 100644 index 0000000..28f0daf --- /dev/null +++ b/src/components/Header/components/ThemeTooltip/ThemeTooltip.tsx @@ -0,0 +1,140 @@ +import { + faArrowRightLong, + faChevronDown +} from '@fortawesome/free-solid-svg-icons'; +import classNames from 'classnames'; + +import Fa from 'solid-fa'; +import { createMemo, createSignal, onMount } from 'solid-js'; + +import { Tooltip } from 'components'; + +import { ThemeTooltipDots } from './components'; + +interface ThemeTooltipOptionType { + label: string; + identifier: string; + dotColors: string[]; +} + +// prettier-ignore +const styles = { + themeTooltip: 'theme-tooltip', + themeTooltipTrigger: 'theme-tooltip-trigger flex h-8 lg:h-10 cursor-pointer gap-1 lg:gap-2 items-center justify-center w-12 min-w-12 max-w-12 lg:min-w-16 lg:max-w-16 lg:w-16 relative after:absolute after:bg-btn-variant after:left-0 after:right-0 after:top-0 after:bottom-0 after:pointer-events-none after:rounded-xl after:duration-200 after:ease-out after:transition-all hover:after:opacity-100', + themeTooltipTriggerDots: 'theme-tooltip-trigger-dots relative z-1', + themeTooltipTriggerIcon: 'theme-tooltip-trigger-icon transition-all text-xs relative z-1 duration-200 ease-out text-tertiary', + themeTooltipTriggerIconRotated: 'rotate-180', + themeTooltipOptions: 'theme-tooltip-options flex-col gap-1 flex', + themeTooltipOption: 'theme-tooltip-option w-60 flex p-3 flex group text-primary font-normal gap-3 items-center transition-all duration-200 ease-out cursor-pointer relative after:transition-all after:duration-200 after:ease-out after:absolute after:opacity-0 hover:after:opacity-40 after:left-0 after:right-0 after:bottom-0 after:top-0 after:bg-accent after:pointer-events-none after:rounded-lg', + themeTooltipOptionActive: 'after:bg-accent after:opacity-100 !text-accent !font-medium transition-all duration-200 ease-out', + themeTooltipOptionDots: 'theme-tooltip-option-dots flex w-5 h-5 justify-between relative z-1', + themeTooltipOptionLabel: 'theme-tooltip-option-label leading-none relative z-1 text-base', + themeTooltipOptionArrow: 'theme-tooltip-option-arrow ml-auto duration-200 transition-all ease-out opacity-0 text-link group-hover:opacity-100' +} satisfies Record; + +export const ThemeTooltip = () => { + const [rootTheme, setRootTheme] = createSignal( + document.documentElement.getAttribute('data-mvx-theme') + ); + + const themeOptions: ThemeTooltipOptionType[] = [ + { + label: 'TealLab', + identifier: 'mvx:dark-theme', + dotColors: ['#23F7DD', '#262626', '#B6B3AF', '#FFFFFF'] + }, + { + label: 'VibeMode', + identifier: 'mvx:vibe-theme', + dotColors: ['#471150', '#5A2A62', '#D200FA', '#FFFFFF'] + }, + { + label: 'BrightLight', + identifier: 'mvx:light-theme', + dotColors: ['#000000', '#A5A5A5', '#E2DEDC', '#F3EFED'] + } + ]; + + const activeTheme = createMemo(() => + themeOptions.find((themeOption) => themeOption.identifier === rootTheme()) + ); + + const handleThemeSwitch = + (themeOption: ThemeTooltipOptionType) => (event: MouseEvent) => { + event.preventDefault(); + setRootTheme(themeOption.identifier); + + document.documentElement.setAttribute( + 'data-mvx-theme', + themeOption.identifier + ); + }; + + onMount(() => { + const observer = new MutationObserver(() => { + const theme = document.documentElement.getAttribute('data-mvx-theme'); + setRootTheme(theme); + }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-mvx-theme'] + }); + + return () => observer.disconnect(); + }); + + return ( + { + const currentTheme = activeTheme(); + + return ( +
+ + + +
+ ); + }} + > +
+ {themeOptions.map((themeOption) => ( +
+ + +
+ {themeOption.label} +
+ + {themeOption.identifier !== activeTheme()?.identifier && ( + + )} +
+ ))} +
+
+ ); +}; diff --git a/src/components/Header/components/ThemeTooltip/components/ThemeTooltipDots/ThemeTooltipDots.tsx b/src/components/Header/components/ThemeTooltip/components/ThemeTooltipDots/ThemeTooltipDots.tsx new file mode 100644 index 0000000..9406acb --- /dev/null +++ b/src/components/Header/components/ThemeTooltip/components/ThemeTooltipDots/ThemeTooltipDots.tsx @@ -0,0 +1,27 @@ +import classNames from 'classnames'; + +import { IPropsWithClass } from 'types'; + +// prettier-ignore +const styles = { + themeTooltipDots: 'theme-tooltip-dots flex w-4 h-4 lg:w-5 gap-1 min-w-4 lg:min-w-5 lg:h-5 justify-between flex-wrap', + themeTooltipDot: 'theme-tooltip-dot w-1.5 h-1.5 lg:w-2 lg:h-2 lg:min-w-2 lg:min-h-2 min-w-1.5 lg:min-w-2 rounded-full transition-all duration-200 ease-out' +} satisfies Record; + +interface ThemeTooltipDotsPropsType extends IPropsWithClass { + dotColors: string[]; +} + +export const ThemeTooltipDots = ({ + dotColors, + class: className +}: ThemeTooltipDotsPropsType) => ( +
+ {dotColors.map((dotColor) => ( +
+ ))} +
+); diff --git a/src/components/Header/components/ThemeTooltip/components/ThemeTooltipDots/index.ts b/src/components/Header/components/ThemeTooltip/components/ThemeTooltipDots/index.ts new file mode 100644 index 0000000..a0c78a4 --- /dev/null +++ b/src/components/Header/components/ThemeTooltip/components/ThemeTooltipDots/index.ts @@ -0,0 +1 @@ +export * from './ThemeTooltipDots'; diff --git a/src/components/Header/components/ThemeTooltip/components/index.ts b/src/components/Header/components/ThemeTooltip/components/index.ts new file mode 100644 index 0000000..a0c78a4 --- /dev/null +++ b/src/components/Header/components/ThemeTooltip/components/index.ts @@ -0,0 +1 @@ +export * from './ThemeTooltipDots'; diff --git a/src/components/Header/components/ThemeTooltip/index.ts b/src/components/Header/components/ThemeTooltip/index.ts new file mode 100644 index 0000000..3deaf1f --- /dev/null +++ b/src/components/Header/components/ThemeTooltip/index.ts @@ -0,0 +1 @@ +export * from './ThemeTooltip'; diff --git a/src/components/Header/components/index.ts b/src/components/Header/components/index.ts new file mode 100644 index 0000000..3deaf1f --- /dev/null +++ b/src/components/Header/components/index.ts @@ -0,0 +1 @@ +export * from './ThemeTooltip'; diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts new file mode 100644 index 0000000..266dec8 --- /dev/null +++ b/src/components/Header/index.ts @@ -0,0 +1 @@ +export * from './Header'; diff --git a/src/components/Label.tsx b/src/components/Label.tsx deleted file mode 100644 index fcd5618..0000000 --- a/src/components/Label.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { IPropsWithChildren } from 'types'; - -export const Label = ({ children }: IPropsWithChildren) => { - return ; -}; diff --git a/src/components/Label/Label.tsx b/src/components/Label/Label.tsx new file mode 100644 index 0000000..4e8555d --- /dev/null +++ b/src/components/Label/Label.tsx @@ -0,0 +1,10 @@ +import { IPropsWithChildren } from 'types'; + +// prettier-ignore +const styles = { + labelContainer: 'label-container text-secondary transition-all duration-200 ease-out text-sm font-normal' +} satisfies Record; + +export const Label = ({ children }: IPropsWithChildren) => ( + +); diff --git a/src/components/Label/index.ts b/src/components/Label/index.ts new file mode 100644 index 0000000..ca58c61 --- /dev/null +++ b/src/components/Label/index.ts @@ -0,0 +1 @@ +export * from './Label'; diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 99f2e0a..5ddf786 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -1,16 +1,19 @@ -import type { Component } from 'solid-js'; +import { Component } from 'solid-js'; +import { Footer, Header } from 'components'; import { IPropsWithChildren } from 'types'; -import { Footer } from './components/Footer'; -import { Header } from './components/Header'; -export const Layout: Component = ({ children }) => { - return ( -
-
-
- {children} -
-
-
- ); -}; +// prettier-ignore +const styles = { + layoutContainer: 'layout-container flex min-h-screen flex-col bg-accent transition-all duration-200 ease-out', + mainContainer: 'main-container flex flex-grow items-stretch justify-center' +} satisfies Record; + +export const Layout: Component = ({ children }) => ( +
+
+ +
{children}
+ +
+
+); diff --git a/src/components/Layout/components/ConnectButton.tsx b/src/components/Layout/components/ConnectButton.tsx deleted file mode 100644 index 2181e81..0000000 --- a/src/components/Layout/components/ConnectButton.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useNavigate } from '@solidjs/router'; -import { Button } from 'components'; -import { UnlockPanelManager } from 'lib'; -import { RouteNamesEnum } from 'localConstants'; - -export const ConnectButton = () => { - const navigate = useNavigate(); - const unlockPanelManager = UnlockPanelManager.init({ - loginHandler: () => { - navigate(RouteNamesEnum.dashboard); - } - }); - - const handleOpenUnlockPanel = () => { - unlockPanelManager.openUnlockPanel(); - }; - - return ; -}; diff --git a/src/components/Layout/components/Footer.tsx b/src/components/Layout/components/Footer.tsx deleted file mode 100644 index f791337..0000000 --- a/src/components/Layout/components/Footer.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import HeartIcon from 'assets/img/heart.svg?component-solid'; - -export const Footer = () => { - return ( - - ); -}; diff --git a/src/components/Layout/components/GitHubButton.tsx b/src/components/Layout/components/GitHubButton.tsx deleted file mode 100644 index 869a7fc..0000000 --- a/src/components/Layout/components/GitHubButton.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { GITHUB_REPO_URL } from 'config'; - -export const GitHubButton = () => { - return ( - - - - - - ); -}; diff --git a/src/components/Layout/components/Header.tsx b/src/components/Layout/components/Header.tsx deleted file mode 100644 index 1b2af33..0000000 --- a/src/components/Layout/components/Header.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useNavigate } from '@solidjs/router'; -import MultiversXLogo from 'assets/img/multiversx-logo.svg?component-solid'; -import { Button } from 'components/Button'; -import { MxLink } from 'components/MxLink'; -import { environment } from 'config'; -import { getAccountProvider, getIsLoggedIn } from 'lib'; -import { RouteNamesEnum } from 'localConstants'; -import { ConnectButton } from './ConnectButton'; -import { GitHubButton } from './GitHubButton'; -import { NotificationsButton } from './NotificationsButton'; - -export const Header = () => { - const isLoggedIn = getIsLoggedIn(); - const navigate = useNavigate(); - const provider = getAccountProvider(); - - const handleLogout = async () => { - await provider.logout(); - navigate(RouteNamesEnum.home); - }; - - return ( -
- - - - - -
- ); -}; diff --git a/src/components/Layout/components/NotificationsButton.tsx b/src/components/Layout/components/NotificationsButton.tsx deleted file mode 100644 index 8a15126..0000000 --- a/src/components/Layout/components/NotificationsButton.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { faBell } from '@fortawesome/free-solid-svg-icons'; -import Fa from 'solid-fa'; -import { Button } from 'components'; -import { NotificationsFeedManager } from 'lib'; - -export const NotificationsButton = () => { - const handleOpenNotificationsFeed = () => { - NotificationsFeedManager.getInstance().openNotificationsFeed(); - }; - - return ( - - ); -}; diff --git a/src/components/Loader.tsx b/src/components/Loader/Loader.tsx similarity index 79% rename from src/components/Loader.tsx rename to src/components/Loader/Loader.tsx index 6d2529e..67cf9fe 100644 --- a/src/components/Loader.tsx +++ b/src/components/Loader/Loader.tsx @@ -5,7 +5,7 @@ import { Component } from 'solid-js'; export const Loader: Component = () => { return (
- +
); }; diff --git a/src/components/Loader/index.ts b/src/components/Loader/index.ts new file mode 100644 index 0000000..d5ce981 --- /dev/null +++ b/src/components/Loader/index.ts @@ -0,0 +1 @@ +export * from './Loader'; diff --git a/src/components/Logo/Logo.tsx b/src/components/Logo/Logo.tsx new file mode 100644 index 0000000..9fb5ae1 --- /dev/null +++ b/src/components/Logo/Logo.tsx @@ -0,0 +1,32 @@ +import classNames from 'classnames'; + +// prettier-ignore +const styles = { + logo: 'logo flex items-center justify-center gap-3', + logoIcon: 'logo-icon relative', + logoIconEmpty: 'logo-icon-empty w-4 h-4 bg-accent border-2 border-logo z-1 relative transition-all duration-200 ease-out', + logoIconFilled: 'logo-icon-filled w-4 h-4 bg-logo-primary absolute left-0.75 bottom-0.75 transition-all duration-200 ease-out', + logoText: 'logo-text text-xl lg:text-2xl font-medium flex text-primary transition-all duration-200 ease-out relative -top-0.5 leading-none', + logoTextHidden: 'logo-text-hidden hidden lg:!flex' +} satisfies Record; + +interface LogoPropsType { + hideTextOnMobile?: boolean; +} + +export const Logo = ({ hideTextOnMobile }: LogoPropsType) => ( +
+
+
+
+
+ +
+ dApp Template +
+
+); diff --git a/src/components/Logo/index.ts b/src/components/Logo/index.ts new file mode 100644 index 0000000..d97c695 --- /dev/null +++ b/src/components/Logo/index.ts @@ -0,0 +1 @@ +export * from './Logo'; diff --git a/src/components/MissingNativeAuthError/MissingNativeAuthError.tsx b/src/components/MissingNativeAuthError/MissingNativeAuthError.tsx new file mode 100644 index 0000000..520838f --- /dev/null +++ b/src/components/MissingNativeAuthError/MissingNativeAuthError.tsx @@ -0,0 +1,22 @@ +import { OutputContainer } from '../OutputContainer'; + +// prettier-ignore +const styles = { + errorContainer: 'error-container flex items-center gap-1', + emphasizedText: 'emphasized-text text-red-500 font-bold', + nativeAuthText: 'native-auth-text font-bold italic leading-none text-primary transition-all duration-200 ease-out' +} satisfies Record; + +export const MissingNativeAuthError = () => ( + +
+

+ Information could + NOT + be displayed because + nativeAuth + is not active +

+
+
+); diff --git a/src/components/MissingNativeAuthError/index.ts b/src/components/MissingNativeAuthError/index.ts new file mode 100644 index 0000000..b91f784 --- /dev/null +++ b/src/components/MissingNativeAuthError/index.ts @@ -0,0 +1 @@ +export * from './MissingNativeAuthError'; diff --git a/src/components/MxLink.tsx b/src/components/MxLink.tsx deleted file mode 100644 index d7163d4..0000000 --- a/src/components/MxLink.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { A } from '@solidjs/router'; -import { IPropsWithChildren, IPropsWithClass } from 'types'; - -interface MxLinkPropsType extends IPropsWithChildren, IPropsWithClass { - to: string; -} - -export const MxLink = ({ - to, - children, - class: - className = 'inline-block rounded-lg px-3 py-2 text-center hover:no-underline my-0 bg-blue-600 text-white hover:bg-blue-700 ml-2 mr-0' -}: MxLinkPropsType) => { - return ( - - {children} - - ); -}; diff --git a/src/components/OutputContainer/OutputContainer.tsx b/src/components/OutputContainer/OutputContainer.tsx index cdfe1b2..81956e7 100644 --- a/src/components/OutputContainer/OutputContainer.tsx +++ b/src/components/OutputContainer/OutputContainer.tsx @@ -1,24 +1,28 @@ import classNames from 'classnames'; -import { Loader } from 'components/Loader'; + +import { Loader } from 'components'; import { IPropsWithChildren, IPropsWithClass } from 'types'; +// prettier-ignore +const styles = { + outputContainer: 'output-container text-sm text-primary font-normal bg-secondary transition-all duration-300 rounded-xl' +} satisfies Record; + type OutputContainerPropsType = IPropsWithChildren & IPropsWithClass & { isLoading?: boolean; }; -export const OutputContainer = (props: OutputContainerPropsType) => { - const { children, isLoading = false, class: className = 'p-4' } = props; - - return ( -
- {isLoading ? : children} -
- ); -}; +export const OutputContainer = ({ + children, + isLoading = false, + class: className = 'p-4', + 'data-testid': dataTestId +}: OutputContainerPropsType) => ( +
+ {isLoading ? : children} +
+); diff --git a/src/components/OutputContainer/components/PingPongOutput.tsx b/src/components/OutputContainer/components/PingPongOutput.tsx index 9f9471c..5e20a77 100644 --- a/src/components/OutputContainer/components/PingPongOutput.tsx +++ b/src/components/OutputContainer/components/PingPongOutput.tsx @@ -1,12 +1,27 @@ -import { ContractAddress } from 'components/ContractAddress'; -import { Label } from 'components/Label'; -import { SignedTransactionType } from 'lib'; +import { Label } from 'components'; +import { contractAddress } from 'config'; +import { useStore } from 'hooks'; +import { + ACCOUNTS_ENDPOINT, + getExplorerLink, + MvxDataWithExplorerLink, + networkSelector, + SignedTransactionType +} from 'lib'; + import { TransactionsOutput } from './TransactionsOutput'; +// prettier-ignore +const styles = { + pingPongAddressContainer: 'ping-pong-address-container flex justify-between mb-4', + pingPongButtons: 'ping-pong-buttons flex gap-3', + timeRemaining: 'time-remaining text-red-600' +} satisfies Record; + type PingPongOutputType = { timeRemaining: string; pongAllowed: boolean; - transactions?: SignedTransactionType[]; + transactions?: SignedTransactionType[] | null; }; export const PingPongOutput = ({ @@ -14,18 +29,34 @@ export const PingPongOutput = ({ pongAllowed, transactions }: PingPongOutputType) => { - if (!transactions) { + const store = useStore(); + const network = networkSelector(store()); + + if (!transactions || transactions?.length === 0) { return null; } + const explorerAddress = network.explorerAddress; + const explorerLink = getExplorerLink({ + to: `/${ACCOUNTS_ENDPOINT}/${contractAddress}`, + explorerAddress + }); + return ( <> - + + + {!pongAllowed && (

- {timeRemaining} until able to pong + {timeRemaining} until able + to pong

)} diff --git a/src/components/OutputContainer/components/TransactionOutput.tsx b/src/components/OutputContainer/components/TransactionOutput.tsx index 2596995..e7aa937 100644 --- a/src/components/OutputContainer/components/TransactionOutput.tsx +++ b/src/components/OutputContainer/components/TransactionOutput.tsx @@ -1,50 +1,90 @@ -import { Label } from 'components/Label'; -import { ExplorerLink, FormatAmount } from 'lib'; +import { Label } from 'components'; +import { useStore } from 'hooks'; import { ACCOUNTS_ENDPOINT, + DECIMALS, + DIGITS, + FormatAmount, + FormatAmountController, + getAccount, + getExplorerLink, getState, + MvxDataWithExplorerLink, networkSelector, SignedTransactionType, TRANSACTIONS_ENDPOINT } from 'lib'; +// prettier-ignore +const styles = { + transactionContainer: 'transaction-container flex flex-col', + transactionElementContainer: 'transaction-elem-container flex gap-2', + transactionElement: 'transaction-elem flex justify-between w-full', + buttons: 'buttons flex gap-3', + dataContainer: 'data-container whitespace-nowrap' +} satisfies Record; + export const TransactionOutput = ({ transaction }: { transaction: SignedTransactionType; }) => { + const store = useStore(); const network = networkSelector(getState()); + const account = getAccount(store()); + const { isValid, valueDecimal, valueInteger, label } = + FormatAmountController.getData({ + digits: DIGITS, + decimals: DECIMALS, + egldLabel: network.egldLabel, + input: account.balance + }); const decodedData = transaction.data ? Buffer.from(transaction.data, 'base64').toString('ascii') : 'N/A'; + const explorerAddress = network.explorerAddress; + const hashExplorerLink = getExplorerLink({ + to: `/${TRANSACTIONS_ENDPOINT}/${transaction.hash}`, + explorerAddress + }); + const receiverExplorerLink = getExplorerLink({ + to: `/${ACCOUNTS_ENDPOINT}/${transaction.receiver}`, + explorerAddress + }); + return ( -
-

+

+
- - {transaction.hash} - -

-

+ + +

+ +
- - {transaction.receiver} - -

+ + +

@@ -55,7 +95,7 @@ export const TransactionOutput = ({ {transaction.gasLimit}

-

+

{decodedData}

diff --git a/src/components/OutputContainer/components/TransactionsOutput.tsx b/src/components/OutputContainer/components/TransactionsOutput.tsx index ee397a1..d253a73 100644 --- a/src/components/OutputContainer/components/TransactionsOutput.tsx +++ b/src/components/OutputContainer/components/TransactionsOutput.tsx @@ -1,13 +1,19 @@ import { SignedTransactionType } from 'lib'; + import { TransactionOutput } from './TransactionOutput'; +// prettier-ignore +const styles = { + transactionsContainer: 'transactions-container flex flex-col gap-4' +} satisfies Record; + export const TransactionsOutput = ({ transactions }: { transactions: SignedTransactionType[]; }) => { return ( -
+
{transactions?.map((transaction) => { return ; })} diff --git a/src/components/PingPongComponent/PingPongComponent.tsx b/src/components/PingPongComponent/PingPongComponent.tsx new file mode 100644 index 0000000..bcff3d5 --- /dev/null +++ b/src/components/PingPongComponent/PingPongComponent.tsx @@ -0,0 +1,160 @@ +import { faArrowDown, faArrowUp } from '@fortawesome/free-solid-svg-icons'; +import { TokenLoginType } from '@multiversx/sdk-dapp/out/types/login.types'; +import moment from 'moment'; + +import Fa from 'solid-fa'; +import { createEffect, createSignal, onCleanup } from 'solid-js'; +import { Label, OutputContainer, PingPongOutput } from 'components'; +import { contractAddress } from 'config'; +import { getCountdownSeconds, setTimeRemaining } from 'helpers'; +import { + ACCOUNTS_ENDPOINT, + MvxDataWithExplorerLink, + getPendingTransactions, + getStore, + MvxButton, + Transaction +} from 'lib'; +import { ItemsIdentifiersEnum } from 'pages/Dashboard/dashboard.types'; + +// prettier-ignore +const styles = { + pingPongContainer: 'ping-pong-container flex flex-col gap-6', + infosContainer: 'infos-container flex flex-col gap-2', + addressComponent: 'address-component flex w-full justify-between', + timeRemaining: 'text-red-600', + buttonsContainer: 'buttons-container flex flex-col gap-2', + buttons: 'buttons flex justify-start gap-2', + buttonContent: 'button-content text-sm font-normal' +} satisfies Record; + +interface PingPongComponentPropsType { + id: ItemsIdentifiersEnum; + sendPingTransaction: (amount: any) => Promise; + sendPongTransaction: (transaction?: any) => Promise; + getTimeToPong: () => Promise; + pingAmount?: string; + getPingTransaction?: () => Promise; + getPongTransaction?: () => Promise; + tokenLogin?: TokenLoginType | null; +} + +export const PingPongComponent = ({ + id, + sendPingTransaction, + sendPongTransaction, + getTimeToPong, + pingAmount +}: PingPongComponentPropsType) => { + const store = getStore(); + const [transactions, setTransactions] = createSignal( + getPendingTransactions() + ); + const unsubscribe = store.subscribe(() => { + setTransactions(getPendingTransactions()); + }); + + onCleanup(() => unsubscribe()); + const hasPendingTransactions = () => transactions().length > 0; + + const [hasPing, setHasPing] = createSignal(true); + const [secondsLeft, setSecondsLeft] = createSignal(0); + + const setSecondsRemaining = async () => { + const secondsRemaining = await getTimeToPong(); + const { canPing, timeRemaining } = setTimeRemaining(secondsRemaining); + + setHasPing(canPing); + if (timeRemaining !== undefined && timeRemaining >= 0) { + setSecondsLeft(timeRemaining); + } + }; + + const onSendPingTransaction = async () => { + await sendPingTransaction(pingAmount); + }; + + const onSendPongTransaction = async () => { + await sendPongTransaction(); + }; + + const timeRemaining = () => + moment() + .startOf('day') + .seconds(secondsLeft() ?? 0) + .format('mm:ss'); + + const pongAllowed = () => secondsLeft() === 0; + + createEffect(() => { + getCountdownSeconds({ secondsLeft: secondsLeft(), setSecondsLeft }); + }); + + createEffect(() => { + hasPing(); + hasPendingTransactions(); + setSecondsRemaining(); + }); + + return ( +
+
+ + + + {!hasPendingTransactions() && ( + <> + + + {!pongAllowed() && ( +

+ + {timeRemaining()} + + until able to pong +

+ )} + + )} + + +
+
+ +
+
+ + + + Ping + + + + + + Pong + +
+
+
+ ); +}; diff --git a/src/components/PingPongComponent/index.ts b/src/components/PingPongComponent/index.ts new file mode 100644 index 0000000..7bf86b5 --- /dev/null +++ b/src/components/PingPongComponent/index.ts @@ -0,0 +1 @@ +export * from './PingPongComponent'; diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000..b40c80e --- /dev/null +++ b/src/components/Tooltip/Tooltip.tsx @@ -0,0 +1,104 @@ +import classNames from 'classnames'; + +import { createSignal, JSX } from 'solid-js'; + +import { IPropsWithClass, IPropsWithChildren } from 'types'; + +interface TooltipPropsType extends IPropsWithChildren, IPropsWithClass { + trigger: (isTooltipVisible: boolean) => JSX.Element; + triggerOnClick?: boolean; + position?: 'top' | 'bottom'; +} + +// prettier-ignore +const styles = { + tooltip: 'tooltip flex relative', + tooltipContentWrapper: 'tooltip-content-wrapper left-1/2 -translate-x-1/2 absolute z-10', + tooltipContentWrapperTop: 'bottom-full pb-3', + tooltipContentWrapperBottom: 'top-full pt-3', + tooltipContent: 'tooltip-content flex-row text-left text-xs gap-1 cursor-default bg-primary rounded-xl border border-secondary p-1 relative after:w-2 after:h-2 after:border after:border-secondary after:bg-primary after:absolute after:left-1/2 after:origin-center after:-translate-x-1/2 after:-rotate-45 transition-all duration-200 ease-out', + tooltipContentTop: 'after:-bottom-1 after:border-t-0 after:border-r-0', + tooltipContentBottom: 'after:-top-1 after:border-b-0 after:border-l-0', + tooltipTrigger: 'tooltip-trigger' +} satisfies Record; + +export const Tooltip = ({ + position = 'top', + triggerOnClick = false, + class: className, + children, + trigger +}: TooltipPropsType) => { + const [isTooltipVisible, setIsTooltipVisible] = createSignal(false); + + const handleTriggerClick = (event: MouseEvent) => { + if (!triggerOnClick) { + return; + } + + event.preventDefault(); + setIsTooltipVisible(!isTooltipVisible()); + }; + + const handleBlur = (event: FocusEvent) => { + if ( + !(event.currentTarget as HTMLElement)?.contains( + event.relatedTarget as Node + ) + ) { + setIsTooltipVisible(false); + } + }; + + const handleMouseEnter = (event: MouseEvent) => { + if (triggerOnClick) { + return; + } + + event.preventDefault(); + setIsTooltipVisible(true); + }; + + const handleMouseLeave = (event: MouseEvent) => { + if (triggerOnClick) { + return; + } + + event.preventDefault(); + setIsTooltipVisible(false); + }; + + return ( +
+ {isTooltipVisible() && ( +
+
event.stopPropagation()} + onBlur={handleBlur} + tabIndex={-1} + class={classNames( + styles.tooltipContent, + { [styles.tooltipContentTop]: position === 'top' }, + { [styles.tooltipContentBottom]: position === 'bottom' } + )} + > + {children} +
+
+ )} + + {trigger(isTooltipVisible())} +
+ ); +}; diff --git a/src/components/Tooltip/index.ts b/src/components/Tooltip/index.ts new file mode 100644 index 0000000..7594a8f --- /dev/null +++ b/src/components/Tooltip/index.ts @@ -0,0 +1 @@ +export * from './Tooltip'; diff --git a/src/components/index.ts b/src/components/index.ts index c294228..57840f8 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,7 +1,11 @@ export * from './OutputContainer'; export * from './Loader'; export * from './Layout'; -export * from './Button'; -export * from './MxLink'; export * from './Label'; export * from './Card'; +export * from './Header'; +export * from './Footer'; +export * from './Tooltip'; +export * from './Logo'; +export * from './MissingNativeAuthError'; +export * from './PingPongComponent'; diff --git a/src/config/config.mainnet.ts b/src/config/config.mainnet.ts index b77743a..bcb05a9 100644 --- a/src/config/config.mainnet.ts +++ b/src/config/config.mainnet.ts @@ -3,5 +3,7 @@ export * from './sharedConfig'; export const contractAddress = 'erd1qqqqqqqqqqqqqpgqtmcuh307t6kky677ernjj9ulk64zq74w9l5qxyhdn7'; export const API_URL = 'https://template-api.multiversx.com'; +export const ID_API_URL = 'https://id-api.multiversx.com'; +export const USERS_API_URL = '/users/api/v1/users/'; export const sampleAuthenticatedDomains = [API_URL]; export const environment = 'mainnet'; diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..4844dc3 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,6 @@ +import { JSX } from 'solid-js'; + +declare module '*.svg' { + const content: (props: { class?: string }) => JSX.Element; + export default content; +} diff --git a/src/helpers/getDetectedBrowser.ts b/src/helpers/getDetectedBrowser.ts new file mode 100644 index 0000000..527f0bd --- /dev/null +++ b/src/helpers/getDetectedBrowser.ts @@ -0,0 +1,23 @@ +import { safeWindow } from '@multiversx/sdk-dapp'; + +import { BrowserEnum } from 'localConstants/browser.enum'; + +export const getDetectedBrowser = () => { + const nav = safeWindow?.navigator; + const userAgent = nav?.userAgent || ''; + + if (/Firefox/.test(userAgent)) { + return BrowserEnum.Firefox; + } + + if (nav && typeof (nav as any).brave !== 'undefined') { + return BrowserEnum.Brave; + } + if (userAgent.toLowerCase().includes('brave')) { + return BrowserEnum.Brave; + } + + if (/Chrome/.test(userAgent)) { + return BrowserEnum.Chrome; + } +}; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index a8ce727..1b0c61f 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,3 +1,4 @@ export * from './withPageTitle'; export * from './useScrollToElement'; export * from './useStore'; +export * from './transactions'; diff --git a/src/hooks/transactions/useSendPingPongTransaction.ts b/src/hooks/transactions/useSendPingPongTransaction.ts index 435b458..9c8317a 100644 --- a/src/hooks/transactions/useSendPingPongTransaction.ts +++ b/src/hooks/transactions/useSendPingPongTransaction.ts @@ -29,7 +29,7 @@ const PONG_TRANSACTION_INFO = { export const useSendPingPongTransaction = () => { const store = useStore(); const network = networkSelector(store()); - const { address } = getAccount(store()); + const address = getAccount()?.address; const getSmartContractFactory = async () => { const response = await axios.get('src/contracts/ping-pong.abi.json'); diff --git a/src/initConfig.ts b/src/initConfig.ts index ed9a8fe..d4e4508 100644 --- a/src/initConfig.ts +++ b/src/initConfig.ts @@ -1,5 +1,6 @@ import { EnvironmentsEnum, InitAppType } from 'lib'; import './styles/globals.css'; +import './styles/tailwind.css'; import { walletConnectV2ProjectId } from './config'; const DEFAULT_TOAST_LIEFTIME = 5000; @@ -19,7 +20,8 @@ export const config: InitAppType = { }, transactionTracking: { successfulToastLifetime: DEFAULT_TOAST_LIEFTIME - } + }, + theme: 'mvx:dark-theme' } // Option 2: Add providers using the config `customProviders` array diff --git a/src/lib/sdkDapp/components/FormatAmount/FormatAmount.tsx b/src/lib/sdkDapp/components/FormatAmount/FormatAmount.tsx index cbd8363..a13c1e6 100644 --- a/src/lib/sdkDapp/components/FormatAmount/FormatAmount.tsx +++ b/src/lib/sdkDapp/components/FormatAmount/FormatAmount.tsx @@ -1,35 +1,27 @@ -import { createEffect, createMemo } from 'solid-js'; -import { FormatAmountController } from 'lib'; +import { createEffect } from 'solid-js'; import { FormatAmountSDKPropsType } from 'lib/sdkDappUI/sdkDappUI.types'; -import { DECIMALS, DIGITS } from 'lib/sdkDappUtils/sdkDappUtils.constants'; import { IPropsWithClass } from 'types'; interface FormatAmountPropsType extends Partial, IPropsWithClass { - egldLabel?: string; - value: string; + isValid: boolean; + valueInteger: string; + valueDecimal: string; + label?: string; + decimalClass?: string; + labelClass?: string; + showLabel?: boolean; } export const FormatAmount = (props: FormatAmountPropsType) => { let elementRef: Partial | undefined; - const formatData = createMemo(() => - FormatAmountController.getData({ - digits: DIGITS, - decimals: DECIMALS, - egldLabel: props.egldLabel, - input: props.value - }) - ); createEffect(() => { - const data = formatData(); - if (!elementRef) { return; } - - Object.assign(elementRef, props, data); + Object.assign(elementRef, props); }); return ; diff --git a/src/lib/sdkDapp/components/MvxButton/MvxButton.tsx b/src/lib/sdkDapp/components/MvxButton/MvxButton.tsx new file mode 100644 index 0000000..c81674f --- /dev/null +++ b/src/lib/sdkDapp/components/MvxButton/MvxButton.tsx @@ -0,0 +1,33 @@ +import { IPropsWithClass } from 'types'; + +interface MvxButtonPropsType extends IPropsWithClass { + disabled?: boolean; + size?: 'small' | 'large'; + variant?: 'primary' | 'secondary' | 'neutral'; + 'data-testid'?: string; + onClick?: (event: MouseEvent) => void; + children?: any; +} + +export const MvxButton = ({ + class: className, + 'data-testid': dataTestId, + disabled, + size, + variant, + onClick, + children +}: MvxButtonPropsType) => { + return ( + + {children} + + ); +}; diff --git a/src/lib/sdkDapp/components/MvxButton/index.ts b/src/lib/sdkDapp/components/MvxButton/index.ts new file mode 100644 index 0000000..5156442 --- /dev/null +++ b/src/lib/sdkDapp/components/MvxButton/index.ts @@ -0,0 +1 @@ +export * from './MvxButton'; diff --git a/src/lib/sdkDapp/components/MvxDataWithExplorerLink/MvxDataWithExplorerLink.tsx b/src/lib/sdkDapp/components/MvxDataWithExplorerLink/MvxDataWithExplorerLink.tsx new file mode 100644 index 0000000..e05a2d3 --- /dev/null +++ b/src/lib/sdkDapp/components/MvxDataWithExplorerLink/MvxDataWithExplorerLink.tsx @@ -0,0 +1,32 @@ +import { IPropsWithClass } from 'types'; + +interface MvxDataWithExplorerLinkPropsType extends IPropsWithClass { + data: string; + explorerLink: string; + withTooltip?: boolean; + showExplorerButton?: boolean; + showCopyButton?: boolean; + 'data-testid'?: string; +} + +export const MvxDataWithExplorerLink = ({ + data, + explorerLink, + class: className, + 'data-testid': dataTestId, + withTooltip, + showExplorerButton, + showCopyButton +}: MvxDataWithExplorerLinkPropsType) => { + return ( + + ); +}; diff --git a/src/lib/sdkDapp/components/MvxDataWithExplorerLink/index.ts b/src/lib/sdkDapp/components/MvxDataWithExplorerLink/index.ts new file mode 100644 index 0000000..1962698 --- /dev/null +++ b/src/lib/sdkDapp/components/MvxDataWithExplorerLink/index.ts @@ -0,0 +1 @@ +export * from './MvxDataWithExplorerLink'; diff --git a/src/lib/sdkDapp/components/MvxTrim/MvxTrim.tsx b/src/lib/sdkDapp/components/MvxTrim/MvxTrim.tsx new file mode 100644 index 0000000..b431e4e --- /dev/null +++ b/src/lib/sdkDapp/components/MvxTrim/MvxTrim.tsx @@ -0,0 +1,26 @@ +import { IPropsWithClass } from 'types'; + +interface MvxTrimPropsType extends IPropsWithClass { + text: string; + shouldTrim?: boolean; + trimFontSize?: string; + 'data-testid'?: string; +} + +export const MvxTrim = ({ + text, + class: className, + 'data-testid': dataTestId, + shouldTrim, + trimFontSize +}: MvxTrimPropsType) => { + return ( + + ); +}; diff --git a/src/lib/sdkDapp/components/MvxTrim/index.ts b/src/lib/sdkDapp/components/MvxTrim/index.ts new file mode 100644 index 0000000..d08fb1c --- /dev/null +++ b/src/lib/sdkDapp/components/MvxTrim/index.ts @@ -0,0 +1 @@ +export * from './MvxTrim'; diff --git a/src/lib/sdkDapp/components/index.tsx b/src/lib/sdkDapp/components/index.tsx index 111ca43..c730f08 100644 --- a/src/lib/sdkDapp/components/index.tsx +++ b/src/lib/sdkDapp/components/index.tsx @@ -1,3 +1,6 @@ export * from './ExplorerLink'; export * from './FormatAmount'; export * from './TransactionsTable'; +export * from './MvxTrim'; +export * from './MvxDataWithExplorerLink'; +export * from './MvxButton'; diff --git a/src/lib/sdkDapp/index.ts b/src/lib/sdkDapp/index.ts index a967065..46f5c5d 100644 --- a/src/lib/sdkDapp/index.ts +++ b/src/lib/sdkDapp/index.ts @@ -3,3 +3,4 @@ export * from './sdkDapp.constants'; export * from './sdkDapp.helpers'; export * from './sdkDapp.selectors'; export * from './sdkDapp.types'; +export * from './sdkDapp.hooks'; diff --git a/src/lib/sdkDapp/sdkDapp.helpers.ts b/src/lib/sdkDapp/sdkDapp.helpers.ts index 565ff20..ddef169 100644 --- a/src/lib/sdkDapp/sdkDapp.helpers.ts +++ b/src/lib/sdkDapp/sdkDapp.helpers.ts @@ -11,3 +11,5 @@ export { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp'; export { setAxiosInterceptors } from '@multiversx/sdk-dapp/out/utils/network/setAxiosInterceptors'; export { refreshAccount } from '@multiversx/sdk-dapp/out/utils/account/refreshAccount'; export { getStore } from '@multiversx/sdk-dapp/out/store/store'; +export { trimUsernameDomain } from '@multiversx/sdk-dapp/out/utils/account/trimUsernameDomain'; +export { getExplorerLink } from '@multiversx/sdk-dapp/out/utils/transactions/getExplorerLink'; diff --git a/src/lib/sdkDapp/sdkDapp.hooks.ts b/src/lib/sdkDapp/sdkDapp.hooks.ts new file mode 100644 index 0000000..eab4bf6 --- /dev/null +++ b/src/lib/sdkDapp/sdkDapp.hooks.ts @@ -0,0 +1,6 @@ +export { useGetAccount } from '@multiversx/sdk-dapp/out/react/account/useGetAccount'; +export { useGetAccountInfo } from '@multiversx/sdk-dapp/out/react/account/useGetAccountInfo'; +export { useGetIsLoggedIn } from '@multiversx/sdk-dapp/out/react/account/useGetIsLoggedIn'; +export { useGetLoginInfo } from '@multiversx/sdk-dapp/out/react/loginInfo/useGetLoginInfo'; +export { useGetNetworkConfig } from '@multiversx/sdk-dapp/out/react/network/useGetNetworkConfig'; +export { useGetPendingTransactions } from '@multiversx/sdk-dapp/out/react/transactions/useGetPendingTransactions'; diff --git a/src/localConstants/browser.enum.ts b/src/localConstants/browser.enum.ts new file mode 100644 index 0000000..c9d7a35 --- /dev/null +++ b/src/localConstants/browser.enum.ts @@ -0,0 +1,5 @@ +export enum BrowserEnum { + Chrome = 'Chrome', + Firefox = 'Firefox', + Brave = 'Brave' +} diff --git a/src/localConstants/dashboard/dashboardLinks.ts b/src/localConstants/dashboard/dashboardLinks.ts new file mode 100644 index 0000000..4609887 --- /dev/null +++ b/src/localConstants/dashboard/dashboardLinks.ts @@ -0,0 +1,9 @@ +export const DOCUMENTATION_LINK = + 'https://docs.multiversx.com/developers/tutorials/your-first-dapp/#set-up-the-dapp-template'; + +export const REACT_LINK = 'https://react.dev/'; + +export const SDK_DAPP_PACKAGE_LINK = + 'https://www.npmjs.com/package/@multiversx/sdk-dapp'; + +export const TYPESCRIPT_LINK = 'https://www.typescriptlang.org/'; diff --git a/src/localConstants/dashboard/index.ts b/src/localConstants/dashboard/index.ts new file mode 100644 index 0000000..d23a16e --- /dev/null +++ b/src/localConstants/dashboard/index.ts @@ -0,0 +1 @@ +export * from './dashboardLinks'; diff --git a/src/localConstants/index.ts b/src/localConstants/index.ts index a382098..38faf17 100644 --- a/src/localConstants/index.ts +++ b/src/localConstants/index.ts @@ -1 +1,4 @@ export * from './routes'; +export * from './dashboard'; +export * from './browser.enum'; +export * from './installExtensionsLinks'; diff --git a/src/localConstants/installExtensionsLinks.ts b/src/localConstants/installExtensionsLinks.ts new file mode 100644 index 0000000..6719dab --- /dev/null +++ b/src/localConstants/installExtensionsLinks.ts @@ -0,0 +1,17 @@ +export const CHROME_EXTENSION_LINK = + 'https://chrome.google.com/webstore/detail/multiversx-defi-wallet/dngmlblcodfobpdpecaadgfbcggfjfnm'; + +export const CHROME_METAMASK_EXTENSION_LINK = + 'https://chromewebstore.google.com/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=en'; + +export const FIREFOX_ADDON_LINK = + 'https://addons.mozilla.org/en-US/firefox/addon/multiversx-defi-wallet'; + +export const FIREFOX_METAMASK_ADDON_LINK = + 'https://addons.mozilla.org/en-US/firefox/addon/ether-metamask'; + +export const GET_LEDGER = 'https://www.ledger.com/'; + +export const GET_XPORTAL = 'https://xportal.com/'; + +export const WALLET_ADDRESS = 'https://devnet-wallet.multiversx.com'; diff --git a/src/pages/Dashboard/Dashboard.tsx b/src/pages/Dashboard/Dashboard.tsx index 275192a..a5a93dc 100644 --- a/src/pages/Dashboard/Dashboard.tsx +++ b/src/pages/Dashboard/Dashboard.tsx @@ -1,54 +1,90 @@ +import classNames from 'classnames'; +import { createSignal } from 'solid-js'; + import { contractAddress } from 'config'; -import { WidgetType } from 'types'; -import { Widget } from './components'; -import { Account, PingPongRaw, SignMessage, Transactions } from './widgets'; -const WIDGETS: WidgetType[] = [ - { - title: 'Account', - widget: Account, - description: 'Connected account details', - reference: 'https://docs.multiversx.com/sdk-and-tools/sdk-dapp/#account' - }, +import { WidgetType } from 'types/widget.types'; + +import { DashboardHeader, LeftPanel, Widget } from './components'; + +import { ItemsIdentifiersEnum } from './dashboard.types'; +import { PingPongRaw, SignMessage, Transactions } from './widgets'; + +// prettier-ignore +const styles = { + dashboardContainer: 'dashboard-container flex w-screen min-h-screen relative border-t border-b border-secondary transition-all duration-200 ease-out', + mobilePanelContainer: 'mobile-panel-container fixed bottom-0 left-0 right-0 z-50 max-h-full overflow-y-auto lg:static lg:max-h-none lg:overflow-visible', + desktopPanelContainer: 'desktop-panel-container lg:flex', + dashboardContent: 'dashboard-content flex flex-col gap-6 justify-center items-center flex-1 w-full overflow-auto border-l border-secondary p-4 lg:p-6 transition-all duration-200 ease-out', + dashboardContentMobilePanelOpen: 'dashboard-content-mobile-panel-open opacity-20 lg:opacity-100 pointer-events-none', + dashboardWidgets: 'dashboard-widgets flex flex-col gap-6 w-full max-w-320' +} satisfies Record; + +const dashboardWidgets: WidgetType[] = [ { title: 'Ping & Pong (Manual)', widget: PingPongRaw, description: 'Smart Contract interactions using manually formulated transactions', reference: - 'https://docs.multiversx.com/sdk-and-tools/indices/es-index-transactions/', - anchor: 'ping-pong-manual' + 'https://docs.multiversx.com/sdk-and-tools/indices/es-index-transactions/' }, { title: 'Sign message', widget: SignMessage, description: 'Message signing using the connected account', - reference: 'https://docs.multiversx.com/sdk-and-tools/sdk-dapp/#account-1', - anchor: 'sign-message' + reference: 'https://docs.multiversx.com/sdk-and-tools/sdk-dapp/#account-1' }, { title: 'Transactions (All)', - widget: Transactions, + widget: () => , description: 'List transactions for the connected account', reference: - 'https://api.elrond.com/#/accounts/AccountController_getAccountTransactions' + 'https://api.multiversx.com/#/accounts/AccountController_getAccountTransactions' }, { title: 'Transactions (Ping & Pong)', - widget: Transactions, + widget: () => ( + + ), props: { receiver: contractAddress }, description: 'List transactions filtered for a given Smart Contract', reference: - 'https://api.elrond.com/#/accounts/AccountController_getAccountTransactions' + 'https://api.multiversx.com/#/accounts/AccountController_getAccountTransactions' } ]; export const Dashboard = () => { + const [isMobilePanelOpen, setIsMobilePanelOpen] = createSignal(false); + return ( -
- {WIDGETS.map((element) => ( - - ))} +
+
+ +
+ +
+ + +
+ {dashboardWidgets.map((element) => ( + + ))} +
+
); }; diff --git a/src/pages/Dashboard/components/DashboardHeader/DashboardHeader.tsx b/src/pages/Dashboard/components/DashboardHeader/DashboardHeader.tsx new file mode 100644 index 0000000..ddea118 --- /dev/null +++ b/src/pages/Dashboard/components/DashboardHeader/DashboardHeader.tsx @@ -0,0 +1,34 @@ +import { + REACT_LINK, + SDK_DAPP_PACKAGE_LINK, + TYPESCRIPT_LINK +} from 'localConstants'; + +import { LinkComponent } from './components'; + +// prettier-ignore +const styles = { + dashboardHeaderContainer: 'dashboard-header-container flex flex-col p-8 lg:p-10 justify-center items-center gap-6 self-stretch', + dashboardHeaderTitle: 'dashboard-header-title text-primary transition-all duration-200 ease-out text-center text-3xl xs:text-5xl lg:text-6xl font-medium', + dashboardHeaderDescription: 'dashboard-header-description text-secondary transition-all duration-200 ease-out text-center text-base xs:text-lg lg:text-xl font-medium' +} satisfies Record; + +export const DashboardHeader = () => ( +
+
Welcome to dApp Template
+ +
+ The MultiversX dApp Template, built using + React.js + and + Typescript. + It's a basic implementation of + + @multiversx/sdk-dapp + + + , providing the basics for MultiversX authentication and TX signing. + +
+
+); diff --git a/src/pages/Dashboard/components/DashboardHeader/components/LinkComponent/LinkComponent.tsx b/src/pages/Dashboard/components/DashboardHeader/components/LinkComponent/LinkComponent.tsx new file mode 100644 index 0000000..cc78887 --- /dev/null +++ b/src/pages/Dashboard/components/DashboardHeader/components/LinkComponent/LinkComponent.tsx @@ -0,0 +1,24 @@ +import { IPropsWithChildren } from 'types'; + +// prettier-ignore +const styles = { + linkAddress: 'link-address underline hover:text-primary transition-all duration-200' +} satisfies Record; + +interface LinkComponentPropsType extends IPropsWithChildren { + linkAddress: string; +} + +export const LinkComponent = ({ + linkAddress, + children +}: LinkComponentPropsType) => ( + + {children} + +); diff --git a/src/pages/Dashboard/components/DashboardHeader/components/LinkComponent/index.ts b/src/pages/Dashboard/components/DashboardHeader/components/LinkComponent/index.ts new file mode 100644 index 0000000..3f165f5 --- /dev/null +++ b/src/pages/Dashboard/components/DashboardHeader/components/LinkComponent/index.ts @@ -0,0 +1 @@ +export * from './LinkComponent'; diff --git a/src/pages/Dashboard/components/DashboardHeader/components/index.ts b/src/pages/Dashboard/components/DashboardHeader/components/index.ts new file mode 100644 index 0000000..3f165f5 --- /dev/null +++ b/src/pages/Dashboard/components/DashboardHeader/components/index.ts @@ -0,0 +1 @@ +export * from './LinkComponent'; diff --git a/src/pages/Dashboard/components/DashboardHeader/index.ts b/src/pages/Dashboard/components/DashboardHeader/index.ts new file mode 100644 index 0000000..c4a59a5 --- /dev/null +++ b/src/pages/Dashboard/components/DashboardHeader/index.ts @@ -0,0 +1 @@ +export * from './DashboardHeader'; diff --git a/src/pages/Dashboard/components/LeftPanel/LeftPanel.tsx b/src/pages/Dashboard/components/LeftPanel/LeftPanel.tsx new file mode 100644 index 0000000..cd4847f --- /dev/null +++ b/src/pages/Dashboard/components/LeftPanel/LeftPanel.tsx @@ -0,0 +1,122 @@ +import { + faClose, + faPowerOff, + faWallet +} from '@fortawesome/free-solid-svg-icons'; +import { useNavigate } from '@solidjs/router'; +import classNames from 'classnames'; + +import Fa from 'solid-fa'; +import IconExpand from 'assets/img/expand-up-down.svg'; +import { Logo } from 'components'; +import { + ACCOUNTS_ENDPOINT, + getAccount, + getAccountProvider, + getIsLoggedIn, + MvxDataWithExplorerLink +} from 'lib'; +import { RouteNamesEnum } from 'localConstants'; + +import { Account, SideMenu } from './components'; + +// prettier-ignore +const styles = { + leftPanelContainer: 'left-panel-container flex flex-col w-screen lg:w-80 gap-8 lg:gap-0 py-4 p-6 sticky lg:h-screen top-0 bg-primary lg:bg-accent transition-all duration-200 ease-out overflow-y-scroll', + leftPanelContainerOpen: 'left-panel-container-open rounded-t-2xl lg:rounded-t-none p-6', + leftPanelMobileHeader: 'left-panel-mobile-header flex lg:hidden justify-between items-center pt-2 pb-1 transition-all duration-200 ease-out', + leftPanelMobileHeaderIconClose: 'left-panel-mobile-header-icon-close text-link transition-all duration-200 ease-out', + leftPanelMobileHeaderIconOpen: 'left-panel-mobile-header-icon-open fill-primary transition-all duration-200 ease-out', + leftPanel: 'left-panel flex flex-col gap-4 lg:!block', + leftPanelHidden: 'hidden', + leftPanelMobileAddressSection: 'left-panel-mobile-address-section lg:hidden bg-accent transition-all duration-200 ease-out h-8 flex items-center justify-between rounded-full border border-secondary px-6 py-4', + leftPanelMobileAddress: 'left-panel-mobile-address flex gap-2 items-center justify-start w-[calc(100%-50px)]', + leftPanelMobileAddressIcon: 'left-panel-mobile-address-icon text-accent transition-all duration-200 ease-out', + logoutButton: 'text-center text-link hover:text-primary transition-all duration-200 ease-out cursor-pointer', + leftPanelComponents: 'flex flex-col gap-4 bg-accent p-6 lg:p-0 rounded-2xl transition-all duration-200 ease-out', + leftPanelBar: 'w-full h-0.25 bg-neutral-700 opacity-40 transition-all duration-200 ease-out', + trimmedAddress: 'w-full text-primary' +} satisfies Record; + +interface LeftPanelPropsType { + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; +} + +export const LeftPanel = ({ + isOpen = false, + setIsOpen +}: LeftPanelPropsType) => { + const handleOpenPanel = () => { + setIsOpen(!isOpen); + }; + + const address = getAccount()?.address; + + const isLoggedIn = getIsLoggedIn(); + + const provider = getAccountProvider(); + const navigate = useNavigate(); + + const handleLogout = async () => { + await provider.logout(); + navigate(RouteNamesEnum.home); + }; + + return ( +
+
+ + + {isOpen ? ( + + ) : ( +
+ +
+ )} +
+ +
+
+
+ + + +
+ + {isLoggedIn && ( + + )} +
+ +
+ + +
+ + +
+
+
+ ); +}; diff --git a/src/pages/Dashboard/components/LeftPanel/components/Account/Account.tsx b/src/pages/Dashboard/components/LeftPanel/components/Account/Account.tsx new file mode 100644 index 0000000..40cf0ed --- /dev/null +++ b/src/pages/Dashboard/components/LeftPanel/components/Account/Account.tsx @@ -0,0 +1,167 @@ +import { + faChevronUp, + faLayerGroup, + faWallet +} from '@fortawesome/free-solid-svg-icons'; +import classNames from 'classnames'; +import { Fa } from 'solid-fa'; + +import { Component, createSignal, JSX } from 'solid-js'; +import xLogo from 'assets/img/x-logo.svg'; +import { Label } from 'components'; +import { useStore } from 'hooks'; +import { + DECIMALS, + DIGITS, + FormatAmount, + FormatAmountController, + getAccount, + networkSelector, + MvxTrim +} from 'lib'; + +import { Username } from './components'; +import { getUserHerotag } from './hooks/getUserHerotag'; + +// prettier-ignore +const styles = { + connectedAccountContainer: 'connected-account flex flex-col gap-4', + connectedAccountHeader: 'connected-account-header flex justify-between items-center', + connectedAccountHeaderTitle: 'connected-account-header-title text-base transition-all duration-200 ease-out text-secondary', + connectedAccountHeaderIcon: 'connected-account-header-icon text-primary transition-transform duration-200 ease-out', + connectedAccountHeaderIconRotated: 'rotate-180', + connectedAccountDetails: 'connected-account-details flex flex-col', + connectedAccountDetailsHidden: 'hidden', + connectedAccountInfo: 'connected-account-info flex h-14 gap-2 items-center', + connectedAccountInfoIcon: 'connected-account-info-icon min-w-10 min-h-10 max-h-10 max-w-10 flex items-center justify-center text-tertiary border border-secondary rounded-lg overflow-hidden p-1.5 transition-all duration-200 ease-out', + connectedAccountInfoText: 'connected-account-info-text truncate flex flex-col', + connectedAccountInfoTextValue: 'connected-account-info-text-value text-primary transition-all duration-200 ease-out text-base', + connectedAccountDetailsIcon: 'connected-account-details-icon w-6 h-6', + connectedAccountDetailsHerotag: 'connected-account-details-herotag rounded-full', + connectedAccountDetailsXLogo: 'connected-account-details-xlogo fill-primary w-8 h-8 transition-all duration-200 ease-out', + connectedAccountDetailsTrimAddress: 'w-max' +} satisfies Record; + +interface AccountDetailsType { + icon: JSX.Element | string; + label: string; + value: number | JSX.Element; +} + +export const Account = () => { + const store = useStore(); + const network = networkSelector(store()); + const account = getAccount(store()); + const address = getAccount()?.address; + + const { isValid, valueDecimal, valueInteger, label } = + FormatAmountController.getData({ + digits: DIGITS, + decimals: DECIMALS, + egldLabel: network.egldLabel, + input: account.balance + }); + + const [isCollapsed, setIsCollapsed] = createSignal(false); + + const toggleCollapse = () => { + setIsCollapsed(!isCollapsed()); + }; + + const [herotag, profileUrl] = getUserHerotag(address); + const XLogo = xLogo as unknown as Component< + JSX.SvgSVGAttributes + >; + + const accountDetails: AccountDetailsType[] = [ + { + icon: , + label: 'Address', + value: ( + + ) as JSX.Element + }, + { + icon: herotag() ? ( + profileUrl ? ( + + ) : ( + herotag().slice(0, 3) + ) + ) : ( + ('@' as any) + ), + label: 'Herotag', + value: () as JSX.Element + }, + { + icon: ( + + ), + label: 'Shard', + value: account.shard as number + }, + { + icon: , + label: 'Balance', + value: ( + + ) as JSX.Element + } + ]; + + return ( +
+
+

+ Connected account details +

+ + +
+ +
+ {accountDetails.map((accountDetail) => ( +
+
+ {accountDetail.icon as any} +
+ +

+ + + {accountDetail.value as any} + +

+
+ ))} +
+
+ ); +}; diff --git a/src/pages/Dashboard/components/LeftPanel/components/Account/components/Username.tsx b/src/pages/Dashboard/components/LeftPanel/components/Account/components/Username.tsx new file mode 100644 index 0000000..b69a56f --- /dev/null +++ b/src/pages/Dashboard/components/LeftPanel/components/Account/components/Username.tsx @@ -0,0 +1,27 @@ +import { AccountType, trimUsernameDomain } from 'lib'; +import { getUserHerotag } from '../hooks'; + +// prettier-ignore +const styles = { + usernameContainer: 'username-container flex gap-0.5', + herotag: 'herotag text-accent transition-all duration-200 ease-out' +} satisfies Record; + +export const Username = (props: { + account?: AccountType | null; + address: string; +}) => { + const { address } = props; + + const [herotag] = getUserHerotag(address); + + return ( +

+ {herotag() ? '@' : ''} + + + {herotag() ? trimUsernameDomain(herotag()) : 'N/A'} + +

+ ); +}; diff --git a/src/pages/Dashboard/widgets/Account/components/index.ts b/src/pages/Dashboard/components/LeftPanel/components/Account/components/index.ts similarity index 100% rename from src/pages/Dashboard/widgets/Account/components/index.ts rename to src/pages/Dashboard/components/LeftPanel/components/Account/components/index.ts diff --git a/src/pages/Dashboard/components/LeftPanel/components/Account/hooks/getUserHerotag.tsx b/src/pages/Dashboard/components/LeftPanel/components/Account/hooks/getUserHerotag.tsx new file mode 100644 index 0000000..033aaa5 --- /dev/null +++ b/src/pages/Dashboard/components/LeftPanel/components/Account/hooks/getUserHerotag.tsx @@ -0,0 +1,39 @@ +import axios from 'axios'; + +import { createEffect, createSignal } from 'solid-js'; +import { ID_API_URL, USERS_API_URL } from 'config/config.mainnet'; + +export const getUserHerotag = (address: string) => { + const [profileUrl, setProfileUrl] = createSignal(''); + const [herotag, setHerotag] = createSignal(''); + + const getUserProfileData = async (userAddress?: string) => { + if (!userAddress) { + return; + } + + try { + const { data } = await axios.get(`${USERS_API_URL}${userAddress}`, { + baseURL: ID_API_URL + }); + + return data; + } catch (err) { + console.error('Unable to fetch profile url'); + } + }; + + createEffect(() => { + if (!address) return; + + const fetchUserProfileUrl = async () => { + const data = await getUserProfileData(address); + setProfileUrl(data?.profile?.url); + setHerotag(data?.herotag); + }; + + fetchUserProfileUrl(); + }); + + return [herotag, profileUrl]; +}; diff --git a/src/pages/Dashboard/components/LeftPanel/components/Account/hooks/index.ts b/src/pages/Dashboard/components/LeftPanel/components/Account/hooks/index.ts new file mode 100644 index 0000000..dfc360b --- /dev/null +++ b/src/pages/Dashboard/components/LeftPanel/components/Account/hooks/index.ts @@ -0,0 +1 @@ +export * from './getUserHerotag'; diff --git a/src/pages/Dashboard/widgets/Account/index.ts b/src/pages/Dashboard/components/LeftPanel/components/Account/index.ts similarity index 100% rename from src/pages/Dashboard/widgets/Account/index.ts rename to src/pages/Dashboard/components/LeftPanel/components/Account/index.ts diff --git a/src/pages/Dashboard/components/LeftPanel/components/SideMenu/SideMenu.tsx b/src/pages/Dashboard/components/LeftPanel/components/SideMenu/SideMenu.tsx new file mode 100644 index 0000000..a7d1664 --- /dev/null +++ b/src/pages/Dashboard/components/LeftPanel/components/SideMenu/SideMenu.tsx @@ -0,0 +1,131 @@ +import { + faChevronUp, + faFilter, + faPenNib, + faRectangleList, + faTableTennisPaddleBall, + IconDefinition +} from '@fortawesome/free-solid-svg-icons'; +import classNames from 'classnames'; + +import Fa from 'solid-fa'; +import { createSignal, JSX } from 'solid-js'; +import { ItemsIdentifiersEnum } from 'pages/Dashboard/dashboard.types'; + +// prettier-ignore +const styles = { + sideMenuContainer: 'side-menu-container flex flex-col gap-4', + sideMenuHeader: 'side-menu-header flex items-center justify-between', + sideMenuHeaderTitle: 'side-menu-header-title text-base transition-all duration-200 ease-out text-secondary', + sideMenuHeaderIcon: 'side-menu-header-icon text-primary transition-transform duration-200 ease-out', + sideMenuHeaderIconRotated: 'rotate-180', + sideMenuItems: 'side-menu-items flex flex-col transition-all duration-200 ease-out', + sideMenuItemsHidden: 'hidden', + sideMenuItem: 'side-menu-item flex items-center gap-2 p-2 cursor-pointer text-tertiary hover:text-primary hover:bg-primary hover:font-bold transition-all duration-200 ease-out fill-tertiary hover:fill-primary hover:rounded-lg', + sideMenuItemActive: 'side-menu-item-active text-primary bg-primary fill-primary rounded-lg font-bold transition-all duration-200 ease-out', + sideMenuItemTitle: 'side-menu-item-title transition-all duration-200 ease-out text-sm' +} satisfies Record; + +interface SideMenuPropsType { + setIsOpen: (isOpen: boolean) => void; +} +interface MenuItemsType { + title: string; + icon?: + | string + | IconDefinition + | ((props: JSX.SvgSVGAttributes) => JSX.Element); + id: ItemsIdentifiersEnum; +} + +const menuItems: MenuItemsType[] = [ + { + title: 'Ping & Pong (Manual)', + icon: faTableTennisPaddleBall, + id: ItemsIdentifiersEnum.pingPongRaw + }, + { + title: 'Sign message', + icon: faPenNib, + id: ItemsIdentifiersEnum.signMessage + }, + { + title: 'Transactions (All)', + icon: faRectangleList, + id: ItemsIdentifiersEnum.transactionsAll + }, + { + title: 'Transactions (Ping & Pong)', + icon: faFilter, + id: ItemsIdentifiersEnum.transactionsPingPong + } +]; + +export const SideMenu = ({ setIsOpen }: SideMenuPropsType) => { + const [isCollapsed, setIsCollapsed] = createSignal(false); + const [activeItem, setActiveItem] = createSignal( + ItemsIdentifiersEnum.pingPongRaw + ); + + const toggleCollapse = () => { + setIsCollapsed(!isCollapsed()); + }; + + const handleMenuItemClick = (id: ItemsIdentifiersEnum) => { + setIsOpen(false); + const target = document.getElementById(id); + if (target) { + const y = target.getBoundingClientRect().top + window.scrollY - 250; + window.scrollTo({ top: y, behavior: 'smooth' }); + + setActiveItem(id); + } + }; + + const setItemIcon = ( + icon: + | IconDefinition + | ((props: JSX.SvgSVGAttributes) => JSX.Element) + ) => { + if ('iconName' in icon) return ; + + const IconComponent = icon; + return ; + }; + + return ( +
+
+

Library

+ + +
+ +
+ {menuItems.map((item) => ( +
handleMenuItemClick(item.id)} + class={classNames(styles.sideMenuItem, { + [styles.sideMenuItemActive]: item.id === activeItem() + })} + > + {item.icon && setItemIcon(item.icon as any)} + +
{item.title}
+
+ ))} +
+
+ ); +}; diff --git a/src/pages/Dashboard/components/LeftPanel/components/SideMenu/index.ts b/src/pages/Dashboard/components/LeftPanel/components/SideMenu/index.ts new file mode 100644 index 0000000..6b65bcf --- /dev/null +++ b/src/pages/Dashboard/components/LeftPanel/components/SideMenu/index.ts @@ -0,0 +1 @@ +export * from './SideMenu'; diff --git a/src/pages/Dashboard/components/LeftPanel/components/index.ts b/src/pages/Dashboard/components/LeftPanel/components/index.ts new file mode 100644 index 0000000..3ba9126 --- /dev/null +++ b/src/pages/Dashboard/components/LeftPanel/components/index.ts @@ -0,0 +1,2 @@ +export * from './Account'; +export * from './SideMenu'; diff --git a/src/pages/Dashboard/components/LeftPanel/index.ts b/src/pages/Dashboard/components/LeftPanel/index.ts new file mode 100644 index 0000000..aedb0f9 --- /dev/null +++ b/src/pages/Dashboard/components/LeftPanel/index.ts @@ -0,0 +1 @@ +export * from './LeftPanel'; diff --git a/src/pages/Dashboard/components/Widget.tsx b/src/pages/Dashboard/components/Widget/Widget.tsx similarity index 89% rename from src/pages/Dashboard/components/Widget.tsx rename to src/pages/Dashboard/components/Widget/Widget.tsx index 0bb1199..9b7a7a9 100644 --- a/src/pages/Dashboard/components/Widget.tsx +++ b/src/pages/Dashboard/components/Widget/Widget.tsx @@ -1,4 +1,4 @@ -import { Card } from 'components/Card'; +import { Card } from 'components'; import { WidgetType } from 'types/widget.types'; export const Widget = ({ diff --git a/src/pages/Dashboard/components/Widget/index.ts b/src/pages/Dashboard/components/Widget/index.ts new file mode 100644 index 0000000..b42adc8 --- /dev/null +++ b/src/pages/Dashboard/components/Widget/index.ts @@ -0,0 +1 @@ +export * from './Widget'; diff --git a/src/pages/Dashboard/components/index.ts b/src/pages/Dashboard/components/index.ts index b42adc8..83da9c3 100644 --- a/src/pages/Dashboard/components/index.ts +++ b/src/pages/Dashboard/components/index.ts @@ -1 +1,3 @@ export * from './Widget'; +export * from './DashboardHeader'; +export * from './LeftPanel'; diff --git a/src/pages/Dashboard/dashboard.types.ts b/src/pages/Dashboard/dashboard.types.ts new file mode 100644 index 0000000..c7abf2e --- /dev/null +++ b/src/pages/Dashboard/dashboard.types.ts @@ -0,0 +1,10 @@ +export enum ItemsIdentifiersEnum { + pingPongRaw = 'ping-pong-raw', + pingPongAbi = 'ping-pong-abi', + pingPongService = 'ping-pong-service', + signMessage = 'sign-message', + nativeAuth = 'native-auth', + batchTransactions = 'batch-transactions', + transactionsAll = 'transactions-all', + transactionsPingPong = 'transactions-ping-pong' +} diff --git a/src/pages/Dashboard/widgets/Account/Account.tsx b/src/pages/Dashboard/widgets/Account/Account.tsx deleted file mode 100644 index 1a8dbba..0000000 --- a/src/pages/Dashboard/widgets/Account/Account.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { createMemo, Show } from 'solid-js'; -import { Label } from 'components/Label'; -import { OutputContainer } from 'components/OutputContainer/OutputContainer'; -import { useStore } from 'hooks'; -import { getAccount, networkSelector } from 'lib'; -import { FormatAmount } from 'lib/sdkDapp/components/FormatAmount/FormatAmount'; -import { Username } from './components'; - -export const Account = () => { - const store = useStore(); - const network = createMemo(() => networkSelector(store())); - const account = createMemo(() => getAccount(store())); - - return ( - -
- -

- - {account().address} -

- - -

- {account().shard} -

- -

- - -

-
-
-
- ); -}; diff --git a/src/pages/Dashboard/widgets/Account/components/Username.tsx b/src/pages/Dashboard/widgets/Account/components/Username.tsx deleted file mode 100644 index 865d4ba..0000000 --- a/src/pages/Dashboard/widgets/Account/components/Username.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Label } from 'components/Label'; -import { AccountType } from 'lib'; - -export const Username = ({ account }: { account: AccountType | null }) => { - if (!account) { - return null; - } - - return ( -

- - - {account.username ? account.username : 'N/A'} - -

- ); -}; diff --git a/src/pages/Dashboard/widgets/PingPongRaw/PingPongRaw.tsx b/src/pages/Dashboard/widgets/PingPongRaw/PingPongRaw.tsx index 43ff869..a72cc30 100644 --- a/src/pages/Dashboard/widgets/PingPongRaw/PingPongRaw.tsx +++ b/src/pages/Dashboard/widgets/PingPongRaw/PingPongRaw.tsx @@ -1,102 +1,25 @@ -import { faArrowUp, faArrowDown } from '@fortawesome/free-solid-svg-icons'; -import moment from 'moment'; -import Fa from 'solid-fa'; -import { createEffect, createSignal, onCleanup } from 'solid-js'; -import { Button } from 'components/Button'; -import { PingPongOutput } from 'components/OutputContainer/components'; -import { OutputContainer } from 'components/OutputContainer/OutputContainer'; -import { getCountdownSeconds, setTimeRemaining } from 'helpers'; -import { useSendPingPongTransaction } from 'hooks/transactions/useSendPingPongTransaction'; -import { getPendingTransactions, getStore } from 'lib'; -import { useGetTimeToPong, useGetPingAmount } from './hooks'; +import { PingPongComponent } from 'components'; +import { useSendPingPongTransaction } from 'hooks'; +import { ItemsIdentifiersEnum } from 'pages/Dashboard/dashboard.types'; + +import { useGetPingAmount, useGetTimeToPong } from './hooks'; // Raw transaction are being done by directly requesting to API instead of calling the smartcontract export const PingPongRaw = () => { const getTimeToPong = useGetTimeToPong(); - const store = getStore(); - const [transactions, setTransactions] = createSignal( - getPendingTransactions() - ); - const unsubscribe = store.subscribe(() => { - setTransactions(getPendingTransactions()); - }); - onCleanup(() => unsubscribe()); - - const hasPendingTransactions = transactions().length > 0; const { sendPingTransaction, sendPongTransaction } = useSendPingPongTransaction(); - const pingAmount = useGetPingAmount(); - - const [hasPing, setHasPing] = createSignal(true); - const [secondsLeft, setSecondsLeft] = createSignal(0); - - const setSecondsRemaining = async () => { - const secondsRemaining = await getTimeToPong(); - const { canPing, timeRemaining } = setTimeRemaining(secondsRemaining); - - setHasPing(canPing); - if (timeRemaining && timeRemaining >= 0) { - setSecondsLeft(timeRemaining); - } - }; - - const onSendPingTransaction = async () => { - await sendPingTransaction(pingAmount()); - }; - const onSendPongTransaction = async () => { - await sendPongTransaction(); - }; - - const timeRemaining = moment() - .startOf('day') - .seconds(secondsLeft() ?? 0) - .format('mm:ss'); - - const pongAllowed = () => secondsLeft() === 0; - - createEffect(() => { - getCountdownSeconds({ secondsLeft: secondsLeft(), setSecondsLeft }); - }); - - createEffect(() => { - setSecondsRemaining(); - }); + const pingAmount = useGetPingAmount(); return ( -
-
-
- - - -
-
- - - - -
+ ); }; diff --git a/src/pages/Dashboard/widgets/PingPongRaw/hooks/useGetTimeToPong.ts b/src/pages/Dashboard/widgets/PingPongRaw/hooks/useGetTimeToPong.ts index b593e3d..9defc94 100644 --- a/src/pages/Dashboard/widgets/PingPongRaw/hooks/useGetTimeToPong.ts +++ b/src/pages/Dashboard/widgets/PingPongRaw/hooks/useGetTimeToPong.ts @@ -1,13 +1,9 @@ import axios from 'axios'; import BigNumber from 'bignumber.js'; + import { contractAddress } from 'config'; -import { - Address, - AddressValue, - getAccount, - getState, - networkSelector -} from 'lib'; +import { Address, getAccount, getState, networkSelector } from 'lib'; + import { PingPongResponseType } from '../types'; const decodeTime = (data: PingPongResponseType) => { @@ -26,11 +22,12 @@ const decodeTime = (data: PingPongResponseType) => { export const useGetTimeToPong = () => { const network = networkSelector(getState()); - const { address } = getAccount(); + const address = getAccount()?.address; const getTimeToPong = async () => { try { - const args = new AddressValue(new Address(address)).valueOf().hex(); + const args = new Address(address).toHex(); + const { data } = await axios.post( `${network.apiAddress}/vm-values/query`, { diff --git a/src/pages/Dashboard/widgets/SignMessage/SignMessage.tsx b/src/pages/Dashboard/widgets/SignMessage/SignMessage.tsx index d222957..d790248 100644 --- a/src/pages/Dashboard/widgets/SignMessage/SignMessage.tsx +++ b/src/pages/Dashboard/widgets/SignMessage/SignMessage.tsx @@ -1,23 +1,35 @@ -import { - faFileSignature, - faBroom, - faArrowsRotate -} from '@fortawesome/free-solid-svg-icons'; -import { Address, Message } from '@multiversx/sdk-core'; +import { faPaste } from '@fortawesome/free-solid-svg-icons'; +// import { MvxButton } from '@multiversx/sdk-dapp-ui/react'; + import Fa from 'solid-fa'; import { createSignal } from 'solid-js'; -import { Button } from 'components/Button'; -import { OutputContainer } from 'components/OutputContainer/OutputContainer'; -import { getAccount, getAccountProvider } from 'lib'; +import { OutputContainer } from 'components'; +import { Address, getAccount, getAccountProvider, Message } from 'lib'; +import { ItemsIdentifiersEnum } from 'pages/Dashboard/dashboard.types'; + import { SignFailure, SignSuccess } from './components'; +// prettier-ignore +const styles = { + signMessageContainer: 'sign-message-container flex flex-col gap-6', + signMessage: 'sign-message flex flex-col gap-2', + signMessageLabel: 'sign-message-label text-secondary transition-all duration-200 ease-out text-sm font-normal', + signMessageText: 'sign-message-text text-secondary transition-all duration-200 ease-out resize-none w-full h-32 rounded-lg focus:outline-none', + signMessagePasteButtonContainer: 'sign-message-paste-button-container w-full flex justify-end', + signMessagePasteButton: 'sign-message-paste-button text-tertiary text-sm font-semibold flex items-center bg-btn-tertiary rounded-md cursor-pointer px-1 transition-all duration-200 ease-out', + signMessagePasteButtonText: 'sign-message-paste-button-text p-1', + signMessageButton: 'sign-message-button flex gap-2 items-start', + signButtonContent: 'sign-button-content text-sm font-normal' +} satisfies Record; + export const SignMessage = () => { const [message, setMessage] = createSignal(''); const [signedMessage, setSignedMessage] = createSignal(null); const [state, setState] = createSignal<'pending' | 'success' | 'error'>( 'pending' ); - const [signatrue, setSignatrue] = createSignal(''); + + const [signature, setSignatrue] = createSignal(''); const address = getAccount()?.address; const provider = getAccountProvider(); @@ -27,6 +39,7 @@ export const SignMessage = () => { address: new Address(address), data: Buffer.from(message()) }); + const signedMessageResult = await provider.signMessage(messageToSign); if (!signedMessageResult?.signature) { @@ -51,55 +64,91 @@ export const SignMessage = () => { setState('pending'); }; + const handlePasteClick = async () => { + const messageToSign = await navigator.clipboard.readText(); + + setMessage(messageToSign); + }; + return ( -
-
+
+
+ + + {!['success', 'error'].includes(state()) && ( +