diff --git a/package-lock.json b/package-lock.json index 2173cb3..a55a680 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "@perawallet/connect", - "version": "0.0.12", + "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@perawallet/connect", - "version": "0.0.12", + "version": "0.1.0", "license": "ISC", "dependencies": { "@json-rpc-tools/utils": "^1.7.6", "@walletconnect/client": "^1.7.7", "buffer": "^6.0.3", - "react-qr-code": "^2.0.5" + "react-qrcode-logo": "^2.7.0" }, "devDependencies": { "@hipo/eslint-config-base": "^4.1.1", @@ -3927,6 +3927,11 @@ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -3949,6 +3954,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -5634,6 +5640,8 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -5655,10 +5663,10 @@ "node": ">=6" } }, - "node_modules/qr.js": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", - "integrity": "sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=" + "node_modules/qrcode-generator": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz", + "integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==" }, "node_modules/qs": { "version": "6.5.3", @@ -5758,24 +5766,21 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "peer": true }, - "node_modules/react-qr-code": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.5.tgz", - "integrity": "sha512-8R/n/5X9n5JsND+npJYYCcrf1jEcphG7o0d5aXl7cx/tpDF0kfSoYucG8BUG3EN/LCzf5Ehf2yl7zdnxdpKGBQ==", + "node_modules/react-qrcode-logo": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/react-qrcode-logo/-/react-qrcode-logo-2.7.0.tgz", + "integrity": "sha512-79Ce+1GlETAbu8fQGAoHczjvXjsm044+FeVAutu9lwJ/w0hRKRRPPHQRkPmXZK4aPi/H9Fqv+7P29VYQ9Yo2dA==", "dependencies": { - "prop-types": "^15.7.2", - "qr.js": "0.0.0" + "lodash.isequal": "^4.5.0", + "qrcode-generator": "^1.4.1" }, "peerDependencies": { - "react": "^16.x || ^17.x || ^18.x", - "react-native-svg": "*" - }, - "peerDependenciesMeta": { - "react-native-svg": { - "optional": true - } + "react": ">=16.4.1", + "react-dom": ">=16.4.1" } }, "node_modules/read-pkg": { @@ -10985,6 +10990,11 @@ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -11007,6 +11017,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -12173,6 +12184,8 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "peer": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -12191,10 +12204,10 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "qr.js": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", - "integrity": "sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=" + "qrcode-generator": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz", + "integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==" }, "qs": { "version": "6.5.3", @@ -12262,15 +12275,17 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "peer": true }, - "react-qr-code": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.5.tgz", - "integrity": "sha512-8R/n/5X9n5JsND+npJYYCcrf1jEcphG7o0d5aXl7cx/tpDF0kfSoYucG8BUG3EN/LCzf5Ehf2yl7zdnxdpKGBQ==", + "react-qrcode-logo": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/react-qrcode-logo/-/react-qrcode-logo-2.7.0.tgz", + "integrity": "sha512-79Ce+1GlETAbu8fQGAoHczjvXjsm044+FeVAutu9lwJ/w0hRKRRPPHQRkPmXZK4aPi/H9Fqv+7P29VYQ9Yo2dA==", "requires": { - "prop-types": "^15.7.2", - "qr.js": "0.0.0" + "lodash.isequal": "^4.5.0", + "qrcode-generator": "^1.4.1" } }, "read-pkg": { diff --git a/package.json b/package.json index 7ae3c00..40279c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@perawallet/connect", - "version": "0.0.12", + "version": "0.1.0", "description": "JavaScript SDK for integrating Pera Wallet to web applications.", "main": "dist/index.js", "scripts": { @@ -35,7 +35,7 @@ "@json-rpc-tools/utils": "^1.7.6", "@walletconnect/client": "^1.7.7", "buffer": "^6.0.3", - "react-qr-code": "^2.0.5" + "react-qrcode-logo": "^2.7.0" }, "peerDependencies": { "algosdk": "^1.15.0", diff --git a/rollup.config.js b/rollup.config.js index b9bfaa6..db32a86 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -17,7 +17,7 @@ export default [ "react-dom", "@walletconnect/client", "@hipo/react-ui-toolkit", - "react-qr-code", + "react-qrcode-logo", "@json-rpc-tools/utils/dist/cjs/format", "algosdk" ], diff --git a/src/asset/icon/Close--dark.svg b/src/asset/icon/Close--dark.svg new file mode 100644 index 0000000..3fb8c1f --- /dev/null +++ b/src/asset/icon/Close--dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/asset/icon/Close.svg b/src/asset/icon/Close.svg index e08d1f8..fb31f33 100644 --- a/src/asset/icon/Close.svg +++ b/src/asset/icon/Close.svg @@ -1,11 +1,3 @@ - - - - - - - - - - + + diff --git a/src/asset/icon/Layer.svg b/src/asset/icon/Layer.svg new file mode 100644 index 0000000..606803b --- /dev/null +++ b/src/asset/icon/Layer.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/asset/icon/Note.svg b/src/asset/icon/Note.svg new file mode 100644 index 0000000..93145b8 --- /dev/null +++ b/src/asset/icon/Note.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/asset/icon/PeraWallet--circle-black.svg b/src/asset/icon/PeraWallet--circle-black.svg new file mode 100644 index 0000000..61003be --- /dev/null +++ b/src/asset/icon/PeraWallet--circle-black.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/asset/icon/PeraWallet--circle-yellow.svg b/src/asset/icon/PeraWallet--circle-yellow.svg new file mode 100644 index 0000000..28b593f --- /dev/null +++ b/src/asset/icon/PeraWallet--circle-yellow.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/asset/icon/PeraWallet--with-text.svg b/src/asset/icon/PeraWallet--with-text.svg new file mode 100644 index 0000000..b6b3c4e --- /dev/null +++ b/src/asset/icon/PeraWallet--with-text.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/asset/icon/PeraWallet.svg b/src/asset/icon/PeraWallet.svg index 2e6c228..f053a80 100644 --- a/src/asset/icon/PeraWallet.svg +++ b/src/asset/icon/PeraWallet.svg @@ -1,9 +1,8 @@ - - - - - - - - + + + + + + + diff --git a/src/asset/icon/Right.svg b/src/asset/icon/Right.svg new file mode 100644 index 0000000..9545eae --- /dev/null +++ b/src/asset/icon/Right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/asset/icon/ShieldTick.svg b/src/asset/icon/ShieldTick.svg new file mode 100644 index 0000000..681bdcd --- /dev/null +++ b/src/asset/icon/ShieldTick.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/declarations/custom.d.ts b/src/declarations/custom.d.ts index 5e52f80..e4d7e6d 100644 --- a/src/declarations/custom.d.ts +++ b/src/declarations/custom.d.ts @@ -2,3 +2,8 @@ declare module "*.svg" { const content: string; export default content; } + +declare module "*.png" { + const content: string; + export default content; +} diff --git a/src/modal/PeraWalletConnectModal.tsx b/src/modal/PeraWalletConnectModal.tsx index ffae223..2bcb76a 100644 --- a/src/modal/PeraWalletConnectModal.tsx +++ b/src/modal/PeraWalletConnectModal.tsx @@ -1,17 +1,14 @@ import CloseIcon from "../asset/icon/Close.svg"; +import CloseIconDark from "../asset/icon/Close--dark.svg"; import "./_pera-wallet-modal.scss"; -import "./_pera-wallet-connect-modal.scss"; -import React, {useState} from "react"; -import QRCode from "react-qr-code"; +import React from "react"; -import {isLargeScreen} from "../util/screen/screenSizeUtils"; -import {isMobile} from "../util/device/deviceUtils"; -import { - generatePeraWalletConnectDeepLink, - getPeraWalletAppMeta -} from "../util/peraWalletUtils"; +import {useIsSmallScreen} from "../util/screen/useMediaQuery"; +import PeraWalletConnectModalTouchScreenMode from "./mode/touch-screen/PeraWalletConnectModalTouchScreenMode"; +import PeraWalletConnectModalDesktopMode from "./mode/desktop/PeraWalletConnectModalDesktopMode"; +import useSetDynamicVhValue from "../util/screen/useSetDynamicVhValue"; interface PeraWalletConnectModalProps { uri: string; @@ -19,113 +16,31 @@ interface PeraWalletConnectModalProps { } function PeraWalletConnectModal({uri, onClose}: PeraWalletConnectModalProps) { - const {logo, name, main_color} = getPeraWalletAppMeta(); - const [isQRCodeVisible, setQRCodeVisibility] = useState(!isMobile()); - const [isSpinnerVisible, setSpinnerVisibility] = useState(false); + const isSmallScreen = useIsSmallScreen(); + + useSetDynamicVhValue(); return ( -
+
-
- -
-
- {isSpinnerVisible ? renderPendingMessage() : renderActionButtons()} - - {isSpinnerVisible && ( - + {isSmallScreen ? ( + + ) : ( + )}
); - - function renderActionButtons() { - return ( - <> - {isMobile() && ( - - {`Launch ${name}`} - - )} - - {isQRCodeVisible && renderQRCode()} - - {!isQRCodeVisible && ( - - )} - - ); - } - - function renderQRCode() { - return ( -
-

- {`Scan QR code with ${name}`} -

- -
- ); - } - - function renderPendingMessage() { - return ( -
-
- -
- -
- {`Please wait while we connect you to ${name}...`} -
-
- ); - } - - function handleToggleSpinnerVisibility() { - setSpinnerVisibility(!isSpinnerVisible); - } - - function handleToggleQRCodeVisibility() { - setQRCodeVisibility(!isQRCodeVisible); - } - - function handleCancelClick() { - onClose(); - } } export default PeraWalletConnectModal; diff --git a/src/modal/_pera-wallet-connect-modal.scss b/src/modal/_pera-wallet-connect-modal.scss deleted file mode 100644 index 3af0f0b..0000000 --- a/src/modal/_pera-wallet-connect-modal.scss +++ /dev/null @@ -1,73 +0,0 @@ -.pera-wallet-connect-modal__display-qr-code-button, -.pera-wallet-connect-modal__launch-pera-wallet-button { - letter-spacing: -0.3px; -} - -.pera-wallet-connect-modal__launch-pera-wallet-button { - display: block; - - padding: 14px; - margin-bottom: 20px; - - border-radius: 4px; - background-color: var(--pera-wallet-main-color); - - color: black; - - text-decoration: none; - text-align: center; - font-size: 14px; -} - -.pera-wallet-connect-modal__cancel-button, -.pera-wallet-connect-modal__display-qr-code-button { - width: 100%; - - padding: 14px; - margin: 0; - - background-color: white; - border: 1px solid black; - border-radius: 4px; - - color: black; -} - -.pera-wallet-connect-modal__qr-code { - cursor: none; - - text-align: center; -} - -.pera-wallet-connect-modal__qr-code__text { - margin-bottom: 15px; -} - -.pera-wallet-connect-modal__pending-message { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - - height: 150px; - - text-align: center; -} - -.pera-wallet-connect-modal__pending-message__logo img { - width: 40px; -} - -.pera-wallet-connect-modal__pending-message__text { - margin-top: 25px; -} - -.pera-wallet-connect-modal__cancel-button { - margin-top: 20px; - - border-color: rgba(0, 0, 0, 0.08); - - color: rgba(0, 0, 0, 0.8); - - font-size: 12px; -} diff --git a/src/modal/_pera-wallet-modal.scss b/src/modal/_pera-wallet-modal.scss index 3e075ff..716f722 100644 --- a/src/modal/_pera-wallet-modal.scss +++ b/src/modal/_pera-wallet-modal.scss @@ -1,18 +1,15 @@ -.pera-wallet-connect-modal { - --pera-wallet-connect-modal-font-family: ui-rounded, "SF Pro Rounded", "SF Pro Text", - medium-content-sans-serif-font, -apple-system, BlinkMacSystemFont, ui-sans-serif, - "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", - sans-serif; +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); +@import "../ui/styles/media-queries"; - display: flex; - align-items: center; - justify-content: center; +.pera-wallet-connect-modal { + --pera-wallet-connect-modal-font-family: "Inter", sans-serif; position: fixed; top: 0; right: 0; left: 0; bottom: 0; + z-index: 10; width: 100vw; height: 100vh; @@ -24,6 +21,13 @@ * { box-sizing: border-box; } + + li { + list-style-type: none; + + margin: 0; + padding: 0; + } } .pera-wallet-connect-button { @@ -32,12 +36,12 @@ justify-content: center; width: auto; - height: 35px; + height: 48px; padding: 14px; border: none; - border-radius: var(--small-radius-size); + border-radius: 12px; outline: none; cursor: pointer; @@ -53,39 +57,116 @@ } .pera-wallet-connect-modal__close-button { - width: 30px; - height: 30px; + width: 40px; + height: 40px; margin: 0; padding: 0; - box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.08); - background-color: white; + background: #333333; + border: 1.5px solid rgba(255, 255, 255, 0.08); + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.15); border-radius: 100%; } .pera-wallet-connect-modal__body { position: relative; + top: 50%; + left: 50%; - width: 440px; + width: 655px; max-width: calc(100vw - 80px); - padding: 24px; + padding: 28px; + + background-color: #edeffb; - background-color: white; box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.08); - border-radius: 4px; + border-radius: 24px; + + transform: translate(-50%, -50%); + + &::before { + --background-line: #1e0972 0 1.2px, transparent 0 calc(100% - 1.2px), #1e0972; + + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: -1; + + content: ""; + + background-image: linear-gradient(var(--background-line)), + linear-gradient(90deg, var(--background-line)); + background-size: 116px 116px; + + mix-blend-mode: overlay; + + border-radius: 24px; + + opacity: 0.8; + + pointer-events: none; + } } .pera-wallet-connect-modal__body__header { display: flex; align-items: center; - justify-content: space-between; + justify-content: flex-end; position: absolute; - top: -40px; - right: 0; + top: -44px; + right: -44px; left: 0; } + +@include for-small-screens { + .pera-wallet-connect-modal__body { + top: 40px; + bottom: 0; + left: 0; + + width: 100%; + max-width: unset; + height: calc(calc(100 * var(--vh))); + + padding: 20px; + + background-color: #ffffff; + + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02), 0 4px 12px rgba(0, 0, 0, 0.03); + + border-radius: 20px 20px 0px 0px; + + transform: unset; + + &::before { + background-image: unset; + } + } + + .pera-wallet-connect-modal__body__header { + display: flex; + align-items: center; + justify-content: flex-end; + + position: static; + } + + .pera-wallet-connect-modal__close-button { + width: 24px; + height: 24px; + + margin: 0; + padding: 0; + + background: transparent; + border: unset; + box-shadow: unset; + } +} diff --git a/src/modal/component/accordion/Accordion.tsx b/src/modal/component/accordion/Accordion.tsx new file mode 100644 index 0000000..8edd270 --- /dev/null +++ b/src/modal/component/accordion/Accordion.tsx @@ -0,0 +1,36 @@ +import React, {useState} from "react"; + +import AccordionItem from "./item/AccordionItem"; +import {AccordionData} from "./util/accordionTypes"; + +interface AccordionProps { + items: AccordionData[]; +} + +function Accordion({items}: AccordionProps) { + const [activeItem, setActiveItem] = useState(0); + + return ( + + ); + + function handleToggle(index: number) { + return () => { + if (activeItem === index) { + setActiveItem(0); + } + setActiveItem(index); + }; + } +} + +export default Accordion; diff --git a/src/modal/component/accordion/button/AccordionButton.tsx b/src/modal/component/accordion/button/AccordionButton.tsx new file mode 100644 index 0000000..584f717 --- /dev/null +++ b/src/modal/component/accordion/button/AccordionButton.tsx @@ -0,0 +1,22 @@ +import "./_accordion-button.scss"; + +import React from "react"; + +import AccordionIcon from "../icon/AccordionIcon"; + +interface AccordionButtonProps { + children: React.ReactNode; + onClick: VoidFunction; +} + +function AccordionButton({children, onClick}: AccordionButtonProps) { + return ( + + ); +} + +export default AccordionButton; diff --git a/src/modal/component/accordion/button/_accordion-button.scss b/src/modal/component/accordion/button/_accordion-button.scss new file mode 100644 index 0000000..562affa --- /dev/null +++ b/src/modal/component/accordion/button/_accordion-button.scss @@ -0,0 +1,26 @@ +.pera-wallet-accordion-button { + display: flex; + align-items: center; + + gap: 16px; + + width: 100%; + + padding: 12px 24px; + + color: #2c3559; + background-color: #ffffff; + + border: none; + border-radius: 24px; + outline: none; + + cursor: pointer; + + font-size: 16px; + line-height: 18px; + letter-spacing: -0.1px; + font-weight: 600; + + transition: all ease-in 0.2s; +} diff --git a/src/modal/component/accordion/icon/AccordionIcon.tsx b/src/modal/component/accordion/icon/AccordionIcon.tsx new file mode 100644 index 0000000..ac24598 --- /dev/null +++ b/src/modal/component/accordion/icon/AccordionIcon.tsx @@ -0,0 +1,9 @@ +import RightIcon from "../../../../asset/icon/Right.svg"; + +import React from "react"; + +function AccordionIcon() { + return ; +} + +export default AccordionIcon; diff --git a/src/modal/component/accordion/item/AccordionItem.tsx b/src/modal/component/accordion/item/AccordionItem.tsx new file mode 100644 index 0000000..95018be --- /dev/null +++ b/src/modal/component/accordion/item/AccordionItem.tsx @@ -0,0 +1,30 @@ +import "./_accordion-item.scss"; + +import React from "react"; + +import AccordionButton from "../button/AccordionButton"; +import AccordionPanel from "../panel/AccordionPanel"; +import {AccordionData} from "../util/accordionTypes"; + +interface AccordionItemProps { + data: AccordionData; + onToggle: VoidFunction; + isActive: boolean; +} + +function AccordionItem({data, onToggle, isActive}: AccordionItemProps) { + const {title, description} = data; + + return ( +
  • + {title} + + {description} +
  • + ); +} + +export default AccordionItem; diff --git a/src/modal/component/accordion/item/_accordion-item.scss b/src/modal/component/accordion/item/_accordion-item.scss new file mode 100644 index 0000000..9de3b8c --- /dev/null +++ b/src/modal/component/accordion/item/_accordion-item.scss @@ -0,0 +1,31 @@ +.pera-wallet-accordion-item { + background-color: #ffffff; + + border-radius: 24px; + + &:not(:last-of-type) { + margin-bottom: 20px; + } +} + +.pera-wallet-accordion-item--active { + .pera-wallet-accordion-panel { + height: 296px; + + border-radius: 0 0 24px 24px; + + transition: height ease-in 0.2s; + } + + .pera-wallet-accordion-button { + padding: 28px 24px 12px; + + border-radius: 24px 24px 0 0; + + transition: all ease-in 0.2s; + } + + .pera-wallet-accordion-icon { + transform: rotate(90deg); + } +} diff --git a/src/modal/component/accordion/panel/AccordionPanel.tsx b/src/modal/component/accordion/panel/AccordionPanel.tsx new file mode 100644 index 0000000..a1016e8 --- /dev/null +++ b/src/modal/component/accordion/panel/AccordionPanel.tsx @@ -0,0 +1,17 @@ +import "./_accordion-panel.scss"; + +import React from "react"; + +interface AccordionPanelProps { + children: React.ReactNode; +} + +function AccordionPanel({children}: AccordionPanelProps) { + return ( +
    +
    {children}
    +
    + ); +} + +export default AccordionPanel; diff --git a/src/modal/component/accordion/panel/_accordion-panel.scss b/src/modal/component/accordion/panel/_accordion-panel.scss new file mode 100644 index 0000000..8e0d23d --- /dev/null +++ b/src/modal/component/accordion/panel/_accordion-panel.scss @@ -0,0 +1,14 @@ +.pera-wallet-accordion-panel { + height: 0; + overflow: hidden; + + color: #69708d; + background-color: #ffffff; + + font-size: 13px; + font-weight: 500; + line-height: 20px; + letter-spacing: -0.04px; + + transition: height ease-in 0.2s; +} diff --git a/src/modal/component/accordion/util/accordionTypes.ts b/src/modal/component/accordion/util/accordionTypes.ts new file mode 100644 index 0000000..e48284c --- /dev/null +++ b/src/modal/component/accordion/util/accordionTypes.ts @@ -0,0 +1,7 @@ +import React from "react"; + +export interface AccordionData { + id: string; + title: string; + description: React.ReactNode; +} diff --git a/src/modal/mode/desktop/PeraWalletConnectModalDesktopMode.tsx b/src/modal/mode/desktop/PeraWalletConnectModalDesktopMode.tsx new file mode 100644 index 0000000..b9c29f4 --- /dev/null +++ b/src/modal/mode/desktop/PeraWalletConnectModalDesktopMode.tsx @@ -0,0 +1,25 @@ +import "./_pera-wallet-connect-modal-desktop-mode.scss"; + +import React from "react"; + +import Accordion from "../../component/accordion/Accordion"; +import {getPeraConnectModalAccordionData} from "../../peraWalletConnectModalUtils"; +import PeraWalletConnectModalInformationSection from "../../section/information/PeraWalletConnectModalInformationSection"; + +interface PeraWalletConnectModalDesktopModeProps { + uri: string; +} + +function PeraWalletConnectModalDesktopMode({ + uri +}: PeraWalletConnectModalDesktopModeProps) { + return ( +
    + + + +
    + ); +} + +export default PeraWalletConnectModalDesktopMode; diff --git a/src/modal/mode/desktop/_pera-wallet-connect-modal-desktop-mode.scss b/src/modal/mode/desktop/_pera-wallet-connect-modal-desktop-mode.scss new file mode 100644 index 0000000..c3c977a --- /dev/null +++ b/src/modal/mode/desktop/_pera-wallet-connect-modal-desktop-mode.scss @@ -0,0 +1,29 @@ +.pera-wallet-connect-modal-desktop-mode { + display: grid; + grid-template-columns: 205px auto; + gap: 80px; +} + +.pera-wallet-connect-modal-desktop-mode__accordion__description { + padding: 0 40px 40px 64px; +} + +.pera-wallet-connect-modal-desktop-mode__pera-download-qr-code { + display: flex; + + width: 264px; + height: 264px; + + margin: -12px auto 0; +} + +#pera-wallet-connect-modal-desktop-mode__qr-code { + width: 164px; + height: 164px; + + margin: 0 auto; + display: flex; + box-shadow: 0px 20px 60px rgba(26, 35, 91, 0.15), 0px 4px 12px rgba(26, 35, 91, 0.05), + 0px 1px 4px rgba(26, 35, 91, 0.06); + border-radius: 24px; +} diff --git a/src/modal/mode/touch-screen/PeraWalletConnectModalTouchScreenMode.tsx b/src/modal/mode/touch-screen/PeraWalletConnectModalTouchScreenMode.tsx new file mode 100644 index 0000000..a8b3903 --- /dev/null +++ b/src/modal/mode/touch-screen/PeraWalletConnectModalTouchScreenMode.tsx @@ -0,0 +1,80 @@ +import "./_pera-wallet-connect-modal-touch-screen-mode.scss"; + +import React, {useState} from "react"; + +import { + generatePeraWalletConnectDeepLink, + getPeraWalletAppMeta +} from "../../../util/peraWalletUtils"; +import PeraWalletConnectModalInformationSection from "../../section/information/PeraWalletConnectModalInformationSection"; +import PeraWalletConnectModalPendingMessage from "../../section/pending-message/PeraWalletConnectModalPendingMessage"; + +interface PeraWalletConnectModalTouchScreenModeProps { + uri: string; +} +function PeraWalletConnectModalTouchScreenMode({ + uri +}: PeraWalletConnectModalTouchScreenModeProps) { + const [view, setView] = useState("default" as "default" | "launching-app"); + const {name} = getPeraWalletAppMeta(); + + return ( +
    + {view === "launching-app" ? ( + + ) : ( + <> + + +
    + + {`Launch ${name}`} + + +
    +

    + {"New to Pera?"} +

    +
    + + + {`Install ${name}`} + +
    + + )} +
    + ); + + function handleChangeModalView() { + if (view === "default") { + setView("launching-app"); + } else if (view === "launching-app") { + setView("default"); + } + } +} + +export default PeraWalletConnectModalTouchScreenMode; diff --git a/src/modal/mode/touch-screen/_pera-wallet-connect-modal-touch-screen-mode.scss b/src/modal/mode/touch-screen/_pera-wallet-connect-modal-touch-screen-mode.scss new file mode 100644 index 0000000..5d4b7e3 --- /dev/null +++ b/src/modal/mode/touch-screen/_pera-wallet-connect-modal-touch-screen-mode.scss @@ -0,0 +1,74 @@ +.pera-wallet-connect-modal-touch-screen-mode { + display: grid; + grid-template-columns: 1fr; + gap: 46px; + + padding: 4px; + + &--pending-message-view { + gap: 56px; + grid-template-rows: auto 48px; + + height: 100%; + + padding-bottom: 70px; + } +} + +.pera-wallet-connect-modal-touch-screen-mode__launch-pera-wallet-button, +.pera-wallet-connect-modal-touch-screen-mode__install-pera-wallet-button { + display: block; + + padding: 14px; + + border-radius: 12px; + + text-decoration: none; + text-align: center; + font-size: 14px; + line-height: 20px; + letter-spacing: -0.09px; + font-weight: 500; +} + +.pera-wallet-connect-modal-touch-screen-mode__launch-pera-wallet-button { + margin-bottom: 32px; + + background-color: #6b46fe; + + color: #ffffff; +} + +.pera-wallet-connect-modal-touch-screen-mode__install-pera-wallet-button { + margin-bottom: 20px; + + background-color: #f2f3f8; + + color: #2c3559; +} + +.pera-wallet-connect-modal-touch-screen-mode__new-to-pera-box { + position: relative; + + margin-bottom: 32px; + + border-top: 1px solid #e6e8ee; +} + +.pera-wallet-connect-modal-touch-screen-mode__new-to-pera-box__text { + position: absolute; + top: -12px; + right: calc(50% - 56px); + + width: 116px; + + color: #69708d; + background-color: #ffffff; + + font-size: 13px; + font-weight: 500; + line-height: 24px; + letter-spacing: -0.04px; + + text-align: center; +} diff --git a/src/modal/peraWalletConnectModalUtils.tsx b/src/modal/peraWalletConnectModalUtils.tsx index d335b38..77fc587 100644 --- a/src/modal/peraWalletConnectModalUtils.tsx +++ b/src/modal/peraWalletConnectModalUtils.tsx @@ -1,8 +1,13 @@ +import PeraWalletLogoCircleYellow from "../asset/icon/PeraWallet--circle-yellow.svg"; +import PeraWalletLogoCircleBlack from "../asset/icon/PeraWallet--circle-black.svg"; + import React from "react"; import ReactDOM from "react-dom"; +import {QRCode} from "react-qrcode-logo"; import PeraWalletConnectModal from "./PeraWalletConnectModal"; import PeraWalletRedirectModal from "./redirect/PeraWalletRedirectModal"; +import {AccordionData} from "./component/accordion/util/accordionTypes"; // The ID of the wrapper element for PeraWalletConnectModal const PERA_WALLET_CONNECT_MODAL_ID = "pera-wallet-connect-modal-wrapper"; @@ -74,6 +79,59 @@ function removeModalWrapperFromDOM(modalId: string) { } } +function getPeraConnectModalAccordionData(uri: string): AccordionData[] { + return [ + { + id: "scan-to-connect", + title: "Scan to connect", + description: ( + <> +

    + {"Scan the QR code below with Pera Wallet's scan feature."} +

    + + + + ) + }, + { + id: "new-to-pera-wallet", + title: "New to Pera Wallet?", + description: ( + <> +

    + {"Scan the QR code with your phone to download Pera Wallet."} +

    + + + + ) + } + ]; +} + +export {getPeraConnectModalAccordionData}; + export { PERA_WALLET_CONNECT_MODAL_ID, PERA_WALLET_REDIRECT_MODAL_ID, diff --git a/src/modal/section/information/PeraWalletConnectModalInformationSection.tsx b/src/modal/section/information/PeraWalletConnectModalInformationSection.tsx new file mode 100644 index 0000000..3cbc5d3 --- /dev/null +++ b/src/modal/section/information/PeraWalletConnectModalInformationSection.tsx @@ -0,0 +1,95 @@ +import ShieldTickIcon from "../../../asset/icon/ShieldTick.svg"; +import LayerIcon from "../../../asset/icon/Layer.svg"; +import NoteIcon from "../../../asset/icon/Note.svg"; +import PeraWalletWithText from "../../../asset/icon/PeraWallet--with-text.svg"; + +import "./_pera-wallet-modal-information-section.scss"; + +import React from "react"; + +import {useIsSmallScreen} from "../../../util/screen/useMediaQuery"; +import {getPeraWalletAppMeta} from "../../../util/peraWalletUtils"; + +function PeraWalletConnectModalInformationSection() { + const isSmallScreen = useIsSmallScreen(); + const {logo, name} = getPeraWalletAppMeta(); + + return ( +
    + {"Pera + + {isSmallScreen && ( +

    + {`Connect to ${name}`} +

    + )} + +

    + {"Simply the best Algorand wallet."} +

    + + {!isSmallScreen && ( +

    + {"Features"} +

    + )} + +
      +
    • +
      + {"Layer +
      + +

      + {"Connect to any Algorand dApp securely"} +

      +
    • + +
    • +
      + {"Tick +
      + +

      + {"Your private keys are safely stored locally"} +

      +
    • + +
    • +
      + {"Note +
      + +

      + {"View NFTs, buy and swap crypto and more"} +

      +
    • +
    +
    + ); +} + +export default PeraWalletConnectModalInformationSection; diff --git a/src/modal/section/information/_pera-wallet-modal-information-section.scss b/src/modal/section/information/_pera-wallet-modal-information-section.scss new file mode 100644 index 0000000..f63fb45 --- /dev/null +++ b/src/modal/section/information/_pera-wallet-modal-information-section.scss @@ -0,0 +1,102 @@ +@import "../../../ui/styles/media-queries"; + +.pera-wallet-connect-modal-information-section { + padding: 12px; + padding-right: 0; +} + +.pera-wallet-connect-modal-information-section__pera-icon { + margin-bottom: 32px; +} + +.pera-wallet-connect-modal-information-section__title { + margin-bottom: 82px; + + color: #2c3559; + + font-size: 22px; + line-height: 30px; + letter-spacing: -0.4px; + font-weight: 500; +} + +.pera-wallet-connect-modal-information-section__secondary-title { + margin-bottom: 16px; + + color: #69708d; + + font-size: 10px; + line-height: 20px; + letter-spacing: 2px; + font-weight: 500; + text-transform: uppercase; +} + +.pera-wallet-connect-modal-information-section__features-item { + display: grid; + align-items: center; + grid-template-columns: 36px auto; + gap: 16px; + + &:not(:last-of-type) { + margin-bottom: 20px; + } +} + +.pera-wallet-connect-modal-information-section__features-item__icon-wrapper { + display: flex; + align-items: center; + justify-content: center; + + width: 36px; + height: 36px; + + background-color: #ffffff; + + border-radius: 50%; +} + +.pera-wallet-connect-modal-information-section__features-item__description { + color: #2c3559; + + font-size: 12px; + line-height: 20px; + letter-spacing: 0.01px; + font-weight: 500; +} + +@include for-small-screens { + .pera-wallet-connect-modal-information-section__information-section { + padding: 0; + } + + .pera-wallet-connect-modal-information-section__pera-icon { + margin-bottom: 16px; + } + + .pera-wallet-connect-modal-information-section__connect-pera-title { + margin-bottom: 8px; + + color: #2c3559; + + font-size: 18px; + font-weight: 600; + line-height: 22px; + letter-spacing: -0.2px; + } + + .pera-wallet-connect-modal-information-section__title { + margin-bottom: 40px; + + color: #2c3559; + + font-size: 14px; + line-height: 24px; + letter-spacing: -0.09px; + font-weight: 400; + } + + .pera-wallet-connect-modal-information-section__features-item__icon-wrapper { + background-color: #f2f3f8; + } +} diff --git a/src/modal/section/pending-message/PeraWalletConnectModalPendingMessage.tsx b/src/modal/section/pending-message/PeraWalletConnectModalPendingMessage.tsx new file mode 100644 index 0000000..08e4721 --- /dev/null +++ b/src/modal/section/pending-message/PeraWalletConnectModalPendingMessage.tsx @@ -0,0 +1,43 @@ +import "./_pera-wallet-connect-modal-pending-message.scss"; + +import React from "react"; + +import {getPeraWalletAppMeta} from "../../../util/peraWalletUtils"; + +interface PeraWalletConnectModalPendingMessageProps { + onClose: () => void; +} + +function PeraWalletConnectModalPendingMessage({ + onClose +}: PeraWalletConnectModalPendingMessageProps) { + const {logo, name} = getPeraWalletAppMeta(); + + return ( + <> +
    + {"Pera + +
    + {`Please wait while we connect you to`} + + {` ${name}...`} +
    +
    + + + + ); + + function handleCancelClick() { + onClose(); + } +} + +export default PeraWalletConnectModalPendingMessage; diff --git a/src/modal/section/pending-message/_pera-wallet-connect-modal-pending-message.scss b/src/modal/section/pending-message/_pera-wallet-connect-modal-pending-message.scss new file mode 100644 index 0000000..edb2d8b --- /dev/null +++ b/src/modal/section/pending-message/_pera-wallet-connect-modal-pending-message.scss @@ -0,0 +1,41 @@ +.pera-wallet-connect-modal-pending-message { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + + text-align: center; +} + +.pera-wallet-connect-modal-pending-message__text { + max-width: 271px; + + margin-top: 20px; + + color: #2c3559; + + font-size: 18px; + font-weight: 500; + line-height: 28px; + letter-spacing: -0.26px; +} + +.pera-wallet-connect-modal-pending-message__cancel-button { + display: block; + + width: 100%; + + padding: 14px; + + color: #2c3559; + background-color: #f2f3f8; + + border-radius: 12px; + + text-decoration: none; + text-align: center; + font-size: 14px; + line-height: 20px; + letter-spacing: -0.09px; + font-weight: 500; +} diff --git a/src/ui/styles/_media-queries.scss b/src/ui/styles/_media-queries.scss new file mode 100644 index 0000000..687ab56 --- /dev/null +++ b/src/ui/styles/_media-queries.scss @@ -0,0 +1,8 @@ +$small-max-width: 767px; +$small-screen-query: "(max-width: " + $small-max-width + ")"; + +@mixin for-small-screens { + @media #{$small-screen-query} { + @content; + } +} diff --git a/src/util/screen/mediaQueryHookConstants.ts b/src/util/screen/mediaQueryHookConstants.ts new file mode 100644 index 0000000..912fde4 --- /dev/null +++ b/src/util/screen/mediaQueryHookConstants.ts @@ -0,0 +1,7 @@ +import {SMALL_SCREEN_BREAKPOINT} from "./screenSizeConstants"; + +const MEDIA_QUERIES = { + SMALL: `(max-width: ${SMALL_SCREEN_BREAKPOINT}px)` +}; + +export {MEDIA_QUERIES}; diff --git a/src/util/screen/useMediaQuery.ts b/src/util/screen/useMediaQuery.ts new file mode 100644 index 0000000..f3ec4dc --- /dev/null +++ b/src/util/screen/useMediaQuery.ts @@ -0,0 +1,45 @@ +import {useState, useEffect} from "react"; + +import {MEDIA_QUERIES} from "./mediaQueryHookConstants"; + +// Originated from : https://github.com/beautifulinteractions/beautiful-react-hooks/blob/master/src/useMediaQuery.js + +function useMediaQuery(mediaQuery: string) { + const isMatchMediaAPISupported = "matchMedia" in window; + const [isMediaMatchingQuery, setIsMediaMatchingQuery] = useState( + isMatchMediaAPISupported && Boolean(window.matchMedia(mediaQuery).matches) + ); + + useEffect(() => { + let mediaQueryList: MediaQueryList; + let mediaQueryListEventHandler: VoidFunction; + + if (isMatchMediaAPISupported) { + mediaQueryList = window.matchMedia(mediaQuery); + mediaQueryListEventHandler = function () { + setIsMediaMatchingQuery(Boolean(mediaQueryList.matches)); + }; + + if (mediaQueryList) { + // Although addListener() is marked as deprecated, it is used here for backward compatibility (for example Safari 13) + mediaQueryList.addListener(mediaQueryListEventHandler); + } + } + + return () => { + if (isMatchMediaAPISupported && mediaQueryListEventHandler && mediaQueryList) { + // Although removeListener() is marked as deprecated, it is used here for backward compatibility (for example Safari 13) + mediaQueryList.removeListener(mediaQueryListEventHandler); + } + }; + }, [mediaQuery, isMatchMediaAPISupported]); + + return isMediaMatchingQuery; +} + +function useIsSmallScreen() { + return useMediaQuery(MEDIA_QUERIES.SMALL); +} + +export default useMediaQuery; +export {useIsSmallScreen}; diff --git a/src/util/screen/useOnWindowResize.tsx b/src/util/screen/useOnWindowResize.tsx new file mode 100644 index 0000000..df85043 --- /dev/null +++ b/src/util/screen/useOnWindowResize.tsx @@ -0,0 +1,37 @@ +import {useEffect, useRef} from "react"; + +const DEFAULT_DEBOUNCE_TIME = 150; + +const DEFAULT_OPTIONS = { + /* Discard emitted resize events that take less than the specified time */ + debounceTime: DEFAULT_DEBOUNCE_TIME +}; + +function useOnWindowResize(callback: VoidFunction, options = DEFAULT_OPTIONS) { + const timeoutId = useRef(undefined); + const callbackRef = useRef(callback); + + useEffect(() => { + callbackRef.current = callback; + }, [callback]); + + useEffect(() => { + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + clearTimeout(timeoutId.current); + }; + + function handleResize() { + if (!timeoutId.current) { + timeoutId.current = setTimeout(() => { + callbackRef.current(); + timeoutId.current = undefined; + }, options.debounceTime); + } + } + }, [options.debounceTime]); +} + +export default useOnWindowResize; diff --git a/src/util/screen/useSetDynamicVhValue.tsx b/src/util/screen/useSetDynamicVhValue.tsx new file mode 100644 index 0000000..a71077b --- /dev/null +++ b/src/util/screen/useSetDynamicVhValue.tsx @@ -0,0 +1,28 @@ +import {useEffect} from "react"; + +import useOnWindowResize from "./useOnWindowResize"; + +/** + * Creates a css variable `--vh` that is the calculated viewport height, + * and updates it on window resize. This `--vh` value can be used instead of + * default `vh` value to prevent layout issues on mobile devices + * See: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ + */ +function useSetDynamicVhValue() { + useEffect(() => { + // This useEffect was added to make sure vh is set on mount (even if there is no window resize) + setVhVariable(); + }, []); + + useOnWindowResize(() => { + setVhVariable(); + }); + + function setVhVariable() { + // a vh unit is equal to 1% of the screen height + // eslint-disable-next-line no-magic-numbers + document.documentElement.style.setProperty("--vh", `${window.innerHeight * 0.01}px`); + } +} + +export default useSetDynamicVhValue;