From 6262267ff11fdeaa3f0d995fb5f286debcbbfd17 Mon Sep 17 00:00:00 2001 From: Pete Watters <2938440+pete-watters@users.noreply.github.com> Date: Tue, 5 Dec 2023 12:10:58 +0000 Subject: [PATCH] chore: replace drawer dialog, containers and global header footers, onboarding, settings, ref #4371 --- .storybook/main.ts | 6 + .storybook/preview.ts | 7 + .storybook/viewports.ts | 46 +++ README.md | 10 +- package.json | 5 +- pnpm-lock.yaml | 40 +- public/html/popup-center.html | 12 - src/app/app.tsx | 29 +- src/app/common/hooks/use-drawers.ts | 26 -- src/app/common/hooks/use-event-listener.ts | 74 ---- .../common/{utils => hooks}/use-interval.ts | 0 src/app/common/hooks/use-latest-ref.ts | 17 - src/app/common/hooks/use-media-query.ts | 23 -- src/app/common/hooks/use-route-header.ts | 16 - .../{utils => hooks}/use-waiting-message.ts | 0 src/app/common/utils.ts | 5 +- src/app/common/utils/copy-to-clipboard.ts | 3 + .../components/account/account-addresses.tsx | 5 +- src/app/components/app-version.tsx | 29 +- .../bitcoin-contract-entry-point-layout.tsx | 4 +- .../bitcoin-custom-fee/bitcoin-custom-fee.tsx | 11 +- .../components/fees-list-item.tsx | 2 +- .../broadcast-error-dialog.tsx | 50 +++ .../broadcast-error-drawer.layout.tsx | 36 -- .../broadcast-error-drawer.tsx | 17 - .../components/centered-page-container.tsx | 15 - .../brc20-token-asset-list.tsx | 9 +- .../brc20-token-asset-item.layout.tsx | 46 +-- .../brc20-token-asset-list.layout.tsx | 10 - .../choose-asset-container.tsx | 25 -- .../choose-crypto-asset.layout.tsx | 22 -- .../crypto-asset-list.layout.tsx | 10 - .../choose-crypto-asset/crypto-asset-list.tsx | 40 +- src/app/components/disclaimer.tsx | 2 +- src/app/components/drawer/base-drawer.tsx | 127 ------ .../drawer/components/drawer-header.tsx | 56 --- .../components/header-action-button.tsx | 34 -- .../components/drawer/controlled-drawer.tsx | 30 -- .../generic-error/generic-error.tsx | 5 - src/app/components/header.tsx | 86 ---- src/app/components/info-card/info-card.tsx | 15 +- .../components/inscription-metadata.tsx | 4 +- src/app/components/leather-logo.tsx | 23 -- src/app/components/modal-header.tsx | 86 ---- src/app/components/network-mode-badge.tsx | 33 -- src/app/components/no-fees-warning-row.tsx | 4 +- src/app/components/preview-button.tsx | 24 -- src/app/components/request-password.tsx | 122 +++--- src/app/components/requester-flag.tsx | 3 +- .../components/secret-key/secret-key-grid.tsx | 24 -- .../secret-key/two-column.layout.tsx | 56 --- .../increase-fee-button.tsx | 10 +- src/app/components/status-ready.tsx | 2 +- .../activity-list/components/tab-wrapper.tsx | 3 +- .../features/add-network/add-network-form.tsx | 134 +++++++ src/app/features/add-network/add-network.tsx | 290 +------------- .../features/add-network/use-add-network.tsx | 146 +++++++ src/app/features/asset-list/asset-list.tsx | 2 +- .../bitcoin-choose-fee/bitcoin-choose-fee.tsx | 4 +- .../components/choose-fee-subtitle.tsx | 2 +- .../features/collectibles/collectibles.tsx | 2 +- .../_collectible-types/collectible-other.tsx | 2 +- .../collectible-text.layout.tsx | 2 +- ...ibes.layout.tsx => collectible.layout.tsx} | 16 +- .../components/taproot-balance-displayer.tsx | 2 +- .../features/container/container.layout.tsx | 18 - src/app/features/container/container.tsx | 157 +++++++- src/app/features/container/total-balance.tsx | 57 +++ .../container/utils/get-popup-header.ts | 83 ++++ .../container/utils/get-title-from-url.ts | 36 ++ .../features/container/utils/route-helpers.ts | 80 ++++ .../current-account-avatar.tsx | 13 +- .../features/current-account/popup-header.tsx | 69 ---- .../components/edit-nonce-field.tsx | 0 .../components/edit-nonce-form.tsx | 0 .../edit-nonce-dialog/edit-nonce-dialog.tsx} | 28 +- .../high-fee-dialog/high-fee-dialog.tsx | 77 ++++ .../components/fee-multiplier-button.tsx | 0 .../components/fee-multiplier.tsx | 0 .../components/increase-fee-actions.tsx | 9 +- .../components/increase-fee-field.tsx | 2 +- .../hooks/use-btc-increase-fee.ts | 0 .../hooks/use-selected-tx.ts | 0 .../increase-btc-fee-dialog.tsx | 110 ++++++ .../increase-fee-sent-dialog.tsx} | 13 +- .../increase-stx-fee-dialog.tsx | 152 ++++++++ .../leather-intro-dialog/confetti-config.ts | 0 .../leather-intro-dialog.tsx | 5 - .../leather-intro-steps.tsx | 2 +- .../components/account-list-unavailable.tsx | 0 .../components/switch-account-list-item.tsx | 3 +- .../switch-account-dialog.tsx | 83 ++++ .../features/errors/app-error-boundary.tsx | 4 - .../components/high-fee-confirmation.tsx | 48 --- .../high-fee-drawer/high-fee-drawer.tsx | 28 -- .../components/in-app-message-item.tsx | 2 +- .../hiro-messages/in-app-messages.tsx | 1 + src/app/features/html-head/head-provider.tsx | 14 +- .../components/increase-btc-fee-form.tsx | 71 ---- .../components/increase-stx-fee-form.tsx | 98 ----- .../increase-btc-fee-drawer.tsx | 35 -- .../increase-fee-drawer.tsx | 38 -- .../increase-stx-fee-drawer.tsx | 41 -- .../ledger/components/ledger-wrapper.tsx | 2 +- .../jwt-signing/ledger-sign-jwt-container.tsx | 16 +- .../ledger-stacks-sign-msg-container.tsx | 21 +- .../request-keys/request-keys-flow.tsx | 11 +- .../tx-signing/tx-signing-flow.tsx | 21 +- .../connect-ledger-error.layout.tsx | 2 +- .../connect-device/connect-ledger-start.tsx | 7 +- .../connect-device/connect-ledger.tsx | 4 +- .../unsupported-browser.layout.tsx | 20 +- .../features/message-signer/hash-drawer.tsx | 5 +- .../message-signer/message-preview-box.tsx | 7 +- .../features/psbt-signer/components/index.ts | 9 + .../psbt-input-output-item.layout.tsx | 2 +- .../components/psbt-request-actions.tsx | 30 +- .../psbt-request-details-section.layout.tsx | 10 +- .../components/psbt-request-fee.tsx | 2 +- .../components/psbt-request-header.tsx | 2 +- .../components/psbt-signer.layout.tsx | 19 - src/app/features/psbt-signer/psbt-signer.tsx | 78 ++-- ...trieve-taproot-to-native-segwit.layout.tsx | 31 +- .../secret-key-displayer.tsx | 8 +- .../components/settings-menu-item.tsx | 27 -- .../components/settings-menu-wrapper.tsx | 27 -- .../settings-dropdown/settings-dropdown.tsx | 170 -------- .../components/advanced-menu-items.tsx | 27 +- .../components/ledger-item-row.tsx | 0 .../components/network-list-item.layout.tsx | 18 +- .../settings/network}/network-list-item.tsx | 0 src/app/features/settings/network/network.tsx | 78 ++++ src/app/features/settings/settings.tsx | 214 ++++++++++ .../settings/sign-out/sign-out-confirm.tsx | 36 ++ .../features/settings/sign-out/sign-out.tsx | 105 +++++ .../theme/theme-dialog.tsx} | 14 +- .../theme/theme-list-item.tsx} | 22 +- .../components/nested-tuple-displayer.tsx | 2 +- .../components/structured-data-box.tsx | 4 +- .../stacks-message-signing.tsx | 3 - .../contract-call-details.tsx | 1 + .../hooks/use-stacks-transaction-summary.ts | 1 - .../stacks-transaction-signer.tsx | 17 +- .../submit-action.tsx | 10 +- .../transaction-error/error-messages.tsx | 12 +- .../components/create-account-action.tsx | 25 -- .../components/switch-account-list.tsx | 37 -- .../switch-account-drawer.tsx | 55 --- .../features/theme-drawer/theme-drawer.tsx | 18 - .../features/theme-drawer/theme-list-item.tsx | 25 -- .../bitcoin-contract-list.tsx | 7 +- .../bitcoin-contract-list-item-layout.tsx | 2 +- .../bitcoin-contract-list-layout.tsx | 22 -- .../bitcoin-contract-request.tsx | 75 ++-- .../bitcoin-contract-request-actions.tsx | 50 +-- .../bitcoin-contract-request-layout.tsx | 20 - .../pages/choose-account/choose-account.tsx | 4 +- .../choose-account/components/accounts.tsx | 77 ++-- .../choose-asset-to-fund.tsx | 37 +- .../fund/components/fund-account-tile.tsx | 11 +- src/app/pages/fund/components/fund.layout.tsx | 56 ++- .../pages/fund/components/icon-components.tsx | 4 +- .../fund/components/receive-funds-item.tsx | 2 +- src/app/pages/fund/fiat-providers-list.tsx | 20 +- src/app/pages/fund/fund.tsx | 8 +- .../pages/home/components/account-actions.tsx | 14 +- .../pages/home/components/account-area.tsx | 28 -- .../home/components/account-info-card.tsx | 67 ---- .../pages/home/components/action-button.tsx | 21 - src/app/pages/home/components/home-tabs.tsx | 10 +- src/app/pages/home/components/home.layout.tsx | 31 -- src/app/pages/home/components/send-button.tsx | 11 +- src/app/pages/home/home.tsx | 43 +- .../allow-diagnostics-layout.tsx | 97 ++--- .../allow-diagnostics/allow-diagnostics.tsx | 4 - .../back-up-secret-key/back-up-secret-key.tsx | 48 ++- .../components/back-up-secret-key.content.tsx | 41 -- .../set-password/components/password-bars.tsx | 28 +- .../onboarding/set-password/set-password.tsx | 64 ++- .../sign-in/components/sign-in.content.tsx | 26 -- .../onboarding/sign-in/mnemonic-form.tsx | 47 ++- src/app/pages/onboarding/sign-in/sign-in.tsx | 53 ++- .../onboarding/welcome/welcome.layout.tsx | 128 ------ src/app/pages/onboarding/welcome/welcome.tsx | 7 +- .../receive/components/address-qr-code.tsx | 34 -- .../components/receive-btc-warning.tsx | 2 +- .../components/receive-collectibles.tsx | 66 ++++ .../pages/receive/components/receive-item.tsx | 14 +- .../receive/components/receive-items.tsx | 21 - .../components/receive-tokens.layout.tsx | 58 ++- .../receive/components/receive-tokens.tsx | 52 +++ src/app/pages/receive/receive-btc.tsx | 16 +- src/app/pages/receive/receive-dialog.tsx | 134 +++++++ src/app/pages/receive/receive-modal.tsx | 152 -------- src/app/pages/receive/receive-ordinal.tsx | 14 +- src/app/pages/receive/receive-stx.tsx | 15 +- .../components/get-addresses.layout.tsx | 2 +- .../components/send-transfer-actions.tsx | 18 - .../components/send-transfer-footer.tsx | 29 -- .../rpc-send-transfer-confirmation.tsx | 19 +- .../rpc-send-transfer-container.tsx | 5 - .../rpc-send-transfer/rpc-send-transfer.tsx | 9 +- .../rpc-sign-bip322-message.tsx | 4 - .../pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx | 1 - .../components/add-network-button.tsx | 17 - .../components/network-list.layout.tsx | 9 - .../components/network-status-indicator.tsx | 10 - .../pages/select-network/select-network.tsx | 67 ---- .../choose-crypto-asset.tsx | 39 +- .../locked-bitcoin-summary.tsx | 4 - .../hooks/use-send-inscription-form.tsx | 1 + .../send-inscription-choose-fee.tsx | 13 +- .../send-inscription-form.tsx | 67 ++-- .../send-inscription-review.tsx | 12 +- .../sent-inscription-summary.tsx | 11 +- src/app/pages/send/send-container.tsx | 30 -- .../components/form-footer.tsx | 30 -- .../account-list-item.tsx | 2 +- .../recipient-accounts-dialog.tsx | 48 +++ .../select-account-button.tsx | 0 .../recipient-accounts-drawer.tsx | 44 --- .../recipient-address-displayer.tsx | 2 +- .../recipient-fields/recipient-field.tsx | 2 +- .../recipient-type-dropdown.tsx | 2 +- .../send-crypto-asset-form.layout.tsx | 23 -- .../form/brc-20/brc-20-choose-fee.tsx | 9 - .../brc-20/brc20-send-form-confirmation.tsx | 5 - .../form/brc-20/brc20-send-form.tsx | 87 +++-- .../form/btc/btc-choose-fee.tsx | 6 +- .../form/btc/btc-send-form-confirmation.tsx | 5 - .../form/btc/btc-send-form.tsx | 100 +++-- .../form/btc/use-btc-choose-fee.tsx | 10 - .../form/send-form-confirmation.tsx | 6 +- .../form/stacks/stacks-common-send-form.tsx | 73 ++-- .../stacks/stacks-send-form-confirmation.tsx | 14 +- .../stacks/use-stacks-common-send-form.tsx | 10 +- .../form/stx/stx-send-form.tsx | 4 + .../form/stx/use-stx-send-form.tsx | 4 + .../hooks/use-send-form-navigate.ts | 4 - .../send-crypto-asset-form.routes.tsx | 52 +-- .../send/sent-summary/brc20-sent-summary.tsx | 4 - .../send/sent-summary/btc-sent-summary.tsx | 4 - .../send/sent-summary/stx-sent-summary.tsx | 12 +- .../sign-out-confirm.layout.tsx | 115 ------ .../sign-out-confirm/sign-out-confirm.tsx | 27 -- src/app/pages/swap/alex-swap-container.tsx | 6 +- .../swap/components/swap-amount-field.tsx | 2 +- .../swap-asset-item.layout.tsx | 2 +- .../components/swap-asset-item.tsx | 4 +- .../components/swap-asset-list.tsx | 10 +- .../swap-choose-asset/swap-choose-asset.tsx | 45 +++ .../swap/components/swap-container.layout.tsx | 25 -- .../swap/components/swap-content.layout.tsx | 22 -- .../{swap-error => components}/swap-error.tsx | 0 .../swap/components/swap-footer.layout.tsx | 27 -- src/app/pages/swap/components/swap-review.tsx | 44 +++ .../components/swap-selected-asset.layout.tsx | 4 +- src/app/pages/swap/generate-swap-routes.tsx | 6 +- .../components/swap-asset-list.layout.tsx | 9 - .../swap-choose-asset/swap-choose-asset.tsx | 49 --- .../swap/swap-review/swap-review.layout.tsx | 11 - .../pages/swap/swap-review/swap-review.tsx | 45 --- src/app/pages/swap/swap.tsx | 40 +- .../transaction-request.tsx | 28 +- src/app/pages/unlock.tsx | 41 +- .../update-profile-request.layout.tsx | 7 +- .../update-profile-request.tsx | 4 - .../components/view-secret-key.content.tsx | 24 -- .../pages/view-secret-key/view-secret-key.tsx | 46 +-- src/app/routes/app-routes.tsx | 32 +- src/app/routes/receive-routes.tsx | 6 +- src/app/routes/request-routes.tsx | 8 +- src/app/routes/settings-routes.tsx | 15 - src/app/store/settings/settings.selectors.ts | 20 +- src/app/store/settings/settings.slice.ts | 9 - src/app/store/ui/ui.hooks.ts | 30 +- src/app/store/ui/ui.ts | 11 - .../account-avatar}/account-avatar-item.tsx | 2 +- .../account-avatar}/account-avatar.tsx | 0 .../account/account.card.stories.tsx | 33 ++ .../ui/components/account/account.card.tsx | 57 +++ .../bullet-separator/bullet-separator.tsx | 2 +- .../ui/components/button/button.stories.tsx | 28 -- src/app/ui/components/button/button.tsx | 9 +- src/app/ui/components/callout/callout.tsx | 2 +- .../containers/container.layout.tsx | 25 ++ .../containers/dialog/dialog.stories.tsx | 26 ++ .../components/containers/dialog/dialog.tsx | 87 +++++ .../containers/footers}/available-balance.tsx | 18 +- .../containers/footers/footer.stories.tsx | 134 +++++++ .../components/containers/footers/footer.tsx | 31 ++ .../headers/components/big-title-header.tsx | 28 ++ .../components/header-action-button.tsx | 34 ++ .../headers/components/network-mode-badge.tsx | 18 + .../containers/headers/header.stories.tsx | 22 ++ .../components/containers/headers/header.tsx | 93 +++++ .../dropdown-menu-item.layout.tsx | 0 .../dropdown-menu.stories.tsx | 0 .../dropdown-menu.tsx | 36 +- .../icon-button}/accessible-icon.tsx | 4 +- .../icon-button/icon-button.stories.tsx | 30 ++ .../ui/components/icon-button/icon-button.tsx | 31 ++ .../ui/components/item-layout/item-layout.tsx | 14 +- src/app/ui/components/link/link.stories.tsx | 14 - src/app/ui/components/link/link.tsx | 2 +- src/app/ui/components/logo.tsx | 20 + .../mnemonic-key/mnemonic-word-input.tsx | 0 .../mnemonic-key/utils/error-handling.ts | 0 .../mnemonic-key/utils/validation.ts | 0 .../components/secret-key/secret-key-grid.tsx | 20 + .../secret-key}/secret-key-word.tsx | 2 +- .../secret-key/secret-key.layout.tsx} | 47 ++- src/app/ui/components/typography/caption.tsx | 2 +- src/app/ui/layout/card/card-content.tsx | 27 ++ src/app/ui/layout/card/card.stories.tsx | 28 ++ src/app/ui/layout/card/card.tsx | 19 + .../ui/layout/page/page.layout.stories.tsx | 21 + src/app/ui/layout/page/page.layout.tsx | 16 + src/app/ui/pages/home.layout.stories.tsx | 51 +++ src/app/ui/pages/home.layout.tsx | 31 ++ .../ui/pages/two-column.layout.stories.tsx | 20 + src/app/ui/pages/two-column.layout.tsx | 55 +++ src/app/ui/pages/welcome.layout.tsx | 143 +++++++ src/app/ui/shared/virtuoso.ts | 8 + src/app/ui/utils/prism.tsx | 2 +- src/background/messaging/messaging-utils.ts | 4 +- src/background/{popup-center.ts => popup.ts} | 26 +- src/shared/constants.ts | 3 - src/shared/route-urls.ts | 7 +- src/shared/utils/px-string-to-number.spec.ts | 12 + src/shared/utils/px-string-to-number.ts | 3 + test-app/src/components/app.tsx | 19 +- test-app/src/components/bns.tsx | 2 + test-app/src/components/counter-actions.tsx | 30 +- test-app/src/components/header.tsx | 32 -- tests/page-object-models/home.page.ts | 35 +- tests/page-object-models/network.page.ts | 22 +- tests/page-object-models/onboarding.page.ts | 1 - tests/page-object-models/send.page.ts | 2 +- tests/selectors/home.selectors.ts | 3 +- tests/selectors/network.selectors.ts | 4 +- tests/selectors/onboarding.selectors.ts | 2 +- tests/selectors/requests.selectors.ts | 8 + tests/selectors/settings.selectors.ts | 5 +- tests/selectors/shared-component.selectors.ts | 5 +- tests/specs/network/add-network.spec.ts | 18 +- tests/specs/send/send-btc.spec.ts | 2 +- tests/specs/send/send-stx.spec.ts | 366 +++++++++--------- ...settings-menu.spec.ts => settings.spec.ts} | 2 + theme/global/full-page-styles.ts | 14 - theme/global/global.ts | 47 ++- theme/global/popup-center-styles.ts | 8 - theme/global/popup-styles.ts | 24 -- theme/recipes/button-recipe.ts | 4 +- theme/recipes/link-recipe.ts | 1 - theme/tokens.ts | 14 +- theme/typography.ts | 4 +- webpack/webpack.config.base.js | 5 - 358 files changed, 4990 insertions(+), 5113 deletions(-) create mode 100644 .storybook/viewports.ts delete mode 100644 public/html/popup-center.html delete mode 100644 src/app/common/hooks/use-drawers.ts delete mode 100644 src/app/common/hooks/use-event-listener.ts rename src/app/common/{utils => hooks}/use-interval.ts (100%) delete mode 100644 src/app/common/hooks/use-latest-ref.ts delete mode 100644 src/app/common/hooks/use-media-query.ts delete mode 100644 src/app/common/hooks/use-route-header.ts rename src/app/common/{utils => hooks}/use-waiting-message.ts (100%) create mode 100644 src/app/common/utils/copy-to-clipboard.ts create mode 100644 src/app/components/broadcast-error-dialog/broadcast-error-dialog.tsx delete mode 100644 src/app/components/broadcast-error-drawer/broadcast-error-drawer.layout.tsx delete mode 100644 src/app/components/broadcast-error-drawer/broadcast-error-drawer.tsx delete mode 100644 src/app/components/centered-page-container.tsx delete mode 100644 src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-list.layout.tsx delete mode 100644 src/app/components/crypto-assets/choose-crypto-asset/choose-asset-container.tsx delete mode 100644 src/app/components/crypto-assets/choose-crypto-asset/choose-crypto-asset.layout.tsx delete mode 100644 src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx delete mode 100644 src/app/components/drawer/base-drawer.tsx delete mode 100644 src/app/components/drawer/components/drawer-header.tsx delete mode 100644 src/app/components/drawer/components/header-action-button.tsx delete mode 100644 src/app/components/drawer/controlled-drawer.tsx delete mode 100644 src/app/components/header.tsx delete mode 100644 src/app/components/leather-logo.tsx delete mode 100644 src/app/components/modal-header.tsx delete mode 100644 src/app/components/network-mode-badge.tsx delete mode 100644 src/app/components/preview-button.tsx delete mode 100644 src/app/components/secret-key/secret-key-grid.tsx delete mode 100644 src/app/components/secret-key/two-column.layout.tsx create mode 100644 src/app/features/add-network/add-network-form.tsx create mode 100644 src/app/features/add-network/use-add-network.tsx rename src/app/features/collectibles/components/{collectibes.layout.tsx => collectible.layout.tsx} (79%) delete mode 100644 src/app/features/container/container.layout.tsx create mode 100644 src/app/features/container/total-balance.tsx create mode 100644 src/app/features/container/utils/get-popup-header.ts create mode 100644 src/app/features/container/utils/get-title-from-url.ts create mode 100644 src/app/features/container/utils/route-helpers.ts delete mode 100644 src/app/features/current-account/popup-header.tsx rename src/app/features/{edit-nonce-drawer => dialogs/edit-nonce-dialog}/components/edit-nonce-field.tsx (100%) rename src/app/features/{edit-nonce-drawer => dialogs/edit-nonce-dialog}/components/edit-nonce-form.tsx (100%) rename src/app/features/{edit-nonce-drawer/edit-nonce-drawer.tsx => dialogs/edit-nonce-dialog/edit-nonce-dialog.tsx} (76%) create mode 100644 src/app/features/dialogs/high-fee-dialog/high-fee-dialog.tsx rename src/app/features/{increase-fee-drawer => dialogs/increase-fee-dialog}/components/fee-multiplier-button.tsx (100%) rename src/app/features/{increase-fee-drawer => dialogs/increase-fee-dialog}/components/fee-multiplier.tsx (100%) rename src/app/features/{increase-fee-drawer => dialogs/increase-fee-dialog}/components/increase-fee-actions.tsx (85%) rename src/app/features/{increase-fee-drawer => dialogs/increase-fee-dialog}/components/increase-fee-field.tsx (98%) rename src/app/features/{increase-fee-drawer => dialogs/increase-fee-dialog}/hooks/use-btc-increase-fee.ts (100%) rename src/app/features/{increase-fee-drawer => dialogs/increase-fee-dialog}/hooks/use-selected-tx.ts (100%) create mode 100644 src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx rename src/app/features/{increase-fee-drawer/increase-fee-sent-drawer.tsx => dialogs/increase-fee-dialog/increase-fee-sent-dialog.tsx} (60%) create mode 100644 src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx rename src/app/features/{ => dialogs}/leather-intro-dialog/confetti-config.ts (100%) rename src/app/features/{ => dialogs}/leather-intro-dialog/leather-intro-dialog.tsx (90%) rename src/app/features/{ => dialogs}/leather-intro-dialog/leather-intro-steps.tsx (98%) rename src/app/features/{switch-account-drawer => dialogs/switch-account-dialog}/components/account-list-unavailable.tsx (100%) rename src/app/features/{switch-account-drawer => dialogs/switch-account-dialog}/components/switch-account-list-item.tsx (95%) create mode 100644 src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx delete mode 100644 src/app/features/high-fee-drawer/components/high-fee-confirmation.tsx delete mode 100644 src/app/features/high-fee-drawer/high-fee-drawer.tsx delete mode 100644 src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx delete mode 100644 src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx delete mode 100644 src/app/features/increase-fee-drawer/increase-btc-fee-drawer.tsx delete mode 100644 src/app/features/increase-fee-drawer/increase-fee-drawer.tsx delete mode 100644 src/app/features/increase-fee-drawer/increase-stx-fee-drawer.tsx create mode 100644 src/app/features/psbt-signer/components/index.ts delete mode 100644 src/app/features/psbt-signer/components/psbt-signer.layout.tsx delete mode 100644 src/app/features/settings-dropdown/components/settings-menu-item.tsx delete mode 100644 src/app/features/settings-dropdown/components/settings-menu-wrapper.tsx delete mode 100644 src/app/features/settings-dropdown/settings-dropdown.tsx rename src/app/features/{settings-dropdown => settings}/components/advanced-menu-items.tsx (77%) rename src/app/features/{settings-dropdown => settings}/components/ledger-item-row.tsx (100%) rename src/app/{pages/select-network => features/settings/network}/components/network-list-item.layout.tsx (79%) rename src/app/{pages/select-network => features/settings/network}/network-list-item.tsx (100%) create mode 100644 src/app/features/settings/network/network.tsx create mode 100644 src/app/features/settings/settings.tsx create mode 100644 src/app/features/settings/sign-out/sign-out-confirm.tsx create mode 100644 src/app/features/settings/sign-out/sign-out.tsx rename src/app/features/{theme-drawer/theme-list.tsx => settings/theme/theme-dialog.tsx} (71%) rename src/app/features/{theme-drawer/theme-list-item-layout.tsx => settings/theme/theme-list-item.tsx} (54%) delete mode 100644 src/app/features/switch-account-drawer/components/create-account-action.tsx delete mode 100644 src/app/features/switch-account-drawer/components/switch-account-list.tsx delete mode 100644 src/app/features/switch-account-drawer/switch-account-drawer.tsx delete mode 100644 src/app/features/theme-drawer/theme-drawer.tsx delete mode 100644 src/app/features/theme-drawer/theme-list-item.tsx delete mode 100644 src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-layout.tsx delete mode 100644 src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-layout.tsx delete mode 100644 src/app/pages/home/components/account-area.tsx delete mode 100644 src/app/pages/home/components/account-info-card.tsx delete mode 100644 src/app/pages/home/components/action-button.tsx delete mode 100644 src/app/pages/home/components/home.layout.tsx delete mode 100644 src/app/pages/onboarding/back-up-secret-key/components/back-up-secret-key.content.tsx delete mode 100644 src/app/pages/onboarding/sign-in/components/sign-in.content.tsx delete mode 100644 src/app/pages/onboarding/welcome/welcome.layout.tsx delete mode 100644 src/app/pages/receive/components/address-qr-code.tsx create mode 100644 src/app/pages/receive/components/receive-collectibles.tsx delete mode 100644 src/app/pages/receive/components/receive-items.tsx create mode 100644 src/app/pages/receive/components/receive-tokens.tsx create mode 100644 src/app/pages/receive/receive-dialog.tsx delete mode 100644 src/app/pages/receive/receive-modal.tsx delete mode 100644 src/app/pages/rpc-send-transfer/components/send-transfer-actions.tsx delete mode 100644 src/app/pages/rpc-send-transfer/components/send-transfer-footer.tsx delete mode 100644 src/app/pages/select-network/components/add-network-button.tsx delete mode 100644 src/app/pages/select-network/components/network-list.layout.tsx delete mode 100644 src/app/pages/select-network/components/network-status-indicator.tsx delete mode 100644 src/app/pages/select-network/select-network.tsx delete mode 100644 src/app/pages/send/send-container.tsx delete mode 100644 src/app/pages/send/send-crypto-asset-form/components/form-footer.tsx rename src/app/pages/send/send-crypto-asset-form/components/{recipient-accounts-drawer => recipient-accounts-dialog}/account-list-item.tsx (95%) create mode 100644 src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx rename src/app/pages/send/send-crypto-asset-form/components/{recipient-accounts-drawer => recipient-accounts-dialog}/select-account-button.tsx (100%) delete mode 100644 src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/recipient-accounts-drawer.tsx delete mode 100644 src/app/pages/send/send-crypto-asset-form/components/send-crypto-asset-form.layout.tsx delete mode 100644 src/app/pages/sign-out-confirm/sign-out-confirm.layout.tsx delete mode 100644 src/app/pages/sign-out-confirm/sign-out-confirm.tsx rename src/app/pages/swap/{ => components}/swap-choose-asset/components/swap-asset-item.tsx (92%) rename src/app/pages/swap/{ => components}/swap-choose-asset/components/swap-asset-list.tsx (89%) create mode 100644 src/app/pages/swap/components/swap-choose-asset/swap-choose-asset.tsx delete mode 100644 src/app/pages/swap/components/swap-container.layout.tsx delete mode 100644 src/app/pages/swap/components/swap-content.layout.tsx rename src/app/pages/swap/{swap-error => components}/swap-error.tsx (100%) delete mode 100644 src/app/pages/swap/components/swap-footer.layout.tsx create mode 100644 src/app/pages/swap/components/swap-review.tsx delete mode 100644 src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx delete mode 100644 src/app/pages/swap/swap-choose-asset/swap-choose-asset.tsx delete mode 100644 src/app/pages/swap/swap-review/swap-review.layout.tsx delete mode 100644 src/app/pages/swap/swap-review/swap-review.tsx delete mode 100644 src/app/pages/view-secret-key/components/view-secret-key.content.tsx delete mode 100644 src/app/routes/settings-routes.tsx rename src/app/{components/account => ui/components/account/account-avatar}/account-avatar-item.tsx (76%) rename src/app/{components/account => ui/components/account/account-avatar}/account-avatar.tsx (100%) create mode 100644 src/app/ui/components/account/account.card.stories.tsx create mode 100644 src/app/ui/components/account/account.card.tsx create mode 100644 src/app/ui/components/containers/container.layout.tsx create mode 100644 src/app/ui/components/containers/dialog/dialog.stories.tsx create mode 100644 src/app/ui/components/containers/dialog/dialog.tsx rename src/app/{components => ui/components/containers/footers}/available-balance.tsx (62%) create mode 100644 src/app/ui/components/containers/footers/footer.stories.tsx create mode 100644 src/app/ui/components/containers/footers/footer.tsx create mode 100644 src/app/ui/components/containers/headers/components/big-title-header.tsx create mode 100644 src/app/ui/components/containers/headers/components/header-action-button.tsx create mode 100644 src/app/ui/components/containers/headers/components/network-mode-badge.tsx create mode 100644 src/app/ui/components/containers/headers/header.stories.tsx create mode 100644 src/app/ui/components/containers/headers/header.tsx rename src/app/ui/components/{dowpdown-menu => dropdown-menu}/dropdown-menu-item.layout.tsx (100%) rename src/app/ui/components/{dowpdown-menu => dropdown-menu}/dropdown-menu.stories.tsx (100%) rename src/app/ui/components/{dowpdown-menu => dropdown-menu}/dropdown-menu.tsx (74%) rename src/app/{pages/home/components => ui/components/icon-button}/accessible-icon.tsx (65%) create mode 100644 src/app/ui/components/icon-button/icon-button.stories.tsx create mode 100644 src/app/ui/components/icon-button/icon-button.tsx create mode 100644 src/app/ui/components/logo.tsx rename src/app/{ => ui}/components/secret-key/mnemonic-key/mnemonic-word-input.tsx (100%) rename src/app/{ => ui}/components/secret-key/mnemonic-key/utils/error-handling.ts (100%) rename src/app/{ => ui}/components/secret-key/mnemonic-key/utils/validation.ts (100%) create mode 100644 src/app/ui/components/secret-key/secret-key-grid.tsx rename src/app/{features/secret-key-displayer/components => ui/components/secret-key}/secret-key-word.tsx (93%) rename src/app/{features/secret-key-displayer/secret-key-displayer.layout.tsx => ui/components/secret-key/secret-key.layout.tsx} (63%) create mode 100644 src/app/ui/layout/card/card-content.tsx create mode 100644 src/app/ui/layout/card/card.stories.tsx create mode 100644 src/app/ui/layout/card/card.tsx create mode 100644 src/app/ui/layout/page/page.layout.stories.tsx create mode 100644 src/app/ui/layout/page/page.layout.tsx create mode 100644 src/app/ui/pages/home.layout.stories.tsx create mode 100644 src/app/ui/pages/home.layout.tsx create mode 100644 src/app/ui/pages/two-column.layout.stories.tsx create mode 100644 src/app/ui/pages/two-column.layout.tsx create mode 100644 src/app/ui/pages/welcome.layout.tsx create mode 100644 src/app/ui/shared/virtuoso.ts rename src/background/{popup-center.ts => popup.ts} (51%) create mode 100644 src/shared/utils/px-string-to-number.spec.ts create mode 100644 src/shared/utils/px-string-to-number.ts delete mode 100644 test-app/src/components/header.tsx rename tests/specs/settings/{settings-menu.spec.ts => settings.spec.ts} (95%) delete mode 100644 theme/global/full-page-styles.ts delete mode 100644 theme/global/popup-center-styles.ts delete mode 100644 theme/global/popup-styles.ts diff --git a/.storybook/main.ts b/.storybook/main.ts index bd5c6cd67d0..6a182aa2387 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -34,6 +34,12 @@ const config: StorybookConfig = { }, ], }, + + { + test: /\.(ts|tsx)$/, + loader: 'esbuild-loader', + options: { tsconfig: './tsconfig.json', target: 'es2020' }, + }, ], }, }, diff --git a/.storybook/preview.ts b/.storybook/preview.ts index ae4ce4cab9a..fe3b4426509 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,6 +1,7 @@ import type { Preview } from '@storybook/react'; import '../src/app/index.css'; +import { customViewports } from './viewports'; const preview: Preview = { parameters: { @@ -24,6 +25,12 @@ const preview: Preview = { date: /Date$/i, }, }, + viewport: { + viewports: { + ...customViewports, + }, + }, + toc: true, }, }; diff --git a/.storybook/viewports.ts b/.storybook/viewports.ts new file mode 100644 index 00000000000..5f333aff73d --- /dev/null +++ b/.storybook/viewports.ts @@ -0,0 +1,46 @@ +import { breakpoints, tokens } from '@leather-wallet/tokens'; + +export const customViewports = { + popup: { + name: 'Popup', + styles: { + width: tokens.sizes.popupWidth.value, + height: tokens.sizes.popupHeight.value, + }, + }, + sm: { + name: 'sm', + styles: { + width: breakpoints.sm, + height: '100%', + }, + }, + md: { + name: 'md', + styles: { + width: breakpoints.md, + height: '100%', + }, + }, + lg: { + name: 'lg', + styles: { + width: breakpoints.lg, + height: '100%', + }, + }, + xl: { + name: 'xl', + styles: { + width: breakpoints.xl, + height: '100%', + }, + }, + '2xl': { + name: '2xl', + styles: { + width: breakpoints['2xl'], + height: '100%', + }, + }, +}; diff --git a/README.md b/README.md index 87af1d68b54..3f82e3c4e3f 100755 --- a/README.md +++ b/README.md @@ -64,8 +64,6 @@ pnpm playwright install --with-deps Note that the installed browsers are tied to the version of Playwright being used, and it may be necessary to run the above command again in some situations, such as when upgrading Playwright or switching branches. [Read the documentation for more information](https://playwright.dev/docs/cli#install-system-dependencies). -### Integration tests - All integration tests can be run using: ```bash @@ -79,6 +77,14 @@ pnpm playwright test specs/TEST.spec.ts pnpm playwright test tests/specs --shard=3/8 ``` +To get more information when running tests you can pass the `--debug` flag or the `--ui` flag to playwright. + +When running tests locally you must add the following to your `.env` file: + +``` +WALLET_ENVIRONMENT=testing +``` + ### Unit tests Unit tests can be run with vitest using: diff --git a/package.json b/package.json index e0efeb22df0..097ff904ca9 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "@dlc-link/dlc-tools": "1.1.1", "@fungible-systems/zone-file": "2.0.0", "@hirosystems/token-metadata-api-client": "1.2.0", - "@leather-wallet/tokens": "0.0.13", + "@leather-wallet/tokens": "0.0.14", "@ledgerhq/hw-transport-webusb": "6.27.19", "@noble/hashes": "1.3.2", "@noble/secp256k1": "2.0.0", @@ -143,6 +143,7 @@ "@radix-ui/colors": "3.0.0", "@radix-ui/react-accessible-icon": "1.0.3", "@radix-ui/react-avatar": "1.0.4", + "@radix-ui/react-dialog": "1.0.5", "@radix-ui/react-dropdown-menu": "2.0.6", "@radix-ui/react-select": "2.0.0", "@radix-ui/react-tabs": "1.0.4", @@ -178,7 +179,6 @@ "@tanstack/react-query-persist-client": "4.35.7", "@types/lodash.uniqby": "4.7.7", "@typescript-eslint/eslint-plugin": "7.0.2", - "@vkontakte/vk-qr": "2.0.13", "@zondax/ledger-stacks": "1.0.4", "alex-sdk": "0.1.26", "are-passive-events-supported": "1.1.1", @@ -227,6 +227,7 @@ "react-head": "3.4.2", "react-intersection-observer": "9.5.2", "react-lottie": "1.2.4", + "react-qr-code": "2.0.12", "react-redux": "8.1.3", "react-router-dom": "6.22.1", "react-virtuoso": "4.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09417f48364..bd6ce947970 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ dependencies: specifier: 1.2.0 version: 1.2.0 '@leather-wallet/tokens': - specifier: 0.0.13 - version: 0.0.13 + specifier: 0.0.14 + version: 0.0.14 '@ledgerhq/hw-transport-webusb': specifier: 6.27.19 version: 6.27.19 @@ -53,6 +53,9 @@ dependencies: '@radix-ui/react-avatar': specifier: 1.0.4 version: 1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.57)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-dialog': + specifier: 1.0.5 + version: 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.57)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-dropdown-menu': specifier: 2.0.6 version: 2.0.6(@types/react-dom@18.2.19)(@types/react@18.2.57)(react-dom@18.2.0)(react@18.2.0) @@ -158,9 +161,6 @@ dependencies: '@typescript-eslint/eslint-plugin': specifier: 7.0.2 version: 7.0.2(@typescript-eslint/parser@7.0.2)(eslint@8.56.0)(typescript@5.3.3) - '@vkontakte/vk-qr': - specifier: 2.0.13 - version: 2.0.13 '@zondax/ledger-stacks': specifier: 1.0.4 version: 1.0.4 @@ -305,6 +305,9 @@ dependencies: react-lottie: specifier: 1.2.4 version: 1.2.4(react@18.2.0) + react-qr-code: + specifier: 2.0.12 + version: 2.0.12(react@18.2.0) react-redux: specifier: 8.1.3 version: 8.1.3(@types/react-dom@18.2.19)(@types/react@18.2.57)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) @@ -3592,8 +3595,8 @@ packages: - supports-color dev: true - /@leather-wallet/tokens@0.0.13: - resolution: {integrity: sha512-ScXDDa0pyB86XqUHTWGoZPsrCpdWG/vEvhD5YyfSIBKnNnPdf/zP16/Wv88edPBXQfcR98YUOgIm+Uvjq75Fqw==} + /@leather-wallet/tokens@0.0.14: + resolution: {integrity: sha512-xCg+aIcn8DexmQhfvxYM85IGP2Q1JNfUBq80ZwV4horKw18MxxTdX5FeEthTrO8quRZu7up+IW6m/l3wElnDsA==} dev: false /@ledgerhq/devices@8.2.0: @@ -9886,10 +9889,6 @@ packages: pretty-format: 29.7.0 dev: true - /@vkontakte/vk-qr@2.0.13: - resolution: {integrity: sha512-yskZf4k0TgJV2atS4WgxjqICeGg1Z+hj8tjvsH2Clf17EJXAczDvn4x1zyqC0CRHDjiOkcbne/FhCKq/nykYiQ==} - dev: false - /@vue/compiler-core@3.4.19: resolution: {integrity: sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==} dependencies: @@ -19903,7 +19902,6 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 - dev: true /property-expr@2.0.6: resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} @@ -19978,6 +19976,10 @@ packages: escape-goat: 4.0.0 dev: true + /qr.js@0.0.0: + resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==} + dev: false + /qs@6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} @@ -20308,6 +20310,20 @@ packages: react: 18.2.0 dev: false + /react-qr-code@2.0.12(react@18.2.0): + resolution: {integrity: sha512-k+pzP5CKLEGBRwZsDPp98/CAJeXlsYRHM2iZn1Sd5Th/HnKhIZCSg27PXO58zk8z02RaEryg+60xa4vyywMJwg==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x + react-native-svg: '*' + peerDependenciesMeta: + react-native-svg: + optional: true + dependencies: + prop-types: 15.8.1 + qr.js: 0.0.0 + react: 18.2.0 + dev: false + /react-redux@8.1.3(@types/react-dom@18.2.19)(@types/react@18.2.57)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1): resolution: {integrity: sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==} peerDependencies: diff --git a/public/html/popup-center.html b/public/html/popup-center.html deleted file mode 100644 index 7d23e5be4ba..00000000000 --- a/public/html/popup-center.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - -
- - - diff --git a/src/app/app.tsx b/src/app/app.tsx index ee957002498..528b162aab0 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -1,9 +1,7 @@ import { Suspense } from 'react'; import { Provider as ReduxProvider } from 'react-redux'; -import { radixBaseCSS } from '@radix-ui/themes/styles.css'; import { QueryClientProvider } from '@tanstack/react-query'; -import { styled } from 'leather-styles/jsx'; import { PersistGate } from 'redux-persist/integration/react'; import { queryClient } from '@app/common/persistence'; @@ -25,21 +23,18 @@ export function App() { } persistor={persistor}> - {/* TODO: this works but investigate importing radixBaseCSS in panda layer config */} - - - - - }> - - - - {reactQueryDevToolsEnabled && } - - - - - + + + + }> + + + + {reactQueryDevToolsEnabled && } + + + + ); diff --git a/src/app/common/hooks/use-drawers.ts b/src/app/common/hooks/use-drawers.ts deleted file mode 100644 index d92f2f48f9b..00000000000 --- a/src/app/common/hooks/use-drawers.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - useShowHighFeeConfirmationState, - useShowSettingsStore, - useShowSwitchAccountsState, - useShowTxSettingsCallback, -} from '@app/store/ui/ui.hooks'; - -export function useDrawers() { - const [isShowingAccounts, setIsShowingSwitchAccountsState] = useShowSwitchAccountsState(); - const [isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation] = - useShowHighFeeConfirmationState(); - - const [isShowingSettings, setIsShowingSettings] = useShowSettingsStore(); - const [isShowingTxSettingsCallback, setIsShowingTxSettingsCallback] = useShowTxSettingsCallback(); - - return { - isShowingAccounts, - setIsShowingSwitchAccountsState, - isShowingHighFeeConfirmation, - setIsShowingHighFeeConfirmation, - isShowingSettings, - setIsShowingSettings, - isShowingTxSettingsCallback, - setIsShowingTxSettingsCallback, - }; -} diff --git a/src/app/common/hooks/use-event-listener.ts b/src/app/common/hooks/use-event-listener.ts deleted file mode 100644 index b50275a17c4..00000000000 --- a/src/app/common/hooks/use-event-listener.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { useEffect } from 'react'; - -import { useLatestRef } from './use-latest-ref'; - -// eslint-disable-next-line @typescript-eslint/ban-types -type FunctionArguments = T extends (...args: infer R) => any ? R : never; -type AddEventListener = FunctionArguments; - -let _window: Window | undefined = undefined; - -// Note: Accessing "window" in IE11 is somewhat expensive, and calling "typeof window" -// hits a memory leak, whereas aliasing it and calling "typeof _window" does not. -// Caching the window value at the file scope lets us minimize the impact. -try { - _window = window; -} catch (e) { - /* no-op */ -} - -/** - * Helper to get the window object. The helper will make sure to use a cached variable - * of "window", to avoid overhead and memory leaks in IE11. - */ -function getWindow(node?: HTMLElement | null): Window | undefined { - return node?.ownerDocument?.defaultView ?? _window; -} - -/** - * Check if we can use the DOM. Useful for SSR purposes - */ -function checkIsBrowser() { - const _window = getWindow(); - return Boolean( - // eslint-disable-next-line @typescript-eslint/unbound-method, deprecation/deprecation - typeof _window !== 'undefined' && _window.document && _window.document.createElement - ); -} - -const isBrowser = checkIsBrowser(); - -/** - * React hook to manage browser event listeners - * - * @param event the event name - * @param handler the event handler function to execute - * @param element the dom environment to execute against (defaults to `document`) - * @param options the event listener options - */ -export function useEventListener( - event: keyof WindowEventMap, - handler: (event: any) => void, - element: Document | null = isBrowser ? document : null, - options?: AddEventListener[2] -) { - const savedHandler = useLatestRef(handler); - - useEffect(() => { - if (!element) return; - - const listener = (event: any) => { - savedHandler.current(event); - }; - - element.addEventListener(event, listener, options); - - return () => { - element.removeEventListener(event, listener, options); - }; - }, [event, element, options, savedHandler]); - - return () => { - element?.removeEventListener(event, savedHandler.current, options); - }; -} diff --git a/src/app/common/utils/use-interval.ts b/src/app/common/hooks/use-interval.ts similarity index 100% rename from src/app/common/utils/use-interval.ts rename to src/app/common/hooks/use-interval.ts diff --git a/src/app/common/hooks/use-latest-ref.ts b/src/app/common/hooks/use-latest-ref.ts deleted file mode 100644 index aa73b9c1b29..00000000000 --- a/src/app/common/hooks/use-latest-ref.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useEffect, useRef } from 'react'; - -/** - * React hook to persist any value between renders, - * but keeps it up-to-date if it changes. - * - * @param value the value or function to persist - */ -export function useLatestRef(value: T) { - const ref = useRef(value); - - useEffect(() => { - ref.current = value; - }, [value]); - - return ref; -} diff --git a/src/app/common/hooks/use-media-query.ts b/src/app/common/hooks/use-media-query.ts deleted file mode 100644 index 1d9249726cc..00000000000 --- a/src/app/common/hooks/use-media-query.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useEffect, useState } from 'react'; - -import { BreakpointToken, token } from 'leather-styles/tokens'; - -function useMediaQuery(query: string) { - const [matches, setMatches] = useState(false); - - useEffect(() => { - const media = window.matchMedia(query); - if (media.matches !== matches) { - setMatches(media.matches); - } - const listener = () => setMatches(media.matches); - window.addEventListener('resize', listener); - return () => window.removeEventListener('resize', listener); - }, [matches, query]); - - return matches; -} - -export function useViewportMinWidth(viewport: BreakpointToken) { - return useMediaQuery(`(min-width: ${token(`breakpoints.${viewport}`)})`); -} diff --git a/src/app/common/hooks/use-route-header.ts b/src/app/common/hooks/use-route-header.ts deleted file mode 100644 index bd89d969d2b..00000000000 --- a/src/app/common/hooks/use-route-header.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useEffect } from 'react'; -import { useLocation } from 'react-router-dom'; - -import { useRouteHeaderState } from '@app/store/ui/ui.hooks'; - -export function useRouteHeader(header: React.JSX.Element, isParentRoute?: boolean) { - const location = useLocation(); - const [_, setRouteHeader] = useRouteHeaderState(); - useEffect(() => { - if (location.state?.hasHeaderTitle && isParentRoute) { - return; - } - setRouteHeader(header); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [location.pathname]); -} diff --git a/src/app/common/utils/use-waiting-message.ts b/src/app/common/hooks/use-waiting-message.ts similarity index 100% rename from src/app/common/utils/use-waiting-message.ts rename to src/app/common/hooks/use-waiting-message.ts diff --git a/src/app/common/utils.ts b/src/app/common/utils.ts index 8f7e1235e56..400db14c612 100644 --- a/src/app/common/utils.ts +++ b/src/app/common/utils.ts @@ -257,6 +257,7 @@ type PageMode = 'popup' | 'full'; type WhenPageModeMap = Record; +// don't use whenPageMode for styling - use panda responsive object export function whenPageMode(pageModeMap: WhenPageModeMap) { return pageModeMap[pageMode]; } @@ -265,10 +266,6 @@ export function isPopupMode() { return pageMode === 'popup'; } -export function isFullPageMode() { - return pageMode === 'full'; -} - interface WhenStacksChainIdMap { [ChainID.Mainnet]: T; [ChainID.Testnet]: T; diff --git a/src/app/common/utils/copy-to-clipboard.ts b/src/app/common/utils/copy-to-clipboard.ts new file mode 100644 index 00000000000..d1467feee4c --- /dev/null +++ b/src/app/common/utils/copy-to-clipboard.ts @@ -0,0 +1,3 @@ +export async function copyToClipboard(text: string) { + await navigator.clipboard.writeText(text); +} diff --git a/src/app/components/account/account-addresses.tsx b/src/app/components/account/account-addresses.tsx index f9c3c66b64e..f8feff13b97 100644 --- a/src/app/components/account/account-addresses.tsx +++ b/src/app/components/account/account-addresses.tsx @@ -1,6 +1,5 @@ import { HStack } from 'leather-styles/jsx'; -import { useViewportMinWidth } from '@app/common/hooks/use-media-query'; import { BulletSeparator } from '@app/ui/components/bullet-separator/bullet-separator'; import { Caption } from '@app/ui/components/typography/caption'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; @@ -12,13 +11,11 @@ interface AccountAddressesProps { index: number; } export function AcccountAddresses({ index }: AccountAddressesProps) { - const isBreakpointSm = useViewportMinWidth('sm'); - return ( - {account => {truncateMiddle(account.address, isBreakpointSm ? 4 : 3)}} + {account => {truncateMiddle(account.address, 4)}} {signer => {truncateMiddle(signer.address, 5)}} diff --git a/src/app/components/app-version.tsx b/src/app/components/app-version.tsx index bb189a2877d..617d7c93cfa 100644 --- a/src/app/components/app-version.tsx +++ b/src/app/components/app-version.tsx @@ -1,6 +1,6 @@ import { forwardRef, useMemo } from 'react'; -import { HTMLStyledProps, styled } from 'leather-styles/jsx'; +import { Box, HTMLStyledProps, styled } from 'leather-styles/jsx'; import { BRANCH_NAME, COMMIT_SHA } from '@shared/environment'; @@ -13,20 +13,19 @@ interface AppVersionLabelProps extends HTMLStyledProps<'span'> { } const AppVersionLabel = forwardRef( ({ children, isLatestVersion, ...props }: AppVersionLabelProps, ref) => ( - - {children} - + + + {children} + + ) ); diff --git a/src/app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point-layout.tsx b/src/app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point-layout.tsx index 08c681f7c0d..3d093e70813 100644 --- a/src/app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point-layout.tsx +++ b/src/app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point-layout.tsx @@ -43,9 +43,9 @@ export function BitcoinContractEntryPointLayout(props: BitcoinContractEntryPoint - {caption} + {caption} - {isLoading ? '' : {usdBalance}} + {isLoading ? '' : {usdBalance}} diff --git a/src/app/components/bitcoin-custom-fee/bitcoin-custom-fee.tsx b/src/app/components/bitcoin-custom-fee/bitcoin-custom-fee.tsx index 440fba223e0..6e52b6310db 100644 --- a/src/app/components/bitcoin-custom-fee/bitcoin-custom-fee.tsx +++ b/src/app/components/bitcoin-custom-fee/bitcoin-custom-fee.tsx @@ -1,5 +1,6 @@ import { Dispatch, SetStateAction, useCallback, useRef } from 'react'; +import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors'; import { Form, Formik } from 'formik'; import { Stack, styled } from 'leather-styles/jsx'; import * as yup from 'yup'; @@ -8,7 +9,7 @@ import { BtcFeeType } from '@shared/models/fees/bitcoin-fees.model'; import { createMoney } from '@shared/models/money.model'; import { openInNewTab } from '@app/common/utils/open-in-new-tab'; -import { PreviewButton } from '@app/components/preview-button'; +import { Button } from '@app/ui/components/button/button'; import { Link } from '@app/ui/components/link/link'; import { OnChooseFeeArgs } from '../bitcoin-fees-list/bitcoin-fees-list'; @@ -107,7 +108,13 @@ export function BitcoinCustomFee({ hasInsufficientBalanceError={hasInsufficientBalanceError} /> - + )} diff --git a/src/app/components/bitcoin-fees-list/components/fees-list-item.tsx b/src/app/components/bitcoin-fees-list/components/fees-list-item.tsx index 8d58a7dabd0..2545382af11 100644 --- a/src/app/components/bitcoin-fees-list/components/fees-list-item.tsx +++ b/src/app/components/bitcoin-fees-list/components/fees-list-item.tsx @@ -41,7 +41,7 @@ export function FeesListItem({ {`${feeFiatValue} | ${feeRate} sats/vB | ${feeAmount}`} diff --git a/src/app/components/broadcast-error-dialog/broadcast-error-dialog.tsx b/src/app/components/broadcast-error-dialog/broadcast-error-dialog.tsx new file mode 100644 index 00000000000..22a026d18b9 --- /dev/null +++ b/src/app/components/broadcast-error-dialog/broadcast-error-dialog.tsx @@ -0,0 +1,50 @@ +import { useLocation, useNavigate } from 'react-router-dom'; + +import GenericError from '@assets/images/generic-error.png'; +import { Flex, styled } from 'leather-styles/jsx'; +import get from 'lodash.get'; + +import { Button } from '@app/ui/components/button/button'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Header } from '@app/ui/components/containers/headers/header'; + +export function BroadcastErrorDialog() { + const navigate = useNavigate(); + const location = useLocation(); + const message = get(location.state, 'message', ''); + + return ( + } + onClose={() => navigate('..')} + footer={ +
+ +
+ } + > + + + + Unable to broadcast transaction + + + Your transaction failed to broadcast{' '} + {message && <>because of the error: {message.toLowerCase()}} + + +
+ ); +} diff --git a/src/app/components/broadcast-error-drawer/broadcast-error-drawer.layout.tsx b/src/app/components/broadcast-error-drawer/broadcast-error-drawer.layout.tsx deleted file mode 100644 index d6b7de7d14a..00000000000 --- a/src/app/components/broadcast-error-drawer/broadcast-error-drawer.layout.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import GenericError from '@assets/images/generic-error.png'; -import { Flex, styled } from 'leather-styles/jsx'; - -import { BaseDrawer } from '@app/components/drawer/base-drawer'; -import { Button } from '@app/ui/components/button/button'; - -interface BroadcastErrorDrawerLayoutProps { - message: string; - onClose(): void; -} -export function BroadcastErrorDrawerLayout({ message, onClose }: BroadcastErrorDrawerLayoutProps) { - return ( - } isShowing onClose={onClose} textAlign="center"> - - - - Unable to broadcast transaction - - - Your transaction failed to broadcast{' '} - {message && <>because of the error: {message.toLowerCase()}} - - - - - ); -} diff --git a/src/app/components/broadcast-error-drawer/broadcast-error-drawer.tsx b/src/app/components/broadcast-error-drawer/broadcast-error-drawer.tsx deleted file mode 100644 index 576ab38bec8..00000000000 --- a/src/app/components/broadcast-error-drawer/broadcast-error-drawer.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useLocation, useNavigate } from 'react-router-dom'; - -import get from 'lodash.get'; - -import { BroadcastErrorDrawerLayout } from './broadcast-error-drawer.layout'; - -export function BroadcastErrorDrawer() { - const navigate = useNavigate(); - const location = useLocation(); - - return ( - navigate('..')} - /> - ); -} diff --git a/src/app/components/centered-page-container.tsx b/src/app/components/centered-page-container.tsx deleted file mode 100644 index fc1d1c41d95..00000000000 --- a/src/app/components/centered-page-container.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Flex, FlexProps } from 'leather-styles/jsx'; - -export function CenteredPageContainer(props: FlexProps) { - return ( - - ); -} diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx index ee6dac95034..1dd6b2ad996 100644 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx @@ -1,5 +1,8 @@ import { useNavigate } from 'react-router-dom'; +import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; +import { Stack } from 'leather-styles/jsx'; + import { RouteUrls } from '@shared/route-urls'; import { noop } from '@shared/utils'; @@ -8,7 +11,6 @@ import { Brc20Token } from '@app/query/bitcoin/bitcoin-client'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { Brc20TokenAssetItemLayout } from './components/brc20-token-asset-item.layout'; -import { Brc20AssetListLayout } from './components/brc20-token-asset-list.layout'; export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) { const navigate = useNavigate(); @@ -28,15 +30,14 @@ export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) { if (!props.brc20Tokens?.length) return null; return ( - + {props.brc20Tokens?.map(token => ( navigateToBrc20SendForm(token) : noop} /> ))} - + ); } diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx index 597bdd7ac26..594349afc90 100644 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx @@ -12,41 +12,33 @@ import { Pressable } from '@app/ui/pressable/pressable'; interface Brc20TokenAssetItemLayoutProps { token: Brc20Token; onClick?(): void; - displayNotEnoughBalance?: boolean; } export function Brc20TokenAssetItemLayout({ onClick, - displayNotEnoughBalance, + token, }: Brc20TokenAssetItemLayoutProps) { const balance = createMoney(Number(token.overall_balance), token.ticker, 0); const formattedBalance = formatBalance(balance.amount.toString()); return ( - - - } - titleLeft={token.ticker} - captionLeft="BRC-20" - titleRight={ - - - {formattedBalance.value} - - - } - /> - - + + } + titleLeft={token.ticker} + captionLeft="BRC-20" + titleRight={ + + + {formattedBalance.value} + + + } + /> + ); } diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-list.layout.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-list.layout.tsx deleted file mode 100644 index 41ea4db9c38..00000000000 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-list.layout.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; -import { Stack, StackProps } from 'leather-styles/jsx'; - -export function Brc20AssetListLayout({ children }: StackProps) { - return ( - - {children} - - ); -} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/choose-asset-container.tsx b/src/app/components/crypto-assets/choose-crypto-asset/choose-asset-container.tsx deleted file mode 100644 index 5eae0646903..00000000000 --- a/src/app/components/crypto-assets/choose-crypto-asset/choose-asset-container.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Flex } from 'leather-styles/jsx'; - -import { HasChildren } from '@app/common/has-children'; -import { whenPageMode } from '@app/common/utils'; - -export function ChooseAssetContainer({ children }: HasChildren) { - return whenPageMode({ - full: ( - - {children} - - ), - popup: ( - - {children} - - ), - }); -} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/choose-crypto-asset.layout.tsx b/src/app/components/crypto-assets/choose-crypto-asset/choose-crypto-asset.layout.tsx deleted file mode 100644 index 137ce63fe77..00000000000 --- a/src/app/components/crypto-assets/choose-crypto-asset/choose-crypto-asset.layout.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Box, Flex, StackProps, styled } from 'leather-styles/jsx'; - -export function ChooseCryptoAssetLayout({ children, title }: StackProps & { title: string }) { - return ( - - - - {title} - - - {children} - - ); -} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx deleted file mode 100644 index c5c9418347b..00000000000 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; -import { Stack, StackProps } from 'leather-styles/jsx'; - -export function CryptoAssetListLayout({ children }: StackProps) { - return ( - - {children} - - ); -} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx index b9db5559af2..df157015680 100644 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx +++ b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx @@ -1,3 +1,6 @@ +import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; +import { Stack } from 'leather-styles/jsx'; + import type { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model'; import { StacksFungibleTokenAsset } from '@shared/models/crypto-asset.model'; @@ -10,17 +13,21 @@ import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; import { CryptoCurrencyAssetItemLayout } from '../crypto-currency-asset/crypto-currency-asset-item.layout'; import { CryptoAssetListItem } from './crypto-asset-list-item'; -import { CryptoAssetListLayout } from './crypto-asset-list.layout'; interface CryptoAssetListProps { cryptoAssetBalances: AllTransferableCryptoAssetBalances[]; onItemClick(cryptoAssetBalance: AllTransferableCryptoAssetBalances): void; + variant: 'send' | 'fund'; } -export function CryptoAssetList({ cryptoAssetBalances, onItemClick }: CryptoAssetListProps) { +export function CryptoAssetList({ + cryptoAssetBalances, + onItemClick, + variant, +}: CryptoAssetListProps) { const { whenWallet } = useWalletType(); return ( - + {signer => ( @@ -44,18 +51,19 @@ export function CryptoAssetList({ cryptoAssetBalances, onItemClick }: CryptoAsse } /> ))} - {whenWallet({ - software: ( - - {() => ( - - {brc20Tokens => } - - )} - - ), - ledger: null, - })} - + {variant === 'send' && + whenWallet({ + software: ( + + {() => ( + + {brc20Tokens => } + + )} + + ), + ledger: null, + })} + ); } diff --git a/src/app/components/disclaimer.tsx b/src/app/components/disclaimer.tsx index fbd8a9e72d5..0ef74549f06 100644 --- a/src/app/components/disclaimer.tsx +++ b/src/app/components/disclaimer.tsx @@ -10,7 +10,7 @@ interface DisclaimerProps extends BoxProps { export function Disclaimer({ disclaimerText, learnMoreUrl, ...props }: DisclaimerProps) { return ( - + {disclaimerText} {learnMoreUrl ? ( openInNewTab(learnMoreUrl)}> diff --git a/src/app/components/drawer/base-drawer.tsx b/src/app/components/drawer/base-drawer.tsx deleted file mode 100644 index 0f393401cb3..00000000000 --- a/src/app/components/drawer/base-drawer.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { ReactNode, Suspense, memo, useCallback, useRef } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { Box, Flex, FlexProps } from 'leather-styles/jsx'; - -import { noop } from '@shared/utils'; - -import { useEventListener } from '@app/common/hooks/use-event-listener'; -import { useOnClickOutside } from '@app/common/hooks/use-onclickoutside'; - -import { DrawerHeader } from './components/drawer-header'; - -function useDrawer(isShowing: boolean, onClose: () => void, pause?: boolean) { - const ref = useRef(null); - - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (isShowing && e.key === 'Escape') { - onClose(); - } - }, - [onClose, isShowing] - ); - - useOnClickOutside(ref, !pause && isShowing ? onClose : null); - useEventListener('keydown', handleKeyDown); - - return ref; -} - -interface BaseDrawerProps extends Omit { - children?: ReactNode; - enableGoBack?: boolean; - icon?: React.JSX.Element; - isShowing: boolean; - isWaitingOnPerformedAction?: boolean; - onClose?(): void; - pauseOnClickOutside?: boolean; - title?: string; - waitingOnPerformedActionMessage?: string; -} -export const BaseDrawer = memo((props: BaseDrawerProps) => { - const { - children, - enableGoBack, - icon, - isShowing, - isWaitingOnPerformedAction, - onClose, - pauseOnClickOutside, - title, - waitingOnPerformedActionMessage, - ...rest - } = props; - const ref = useDrawer(isShowing, onClose ? onClose : noop, pauseOnClickOutside); - const navigate = useNavigate(); - - const onGoBack = () => navigate(-1); - - return ( - - - - - - }>{children} - - - - - ); -}); diff --git a/src/app/components/drawer/components/drawer-header.tsx b/src/app/components/drawer/components/drawer-header.tsx deleted file mode 100644 index c0faa9e2e2c..00000000000 --- a/src/app/components/drawer/components/drawer-header.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Box, Flex, styled } from 'leather-styles/jsx'; -import { useHover } from 'use-events'; - -import { ArrowLeftIcon } from '@app/ui/icons/arrow-left-icon'; -import { CloseIcon } from '@app/ui/icons/close-icon'; - -import { HeaderActionButton } from './header-action-button'; - -interface DrawerHeaderProps { - enableGoBack?: boolean; - icon?: React.JSX.Element; - isWaitingOnPerformedAction?: boolean; - onClose?(): void; - onGoBack(): void; - title?: string; - waitingOnPerformedActionMessage?: string; -} -export function DrawerHeader({ - enableGoBack, - icon, - isWaitingOnPerformedAction, - onClose, - onGoBack, - title, - waitingOnPerformedActionMessage, -}: DrawerHeaderProps) { - const [isHovered, bind] = useHover(); - - return ( - - {enableGoBack ? ( - } - isWaitingOnPerformedAction={isWaitingOnPerformedAction} - onAction={onGoBack} - /> - ) : ( - - )} - {icon && icon} - {title && {title}} - {isHovered && isWaitingOnPerformedAction && ( - - {waitingOnPerformedActionMessage} - - )} - {onClose && ( - } - isWaitingOnPerformedAction={isWaitingOnPerformedAction} - onAction={onClose} - /> - )} - - ); -} diff --git a/src/app/components/drawer/components/header-action-button.tsx b/src/app/components/drawer/components/header-action-button.tsx deleted file mode 100644 index 3fa5c4308a0..00000000000 --- a/src/app/components/drawer/components/header-action-button.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { HomePageSelectors } from '@tests/selectors/home.selectors'; -import { Grid } from 'leather-styles/jsx'; - -interface HeaderActionButtonProps { - icon?: React.JSX.Element; - isWaitingOnPerformedAction?: boolean; - onAction?(): void; -} -export function HeaderActionButton(props: HeaderActionButtonProps) { - const { icon, isWaitingOnPerformedAction, onAction } = props; - - return ( - - {icon} - - ); -} diff --git a/src/app/components/drawer/controlled-drawer.tsx b/src/app/components/drawer/controlled-drawer.tsx deleted file mode 100644 index 02819286cbe..00000000000 --- a/src/app/components/drawer/controlled-drawer.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ReactNode } from 'react'; - -import { BaseDrawer } from './base-drawer'; - -interface ControlledDrawerProps { - children?: ReactNode; - enableGoBack?: boolean; - icon?: React.JSX.Element; - isShowing: boolean; - onClose(): void; - pauseOnClickOutside?: boolean; - title?: string; -} -// The visibility of this drawer is controlled by an atom -export function ControlledDrawer(props: ControlledDrawerProps) { - const { children, enableGoBack, icon, isShowing, onClose, pauseOnClickOutside, title } = props; - - return ( - - {children} - - ); -} diff --git a/src/app/components/generic-error/generic-error.tsx b/src/app/components/generic-error/generic-error.tsx index 6c4e401f977..757458e2b39 100644 --- a/src/app/components/generic-error/generic-error.tsx +++ b/src/app/components/generic-error/generic-error.tsx @@ -4,9 +4,6 @@ import { FlexProps, styled } from 'leather-styles/jsx'; import { closeWindow } from '@shared/utils'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { Header } from '@app/components/header'; - import { GenericErrorLayout } from './generic-error.layout'; export function GenericErrorListItem({ text }: { text: ReactNode }) { @@ -23,8 +20,6 @@ interface GenericErrorProps extends FlexProps { export function GenericError(props: GenericErrorProps) { const { body, helpTextList, onClose = () => closeWindow(), title, ...rest } = props; - useRouteHeader(
); - return ( - {onClose ? ( - - - - ) : null} - {!title && (!onClose || isBreakpointSm) ? ( - - - navigate(RouteUrls.Home)} - /> - - - - ) : ( - - {title} - - )} - - - {!hideActions && ( - - )} - {actionButton ? actionButton : null} - - - ); -} diff --git a/src/app/components/info-card/info-card.tsx b/src/app/components/info-card/info-card.tsx index 178ffd1a03a..6795b927d17 100644 --- a/src/app/components/info-card/info-card.tsx +++ b/src/app/components/info-card/info-card.tsx @@ -5,7 +5,6 @@ import { Box, BoxProps, Flex, FlexProps, HStack, Stack, styled } from 'leather-s import { isString } from '@shared/utils'; -import { whenPageMode } from '@app/common/utils'; import { Button } from '@app/ui/components/button/button'; import { DashedHr } from '@app/ui/components/hr'; @@ -13,6 +12,7 @@ import { DashedHr } from '@app/ui/components/hr'; interface InfoCardProps extends FlexProps { children: ReactNode; } +/** @deprecated - replace with ui/card */ export function InfoCard({ children, ...props }: InfoCardProps) { return ( @@ -120,23 +120,18 @@ export function InfoCardBtn({ icon, label, onClick }: InfoCardBtnProps) { interface InfoCardFooterProps { children: ReactNode; } +/** @deprecated replace with ui/footer */ export function InfoCardFooter({ children }: InfoCardFooterProps) { return ( {children} diff --git a/src/app/components/inscription-preview-card/components/inscription-metadata.tsx b/src/app/components/inscription-preview-card/components/inscription-metadata.tsx index b31368f81de..869bfe31f7b 100644 --- a/src/app/components/inscription-preview-card/components/inscription-metadata.tsx +++ b/src/app/components/inscription-preview-card/components/inscription-metadata.tsx @@ -20,9 +20,9 @@ export function InscriptionMetadata({ {icon && icon} {title} - {subtitle} + {subtitle} {action ? ( - action()} textStyle="caption.02" variant="text"> + action()} textStyle="caption.01" variant="text"> {actionLabel} ) : null} diff --git a/src/app/components/leather-logo.tsx b/src/app/components/leather-logo.tsx deleted file mode 100644 index 8ec8f24dc8c..00000000000 --- a/src/app/components/leather-logo.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { memo } from 'react'; - -import { styled } from 'leather-styles/jsx'; - -import { LogomarkIcon } from '@app/ui/icons/logomark-icon'; - -interface LeatherLogoProps { - onClick?(): void; -} -export const LeatherLogo = memo((props: LeatherLogoProps) => { - const { onClick } = props; - - return ( - - - - ); -}); diff --git a/src/app/components/modal-header.tsx b/src/app/components/modal-header.tsx deleted file mode 100644 index b1d086e90ce..00000000000 --- a/src/app/components/modal-header.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { useNavigate } from 'react-router-dom'; - -import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; -import { Box, Flex, styled } from 'leather-styles/jsx'; -import { token } from 'leather-styles/tokens'; - -import { RouteUrls } from '@shared/route-urls'; - -import { NetworkModeBadge } from '@app/components/network-mode-badge'; -import { Button } from '@app/ui/components/button/button'; -import { ArrowLeftIcon } from '@app/ui/icons/arrow-left-icon'; -import { CloseIcon } from '@app/ui/icons/close-icon'; - -interface ModalHeaderProps { - actionButton?: React.JSX.Element; - closeIcon?: boolean; - hideActions?: boolean; - onClose?(): void; - onGoBack?(): void; - defaultClose?: boolean; - defaultGoBack?: boolean; - title: string; -} - -export function ModalHeader({ - actionButton, - hideActions, - onClose, - onGoBack, - closeIcon, - title, - defaultGoBack, - defaultClose, - ...rest -}: ModalHeaderProps) { - const navigate = useNavigate(); - - function defaultCloseAction() { - navigate(RouteUrls.Home); - } - function defaultGoBackAction() { - navigate(-1); - } - - const hasCloseIcon = onClose || defaultClose; - - return ( - - {onGoBack || defaultGoBack ? ( - - - - ) : ( - - )} - - - - {title} - - - - - - {hasCloseIcon && ( - - )} - - - ); -} diff --git a/src/app/components/network-mode-badge.tsx b/src/app/components/network-mode-badge.tsx deleted file mode 100644 index d8067de7392..00000000000 --- a/src/app/components/network-mode-badge.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { memo, useMemo } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { ChainID } from '@stacks/transactions'; -import { Flex, FlexProps } from 'leather-styles/jsx'; - -import { RouteUrls } from '@shared/route-urls'; - -import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; -import { Tag } from '@app/ui/components/tag/tag'; - -export const NetworkModeBadge = memo((props: FlexProps) => { - const navigate = useNavigate(); - const { chain, name } = useCurrentNetworkState(); - const isTestnetChain = useMemo( - () => chain.stacks.chainId === ChainID.Testnet, - [chain.stacks.chainId] - ); - - if (!isTestnetChain) return null; - - return ( - navigate(RouteUrls.SelectNetwork, { relative: 'path' })} - position="relative" - zIndex={999} - {...props} - > - - - ); -}); diff --git a/src/app/components/no-fees-warning-row.tsx b/src/app/components/no-fees-warning-row.tsx index 3ed6cfedf31..420bf92a6d5 100644 --- a/src/app/components/no-fees-warning-row.tsx +++ b/src/app/components/no-fees-warning-row.tsx @@ -9,8 +9,8 @@ interface NoFeesWarningRowProps { export function NoFeesWarningRow({ chainId }: NoFeesWarningRowProps) { return ( - No fees are incurred - + No fees are incurred + {whenStacksChainId(chainId)({ [ChainID.Testnet]: 'Testnet', [ChainID.Mainnet]: 'Mainnet', diff --git a/src/app/components/preview-button.tsx b/src/app/components/preview-button.tsx deleted file mode 100644 index cf624992db5..00000000000 --- a/src/app/components/preview-button.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors'; -import { useFormikContext } from 'formik'; - -import { Button } from '@app/ui/components/button/button'; - -interface PreviewButtonProps { - text?: string; - isDisabled?: boolean; -} -export function PreviewButton({ text = 'Continue', isDisabled, ...props }: PreviewButtonProps) { - const { handleSubmit } = useFormikContext(); - - return ( - - ); -} diff --git a/src/app/components/request-password.tsx b/src/app/components/request-password.tsx index 1e87f6c88b1..f1a3f30a70b 100644 --- a/src/app/components/request-password.tsx +++ b/src/app/components/request-password.tsx @@ -1,16 +1,19 @@ -import { FormEvent, ReactNode, useCallback, useState } from 'react'; +import { FormEvent, useCallback, useState } from 'react'; import { SettingsSelectors } from '@tests/selectors/settings.selectors'; -import { Stack, styled } from 'leather-styles/jsx'; +import { Box, Stack, styled } from 'leather-styles/jsx'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useKeyActions } from '@app/common/hooks/use-key-actions'; import { buildEnterKeyEvent } from '@app/common/hooks/use-modifier-key'; -import { WaitingMessages, useWaitingMessage } from '@app/common/utils/use-waiting-message'; +import { WaitingMessages, useWaitingMessage } from '@app/common/hooks/use-waiting-message'; import { Button } from '@app/ui/components/button/button'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Logo } from '@app/ui/components/logo'; +import { Card } from '@app/ui/layout/card/card'; +import { Page } from '@app/ui/layout/page/page.layout'; import { ErrorLabel } from './error-label'; -import { TwoColumnLayout } from './secret-key/two-column.layout'; const waitingMessages: WaitingMessages = { '2': 'Verifying password…', @@ -18,12 +21,13 @@ const waitingMessages: WaitingMessages = { '20': 'Almost there', }; +const caption = + 'Your password is used to secure your Secret Key and is only used locally on your device.'; + interface RequestPasswordProps { onSuccess(): void; - title?: ReactNode; - caption?: string; } -export function RequestPassword({ title, caption, onSuccess }: RequestPasswordProps) { +export function RequestPassword({ onSuccess }: RequestPasswordProps) { const [password, setPassword] = useState(''); const [error, setError] = useState(''); const { unlockWallet } = useKeyActions(); @@ -50,72 +54,62 @@ export function RequestPassword({ title, caption, onSuccess }: RequestPasswordPr }, [analytics, startWaitingMessage, stopWaitingMessage, unlockWallet, password, onSuccess]); return ( - <> - - - {title} - - - {(isRunning && waitingMessage) || caption} - - + + + + + + } - rightColumn={ - <> - - Your password - - - ) => { - setError(''); - setPassword(e.currentTarget.value); - }} - onKeyUp={buildEnterKeyEvent(submit)} - p="space.04" - placeholder="Enter your password" - ring="none" - type="password" - textStyle="body.02" - value={password} - width="100%" - /> - {error && {error}} - + footer={ +
- +
} - /> - + > + + Enter your password + {(isRunning && waitingMessage) || caption} + ) => { + setError(''); + setPassword(e.currentTarget.value); + }} + onKeyUp={buildEnterKeyEvent(submit)} + p="space.04" + placeholder="Enter your password" + ring="none" + type="password" + textStyle="body.02" + value={password} + width="100%" + /> + {error && {error}} + + + {/* TODO: #4735 implement forgot password flow */} + {/* + Forgot password? + */} +
+
); } diff --git a/src/app/components/requester-flag.tsx b/src/app/components/requester-flag.tsx index 676a0ae68f6..7c4b213bf05 100644 --- a/src/app/components/requester-flag.tsx +++ b/src/app/components/requester-flag.tsx @@ -12,8 +12,7 @@ export function RequesterFlag({ requester }: RequesterFlagProps) { return ( } - align="top" - alignItems="center" + align="middle" justifyContent="center" py="space.04" px="space.02" diff --git a/src/app/components/secret-key/secret-key-grid.tsx b/src/app/components/secret-key/secret-key-grid.tsx deleted file mode 100644 index c791d7acd1c..00000000000 --- a/src/app/components/secret-key/secret-key-grid.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Grid, Stack } from 'leather-styles/jsx'; - -interface SecretKeyGridProps { - children: React.ReactNode; -} -export function SecretKeyGrid({ children }: SecretKeyGridProps) { - return ( - - - {children} - - - ); -} diff --git a/src/app/components/secret-key/two-column.layout.tsx b/src/app/components/secret-key/two-column.layout.tsx deleted file mode 100644 index 7ea7e48e29e..00000000000 --- a/src/app/components/secret-key/two-column.layout.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Flex, Stack, styled } from 'leather-styles/jsx'; - -interface TwoColumnLayoutProps { - leftColumn: React.JSX.Element; - rightColumn: React.JSX.Element; -} - -export function TwoColumnLayout({ - leftColumn, - rightColumn, -}: TwoColumnLayoutProps): React.JSX.Element { - return ( - - - - {leftColumn} - - - - - - {rightColumn} - - - - ); -} diff --git a/src/app/components/stacks-transaction-item/increase-fee-button.tsx b/src/app/components/stacks-transaction-item/increase-fee-button.tsx index fe309deae11..84f62c82928 100644 --- a/src/app/components/stacks-transaction-item/increase-fee-button.tsx +++ b/src/app/components/stacks-transaction-item/increase-fee-button.tsx @@ -1,5 +1,6 @@ import { HStack, styled } from 'leather-styles/jsx'; +import { whenPageMode } from '@app/common/utils'; import { ChevronsRightIcon } from '@app/ui/icons/chevrons-right-icon'; interface IncreaseFeeButtonProps { @@ -15,7 +16,7 @@ export function IncreaseFeeButton(props: IncreaseFeeButtonProps) { { onIncreaseFee(); @@ -32,7 +33,12 @@ export function IncreaseFeeButton(props: IncreaseFeeButtonProps) { > - Increase fee + + {whenPageMode({ + popup: 'Fee', + full: 'Increase fee', + })} + ); diff --git a/src/app/components/status-ready.tsx b/src/app/components/status-ready.tsx index 1b28e08a11c..d0aabde4d7d 100644 --- a/src/app/components/status-ready.tsx +++ b/src/app/components/status-ready.tsx @@ -5,7 +5,7 @@ export function StatusReady() { width: '8px', height: '8px', borderRadius: '50%', - backgroundColor: '#23A978', + background: '#23A978', }} /> ); diff --git a/src/app/features/activity-list/components/tab-wrapper.tsx b/src/app/features/activity-list/components/tab-wrapper.tsx index 7d82c501831..f6b5220731b 100644 --- a/src/app/features/activity-list/components/tab-wrapper.tsx +++ b/src/app/features/activity-list/components/tab-wrapper.tsx @@ -10,8 +10,7 @@ export function ActivityListTabWrapper({ padContent = false, }: ActivityListTabWrapperProps) { return ( - // Height set based on the height of the empty assets screen - + {children} ); diff --git a/src/app/features/add-network/add-network-form.tsx b/src/app/features/add-network/add-network-form.tsx new file mode 100644 index 00000000000..88c9066961e --- /dev/null +++ b/src/app/features/add-network/add-network-form.tsx @@ -0,0 +1,134 @@ +import { useCallback, useEffect } from 'react'; + +import { SelectContent, SelectItem, SelectRoot, SelectTrigger } from '@radix-ui/themes'; +import { NetworkSelectors } from '@tests/selectors/network.selectors'; +import { useFormikContext } from 'formik'; +import { css } from 'leather-styles/css'; + +import { Input } from '@app/ui/components/input/input'; +import { Title } from '@app/ui/components/typography/title'; + +import { type AddNetworkFormValues, useAddNetwork } from './use-add-network'; + +export function AddNetworkForm() { + const { handleChange, setFieldValue, values } = useFormikContext(); + + const { bitcoinApi, handleApiChange } = useAddNetwork(); + + const setStacksUrl = useCallback( + (value: string) => { + void setFieldValue('stacksUrl', value); + }, + [setFieldValue] + ); + + const setBitcoinUrl = useCallback( + (value: string) => { + void setFieldValue('bitcoinUrl', value); + }, + [setFieldValue] + ); + + useEffect(() => { + switch (bitcoinApi) { + case 'mainnet': + setStacksUrl('https://api.hiro.so'); + setBitcoinUrl('https://blockstream.info/api'); + break; + case 'testnet': + setStacksUrl('https://api.testnet.hiro.so'); + setBitcoinUrl('https://blockstream.info/testnet/api'); + break; + case 'signet': + setStacksUrl('https://api.testnet.hiro.so'); + setBitcoinUrl('https://mempool.space/signet/api'); + break; + case 'regtest': + setStacksUrl('https://api.testnet.hiro.so'); + setBitcoinUrl('https://mempool.space/testnet/api'); + break; + } + }, [bitcoinApi, setStacksUrl, setBitcoinUrl]); + + return ( + <> + + Name + + + Bitcoin API + {/* TODO: Replace with new Select */} + + + + + Mainnet + + + Testnet + + + Signet + + + Regtest + + + + Stacks API URL + + Name + + + Bitcoin API URL + + Bitcoin API URL + + + + Network key + + + + ); +} diff --git a/src/app/features/add-network/add-network.tsx b/src/app/features/add-network/add-network.tsx index ed2af5067b7..6c85285c705 100644 --- a/src/app/features/add-network/add-network.tsx +++ b/src/app/features/add-network/add-network.tsx @@ -1,213 +1,28 @@ -import { useCallback, useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { SelectContent, SelectItem, SelectRoot, SelectTrigger } from '@radix-ui/themes'; -import { ChainID } from '@stacks/transactions'; import { NetworkSelectors } from '@tests/selectors/network.selectors'; -import { Formik, useFormik } from 'formik'; -import { css } from 'leather-styles/css'; +import { Form, Formik } from 'formik'; import { Stack, styled } from 'leather-styles/jsx'; -import { BitcoinNetworkModes, DefaultNetworkConfigurations } from '@shared/constants'; -import { RouteUrls } from '@shared/route-urls'; -import { isValidUrl } from '@shared/utils/validate-url'; - -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { removeTrailingSlash } from '@app/common/url-join'; -import { CenteredPageContainer } from '@app/components/centered-page-container'; import { ErrorLabel } from '@app/components/error-label'; -import { Header } from '@app/components/header'; -import { - useCurrentStacksNetworkState, - useNetworksActions, -} from '@app/store/networks/networks.hooks'; import { Button } from '@app/ui/components/button/button'; -import { Input } from '@app/ui/components/input/input'; -import { Title } from '@app/ui/components/typography/title'; +import { Page } from '@app/ui/layout/page/page.layout'; -/** - * The **peer** network ID. - * Not used in signing, but needed to determine the parent of a subnet. - */ -enum PeerNetworkID { - Mainnet = 0x17000000, - Testnet = 0xff000000, -} - -interface AddNetworkFormValues { - key: string; - name: string; - stacksUrl: string; - bitcoinUrl: string; -} -const addNetworkFormValues: AddNetworkFormValues = { - key: '', - name: '', - stacksUrl: '', - bitcoinUrl: '', -}; +import { AddNetworkForm } from './add-network-form'; +import { useAddNetwork } from './use-add-network'; export function AddNetwork() { - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const navigate = useNavigate(); - const network = useCurrentStacksNetworkState(); - const networksActions = useNetworksActions(); - const [bitcoinApi, setBitcoinApi] = useState('mainnet'); - - const formikProps = useFormik({ - initialValues: addNetworkFormValues, - onSubmit: () => {}, - }); - - const { setFieldValue } = formikProps; - - useRouteHeader(
navigate(RouteUrls.Home)} />); - - const handleApiChange = (newValue: BitcoinNetworkModes) => { - setBitcoinApi(newValue); - }; - - const setStacksUrl = useCallback( - (value: string) => { - void setFieldValue('stacksUrl', value); - }, - [setFieldValue] - ); - - const setBitcoinUrl = useCallback( - (value: string) => { - void setFieldValue('bitcoinUrl', value); - }, - [setFieldValue] - ); - - useEffect(() => { - switch (bitcoinApi) { - case 'mainnet': - setStacksUrl('https://api.hiro.so'); - setBitcoinUrl('https://blockstream.info/api'); - break; - case 'testnet': - setStacksUrl('https://api.testnet.hiro.so'); - setBitcoinUrl('https://blockstream.info/testnet/api'); - break; - case 'signet': - setStacksUrl('https://api.testnet.hiro.so'); - setBitcoinUrl('https://mempool.space/signet/api'); - break; - case 'regtest': - setStacksUrl('https://api.testnet.hiro.so'); - setBitcoinUrl('https://mempool.space/testnet/api'); - break; - } - }, [bitcoinApi, setStacksUrl, setBitcoinUrl]); + const { error, initialFormValues, loading, onSubmit } = useAddNetwork(); return ( - - { - const { name, stacksUrl, bitcoinUrl, key } = formikProps.values; - - if (!name) { - setError('Enter a name'); - return; - } - - if (!isValidUrl(stacksUrl)) { - setError('Enter a valid Stacks API URL'); - return; - } - - if (!isValidUrl(bitcoinUrl)) { - setError('Enter a valid Bitcoin API URL'); - return; - } - - if (!key) { - setError('Enter a unique key'); - return; - } - - setLoading(true); - setError(''); - - const stacksPath = removeTrailingSlash(new URL(formikProps.values.stacksUrl).href); - const bitcoinPath = removeTrailingSlash(new URL(formikProps.values.bitcoinUrl).href); - - try { - const bitcoinResponse = await network.fetchFn(`${bitcoinPath}/mempool/recent`); - if (!bitcoinResponse.ok) throw new Error('Unable to fetch mempool from bitcoin node'); - const bitcoinMempool = await bitcoinResponse.json(); - if (!Array.isArray(bitcoinMempool)) - throw new Error('Unable to fetch mempool from bitcoin node'); - } catch (error) { - setError('Unable to fetch mempool from bitcoin node'); - setLoading(false); - return; - } - - let stacksChainInfo: any; - try { - const stacksResponse = await network.fetchFn(`${stacksPath}/v2/info`); - stacksChainInfo = await stacksResponse.json(); - if (!stacksChainInfo) throw new Error('Unable to fetch info from stacks node'); - } catch (error) { - setError('Unable to fetch info from stacks node'); - setLoading(false); - return; - } - - // Attention: - // For mainnet/testnet the v2/info response `.network_id` refers to the chain ID - // For subnets the v2/info response `.network_id` refers to the network ID and the chain ID (they are the same for subnets) - // The `.parent_network_id` refers to the actual peer network ID in both cases - const { network_id: chainId, parent_network_id: parentNetworkId } = stacksChainInfo; - - const isSubnet = typeof stacksChainInfo.l1_subnet_governing_contract === 'string'; - const isFirstLevelSubnet = - isSubnet && - (parentNetworkId === PeerNetworkID.Mainnet || - parentNetworkId === PeerNetworkID.Testnet); - - // Currently, only subnets of mainnet and testnet are supported in the wallet - if (isFirstLevelSubnet) { - const parentChainId = - parentNetworkId === PeerNetworkID.Mainnet ? ChainID.Mainnet : ChainID.Testnet; - networksActions.addNetwork({ - id: key as DefaultNetworkConfigurations, - name: name, - chainId: parentChainId, // Used for differentiating control flow in the wallet - subnetChainId: chainId, // Used for signing transactions (via the network object, not to be confused with the NetworkConfigurations) - url: stacksPath, - bitcoinNetwork: bitcoinApi, - bitcoinUrl: bitcoinPath, - }); - navigate(RouteUrls.Home); - } else if (chainId === ChainID.Mainnet || chainId === ChainID.Testnet) { - networksActions.addNetwork({ - id: key as DefaultNetworkConfigurations, - name: name, - chainId: chainId, - url: stacksPath, - bitcoinNetwork: bitcoinApi, - bitcoinUrl: bitcoinPath, - }); - navigate(RouteUrls.Home); - } else { - setError('Unable to determine chainID from node.'); - } - setLoading(false); - }} - > - {({ handleSubmit }) => ( -
+ + + {() => ( + Use this form to add a new instance of the{' '} @@ -224,98 +39,21 @@ export function AddNetwork() { . Make sure you review and trust the host before you add it. - - Name - - - Bitcoin API - {/* TODO: Replace with new Select */} - - - - - Mainnet - - - Testnet - - - Signet - - - Regtest - - - - Stacks API URL - - Name - - - Bitcoin API URL - - Bitcoin API URL - - - - Network key - - + {error ? ( {error} ) : null} - + )} -
+ ); } diff --git a/src/app/features/add-network/use-add-network.tsx b/src/app/features/add-network/use-add-network.tsx new file mode 100644 index 00000000000..52b62371df9 --- /dev/null +++ b/src/app/features/add-network/use-add-network.tsx @@ -0,0 +1,146 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { ChainID } from '@stacks/transactions'; + +import type { BitcoinNetworkModes, DefaultNetworkConfigurations } from '@shared/constants'; +import { RouteUrls } from '@shared/route-urls'; +import { isValidUrl } from '@shared/utils/validate-url'; + +import { removeTrailingSlash } from '@app/common/url-join'; +import { + useCurrentStacksNetworkState, + useNetworksActions, +} from '@app/store/networks/networks.hooks'; + +/** + * The **peer** network ID. + * Not used in signing, but needed to determine the parent of a subnet. + */ +enum PeerNetworkID { + Mainnet = 0x17000000, + Testnet = 0xff000000, +} + +export interface AddNetworkFormValues { + key: string; + name: string; + stacksUrl: string; + bitcoinUrl: string; +} + +const initialFormValues: AddNetworkFormValues = { + key: '', + name: '', + stacksUrl: '', + bitcoinUrl: '', +}; + +export function useAddNetwork() { + const [bitcoinApi, setBitcoinApi] = useState('mainnet'); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const navigate = useNavigate(); + const network = useCurrentStacksNetworkState(); + const networksActions = useNetworksActions(); + + return { + bitcoinApi, + handleApiChange: (value: BitcoinNetworkModes) => setBitcoinApi(value), + error, + initialFormValues, + loading, + onSubmit: async (values: AddNetworkFormValues) => { + const { name, stacksUrl, bitcoinUrl, key } = values; + + if (!name) { + setError('Enter a name'); + return; + } + + if (!isValidUrl(stacksUrl)) { + setError('Enter a valid Stacks API URL'); + return; + } + + if (!isValidUrl(bitcoinUrl)) { + setError('Enter a valid Bitcoin API URL'); + return; + } + + if (!key) { + setError('Enter a unique key'); + return; + } + + setLoading(true); + setError(''); + + const stacksPath = removeTrailingSlash(new URL(values.stacksUrl).href); + const bitcoinPath = removeTrailingSlash(new URL(values.bitcoinUrl).href); + + try { + const bitcoinResponse = await network.fetchFn(`${bitcoinPath}/mempool/recent`); + if (!bitcoinResponse.ok) throw new Error('Unable to fetch mempool from bitcoin node'); + const bitcoinMempool = await bitcoinResponse.json(); + if (!Array.isArray(bitcoinMempool)) + throw new Error('Unable to fetch mempool from bitcoin node'); + } catch (error) { + setError('Unable to fetch mempool from bitcoin node'); + setLoading(false); + return; + } + + let stacksChainInfo: any; + try { + const stacksResponse = await network.fetchFn(`${stacksPath}/v2/info`); + stacksChainInfo = await stacksResponse.json(); + if (!stacksChainInfo) throw new Error('Unable to fetch info from stacks node'); + } catch (error) { + setError('Unable to fetch info from stacks node'); + setLoading(false); + return; + } + + // Attention: + // For mainnet/testnet the v2/info response `.network_id` refers to the chain ID + // For subnets the v2/info response `.network_id` refers to the network ID and the chain ID (they are the same for subnets) + // The `.parent_network_id` refers to the actual peer network ID in both cases + const { network_id: chainId, parent_network_id: parentNetworkId } = stacksChainInfo; + + const isSubnet = typeof stacksChainInfo.l1_subnet_governing_contract === 'string'; + const isFirstLevelSubnet = + isSubnet && + (parentNetworkId === PeerNetworkID.Mainnet || parentNetworkId === PeerNetworkID.Testnet); + + // Currently, only subnets of mainnet and testnet are supported in the wallet + if (isFirstLevelSubnet) { + const parentChainId = + parentNetworkId === PeerNetworkID.Mainnet ? ChainID.Mainnet : ChainID.Testnet; + networksActions.addNetwork({ + id: key as DefaultNetworkConfigurations, + name: name, + chainId: parentChainId, // Used for differentiating control flow in the wallet + subnetChainId: chainId, // Used for signing transactions (via the network object, not to be confused with the NetworkConfigurations) + url: stacksPath, + bitcoinNetwork: bitcoinApi, + bitcoinUrl: bitcoinPath, + }); + navigate(RouteUrls.Home); + } else if (chainId === ChainID.Mainnet || chainId === ChainID.Testnet) { + networksActions.addNetwork({ + id: key as DefaultNetworkConfigurations, + name: name, + chainId: chainId, + url: stacksPath, + bitcoinNetwork: bitcoinApi, + bitcoinUrl: bitcoinPath, + }); + navigate(RouteUrls.Home); + } else { + setError('Unable to determine chainID from node.'); + } + setLoading(false); + }, + }; +} diff --git a/src/app/features/asset-list/asset-list.tsx b/src/app/features/asset-list/asset-list.tsx index 78bc0ff035d..4e63f2ebde6 100644 --- a/src/app/features/asset-list/asset-list.tsx +++ b/src/app/features/asset-list/asset-list.tsx @@ -32,7 +32,7 @@ export function AssetsList() { const { whenWallet } = useWalletType(); return ( - + {whenWallet({ software: ( - + diff --git a/src/app/features/bitcoin-choose-fee/components/choose-fee-subtitle.tsx b/src/app/features/bitcoin-choose-fee/components/choose-fee-subtitle.tsx index dbec55fd8d9..95961f5c709 100644 --- a/src/app/features/bitcoin-choose-fee/components/choose-fee-subtitle.tsx +++ b/src/app/features/bitcoin-choose-fee/components/choose-fee-subtitle.tsx @@ -10,7 +10,7 @@ export function ChooseFeeSubtitle({ isSendingMax }: { isSendingMax: boolean }) { ); return ( - + {subtitle} ); diff --git a/src/app/features/collectibles/collectibles.tsx b/src/app/features/collectibles/collectibles.tsx index 7f440757a5b..2cb61a05127 100644 --- a/src/app/features/collectibles/collectibles.tsx +++ b/src/app/features/collectibles/collectibles.tsx @@ -13,7 +13,7 @@ import { useConfigNftMetadataEnabled } from '@app/query/common/remote-config/rem import { AddCollectible } from './components/add-collectible'; import { Ordinals } from './components/bitcoin/ordinals'; import { Stamps } from './components/bitcoin/stamps'; -import { CollectiblesLayout } from './components/collectibes.layout'; +import { CollectiblesLayout } from './components/collectible.layout'; import { StacksCryptoAssets } from './components/stacks/stacks-crypto-assets'; import { TaprootBalanceDisplayer } from './components/taproot-balance-displayer'; import { useIsFetchingCollectiblesRelatedQuery } from './hooks/use-is-fetching-collectibles'; diff --git a/src/app/features/collectibles/components/_collectible-types/collectible-other.tsx b/src/app/features/collectibles/components/_collectible-types/collectible-other.tsx index 0f6117e9226..cd649dbb4e0 100644 --- a/src/app/features/collectibles/components/_collectible-types/collectible-other.tsx +++ b/src/app/features/collectibles/components/_collectible-types/collectible-other.tsx @@ -10,7 +10,7 @@ export function CollectibleOther({ children, ...props }: CollectibleOtherProps) - + - {title} + + {title} + {isLoading ? ( ) : ( @@ -35,12 +37,10 @@ export function CollectiblesLayout({ {subHeader} {children} diff --git a/src/app/features/collectibles/components/taproot-balance-displayer.tsx b/src/app/features/collectibles/components/taproot-balance-displayer.tsx index e82e23a3986..e94fe3f89b1 100644 --- a/src/app/features/collectibles/components/taproot-balance-displayer.tsx +++ b/src/app/features/collectibles/components/taproot-balance-displayer.tsx @@ -19,7 +19,7 @@ export function TaprootBalanceDisplayer({ onSelectRetrieveBalance }: TaprootBala if (balance.amount.isLessThanOrEqualTo(0)) return null; return ( - onSelectRetrieveBalance()} textStyle="caption.02" variant="text"> + onSelectRetrieveBalance()} textStyle="caption.01" variant="text"> {formatMoney(balance)} diff --git a/src/app/features/container/container.layout.tsx b/src/app/features/container/container.layout.tsx deleted file mode 100644 index a3c5e99e9b8..00000000000 --- a/src/app/features/container/container.layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Flex } from 'leather-styles/jsx'; - -interface ContainerLayoutProps { - children: React.JSX.Element | React.JSX.Element[]; - header: React.JSX.Element | null; -} -export function ContainerLayout(props: ContainerLayoutProps) { - const { children, header } = props; - - return ( - - {header || null} - - {children} - - - ); -} diff --git a/src/app/features/container/container.tsx b/src/app/features/container/container.tsx index 173960eb015..6bf108f9553 100644 --- a/src/app/features/container/container.tsx +++ b/src/app/features/container/container.tsx @@ -1,25 +1,62 @@ -import { useEffect } from 'react'; -import { Outlet, useLocation } from 'react-router-dom'; +import { useEffect, useState } from 'react'; +import { Outlet, useLocation, useNavigate } from 'react-router-dom'; +import { ChainID } from '@stacks/transactions'; +import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; +import { SettingsSelectors } from '@tests/selectors/settings.selectors'; +import { Box } from 'leather-styles/jsx'; + +import { RouteUrls } from '@shared/route-urls'; import { closeWindow } from '@shared/utils'; import { useAnalytics, useInitalizeAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { LoadingSpinner } from '@app/components/loading-spinner'; +import { CurrentAccountAvatar } from '@app/features/current-account/current-account-avatar'; +import { CurrentAccountName } from '@app/features/current-account/current-account-name'; +import { SwitchAccountDialog } from '@app/features/dialogs/switch-account-dialog/switch-account-dialog'; +import { InAppMessages } from '@app/features/hiro-messages/in-app-messages'; import { useOnSignOut } from '@app/routes/hooks/use-on-sign-out'; import { useOnWalletLock } from '@app/routes/hooks/use-on-wallet-lock'; import { useHasStateRehydrated } from '@app/store'; -import { useRouteHeaderState } from '@app/store/ui/ui.hooks'; +import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; +import { ContainerLayout } from '@app/ui/components/containers/container.layout'; +import { NetworkModeBadge } from '@app/ui/components/containers/headers/components/network-mode-badge'; +import { Header } from '@app/ui/components/containers/headers/header'; +import { Flag } from '@app/ui/components/flag/flag'; +import { Logo } from '@app/ui/components/logo'; +import { HamburgerIcon } from '@app/ui/icons/'; import { useRestoreFormState } from '../popup-send-form-restoration/use-restore-form-state'; -import { SettingsDropdown } from '../settings-dropdown/settings-dropdown'; -import { SwitchAccountDrawer } from '../switch-account-drawer/switch-account-drawer'; -import { ContainerLayout } from './container.layout'; +import { Settings } from '../settings/settings'; +import { TotalBalance } from './total-balance'; +import { + getDisplayAddresssBalanceOf, + isKnownPopupRoute, + isRpcRoute, + showAccountInfo, + showBalanceInfo, +} from './utils/get-popup-header'; +import { getTitleFromUrl } from './utils/get-title-from-url'; +import { + canGoBack, + getIsSessionLocked, + getPageVariant, + hideLogo, + hideSettingsOnSm, + isLandingPage, + isNoHeaderPopup, + isSummaryPage, +} from './utils/route-helpers'; export function Container() { - const [routeHeader] = useRouteHeaderState(); - const { pathname } = useLocation(); + const [isShowingSwitchAccount, setIsShowingSwitchAccount] = useState(false); + const navigate = useNavigate(); + const { pathname: locationPathname } = useLocation(); + const pathname = locationPathname as RouteUrls; + const analytics = useAnalytics(); const hasStateRehydrated = useHasStateRehydrated(); + const { chain, name: chainName } = useCurrentNetworkState(); useOnWalletLock(() => closeWindow()); useOnSignOut(() => closeWindow()); @@ -28,13 +65,111 @@ export function Container() { useEffect(() => void analytics.page('view', `${pathname}`), [analytics, pathname]); + const variant = getPageVariant(pathname); + + const displayHeader = !isLandingPage(pathname) && !isNoHeaderPopup(pathname); + const isSessionLocked = getIsSessionLocked(pathname); + + function getOnGoBackLocation(pathname: RouteUrls) { + switch (pathname) { + case RouteUrls.Swap: + case RouteUrls.Fund.replace(':currency', 'STX'): + case RouteUrls.Fund.replace(':currency', 'BTC'): + case RouteUrls.SendCryptoAssetForm.replace(':symbol', 'stx'): + case RouteUrls.SendCryptoAssetForm.replace(':symbol', 'btc'): + return navigate(RouteUrls.Home); + case RouteUrls.SendStxConfirmation: + return navigate(RouteUrls.SendCryptoAssetForm.replace(':symbol', 'stx')); + case RouteUrls.SendBtcConfirmation: + return navigate(RouteUrls.SendCryptoAssetForm.replace(':symbol', 'btc')); + default: + return navigate(-1); + } + } + if (!hasStateRehydrated) return ; + const showLogoSm = variant === 'home' || isSessionLocked || isKnownPopupRoute(pathname); + const hideSettings = + isKnownPopupRoute(pathname) || isSummaryPage(pathname) || variant === 'onboarding'; + + const isLogoClickable = variant !== 'home' && !isRpcRoute(pathname); return ( <> - - - + setIsShowingSwitchAccount(false)} + /> + + + getOnGoBackLocation(pathname) : undefined} + onClose={isSummaryPage(pathname) ? () => navigate(RouteUrls.Home) : undefined} + settingsMenu={ + hideSettings ? null : ( + + } + toggleSwitchAccount={() => setIsShowingSwitchAccount(!isShowingSwitchAccount)} + /> + ) + } + networkBadge={ + + } + title={getTitleFromUrl(pathname)} + logo={ + !hideLogo(pathname) && ( + + navigate(RouteUrls.Home) : undefined} + /> + + ) + } + account={ + showAccountInfo(pathname) && ( + + setIsShowingSwitchAccount(!isShowingSwitchAccount) + } + /> + } + > + + + ) + } + totalBalance={ + showBalanceInfo(pathname) && ( + + ) + } + /> + ) : null + } + > diff --git a/src/app/features/container/total-balance.tsx b/src/app/features/container/total-balance.tsx new file mode 100644 index 00000000000..2ca38223807 --- /dev/null +++ b/src/app/features/container/total-balance.tsx @@ -0,0 +1,57 @@ +import { Suspense } from 'react'; + +import { Box, HStack } from 'leather-styles/jsx'; + +import { BtcBalance } from '@app/components/balance-btc'; +import { StxBalance } from '@app/components/balance-stx'; +import { LoadingRectangle } from '@app/components/loading-rectangle'; +import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query'; +import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; + +interface TotalBalanceLayoutProps { + children: React.ReactNode; +} +function TotalBalanceLayout({ children }: TotalBalanceLayoutProps) { + return ( + + {children} + + ); +} + +interface TotalBalanceProps { + displayAddresssBalanceOf?: 'all' | 'stx'; +} + +/** + * #4370 This code has been ported from legacy PopupHeader to load balances + */ + +function TotalBalanceSuspense({ displayAddresssBalanceOf }: TotalBalanceProps) { + const account = useCurrentStacksAccount(); + const isBitcoinEnabled = useConfigBitcoinEnabled(); + return ( + + + {account && displayAddresssBalanceOf === 'stx' && } + {isBitcoinEnabled && displayAddresssBalanceOf === 'all' && } + + + ); +} + +function TotalBalanceFallback() { + return ( + + + + ); +} + +export function TotalBalance(props: TotalBalanceProps) { + return ( + }> + + + ); +} diff --git a/src/app/features/container/utils/get-popup-header.ts b/src/app/features/container/utils/get-popup-header.ts new file mode 100644 index 00000000000..16ae97bd077 --- /dev/null +++ b/src/app/features/container/utils/get-popup-header.ts @@ -0,0 +1,83 @@ +/** + * POPUP header logic notes here -> https://github.com/leather-wallet/extension/issues/4371#issuecomment-1919114939 + */ +import { RouteUrls } from '@shared/route-urls'; + +export function isRpcRoute(pathname: RouteUrls) { + //RouteUrls.RpcReceiveBitcoinContractOffer + if (pathname.match('/bitcoin-contract-offer')) return true; + switch (pathname) { + case RouteUrls.PsbtRequest: + case RouteUrls.SignatureRequest: + case RouteUrls.RpcStacksSignature: + case RouteUrls.RpcSignBip322Message: + case RouteUrls.RpcStacksSignature: + case RouteUrls.RpcSignPsbt: + case RouteUrls.RpcSignPsbtSummary: + case RouteUrls.RpcSendTransfer: + case RouteUrls.RpcSendTransferChooseFee: + case RouteUrls.RpcSendTransferConfirmation: + case RouteUrls.RpcSendTransferSummary: + return true; + default: + return false; + } +} + +export function showAccountInfo(pathname: RouteUrls) { + switch (pathname) { + case RouteUrls.TransactionRequest: + case RouteUrls.ProfileUpdateRequest: + case RouteUrls.RpcSendTransfer: + return true; + default: + return false; + } +} + +export function showBalanceInfo(pathname: RouteUrls) { + switch (pathname) { + case RouteUrls.ProfileUpdateRequest: + case RouteUrls.RpcSendTransfer: + return true; + case RouteUrls.TransactionRequest: + default: + return false; + } +} + +export function getDisplayAddresssBalanceOf(pathname: RouteUrls) { + // TODO it's unclear when to show ALL or STX balance here + switch (pathname) { + case RouteUrls.TransactionRequest: + case RouteUrls.ProfileUpdateRequest: + case RouteUrls.RpcSendTransfer: + return 'all'; + default: + return 'stx'; + } +} + +export function isKnownPopupRoute(pathname: RouteUrls) { + if (pathname.match('/bitcoin-contract-offer')) return true; + switch (pathname) { + case RouteUrls.TransactionRequest: + case RouteUrls.ProfileUpdateRequest: + case RouteUrls.PsbtRequest: + case RouteUrls.SignatureRequest: + case RouteUrls.RpcGetAddresses: + case RouteUrls.RpcSendTransfer: + case RouteUrls.SignatureRequest: + case RouteUrls.RpcSignBip322Message: + case RouteUrls.RpcStacksSignature: + case RouteUrls.RpcSignPsbt: + case RouteUrls.RpcSignPsbtSummary: + case RouteUrls.RpcSendTransfer: + case RouteUrls.RpcSendTransferChooseFee: + case RouteUrls.RpcSendTransferConfirmation: + case RouteUrls.RpcSendTransferSummary: + return true; + default: + return false; + } +} diff --git a/src/app/features/container/utils/get-title-from-url.ts b/src/app/features/container/utils/get-title-from-url.ts new file mode 100644 index 00000000000..42ed7fe7a01 --- /dev/null +++ b/src/app/features/container/utils/get-title-from-url.ts @@ -0,0 +1,36 @@ +import { RouteUrls } from '@shared/route-urls'; + +export function getTitleFromUrl(pathname: RouteUrls) { + if (pathname.match(RouteUrls.SendCryptoAsset)) { + // don't show send on first step of send flow or popuop transfer + if (pathname === RouteUrls.SendCryptoAsset) return undefined; + if (pathname === RouteUrls.RpcSendTransfer) return undefined; + return 'Send'; + } + + switch (pathname) { + case RouteUrls.AddNetwork: + return 'Add a network'; + case RouteUrls.BitcoinContractList: + return 'Bitcoin Contracts'; + case RouteUrls.BitcoinContractLockSuccess: + return 'Locked Bitcoin'; + case RouteUrls.SendBrc20ChooseFee: + return 'Choose fee'; + case RouteUrls.SendBrc20Confirmation: + case RouteUrls.SwapReview: + case RouteUrls.SendBrc20Confirmation: + case '/send/btc/confirm': + return 'Review'; + case RouteUrls.Swap: + return 'Swap'; + case RouteUrls.SentStxTxSummary: + case RouteUrls.SentBtcTxSummary: + return 'Sent'; + case RouteUrls.SentBrc20Summary: + return 'Creating transfer inscription'; + case RouteUrls.SendBrc20Confirmation: + default: + return undefined; + } +} diff --git a/src/app/features/container/utils/route-helpers.ts b/src/app/features/container/utils/route-helpers.ts new file mode 100644 index 00000000000..868bea018dd --- /dev/null +++ b/src/app/features/container/utils/route-helpers.ts @@ -0,0 +1,80 @@ +import { RouteUrls } from '@shared/route-urls'; + +import { isKnownPopupRoute } from './get-popup-header'; + +function isHomePage(pathname: RouteUrls) { + return ( + pathname === RouteUrls.Home || + pathname.match(RouteUrls.Activity) || + pathname.match(RouteUrls.Receive) || + pathname.match(RouteUrls.SendOrdinalInscription) + ); +} + +export function isLandingPage(pathname: RouteUrls) { + return pathname === RouteUrls.RequestDiagnostics || pathname.match(RouteUrls.Onboarding); // need to match get-started/ledger +} + +function isOnboardingPage(pathname: RouteUrls) { + return ( + pathname === RouteUrls.BackUpSecretKey || + pathname === RouteUrls.SetPassword || + pathname === RouteUrls.SignIn || + pathname === RouteUrls.ViewSecretKey + ); +} + +function isFundPage(pathname: RouteUrls) { + return ( + pathname === RouteUrls.Fund.replace(':currency', 'STX') || + pathname === RouteUrls.Fund.replace(':currency', 'BTC') + ); +} + +export function getPageVariant(pathname: RouteUrls) { + if (isFundPage(pathname)) return 'fund'; + if (isHomePage(pathname)) return 'home'; + if (isOnboardingPage(pathname)) return 'onboarding'; + return 'page'; +} + +export function getIsSessionLocked(pathname: RouteUrls) { + return pathname === RouteUrls.Unlock; +} + +export function isSummaryPage(pathname: RouteUrls) { + /* TODO refactor the summary routes to make this cleaner + we need to block going back from summary pages catching the dynamic routes: + SentBtcTxSummary = '/sent/btc/:txId', + SentStxTxSummary = '/sent/stx/:txId', + SentBrc20Summary = '/send/brc20/:ticker/summary', + RpcSignPsbtSummary = '/sign-psbt/summary', + RpcSendTransferSummary = '/send-transfer/summary', + */ + return pathname.match('/sent/stx/') || pathname.match('/sent/btc/' || pathname.match('summary')); +} + +export function canGoBack(pathname: RouteUrls) { + if (getIsSessionLocked(pathname) || isKnownPopupRoute(pathname) || isSummaryPage(pathname)) { + return false; + } + return true; +} + +export function hideLogo(pathname: RouteUrls) { + return pathname === RouteUrls.RpcGetAddresses || pathname === RouteUrls.ViewSecretKey; +} + +export function isNoHeaderPopup(pathname: RouteUrls) { + return pathname === RouteUrls.RpcGetAddresses || pathname === RouteUrls.ChooseAccount; +} + +export function hideSettingsOnSm(pathname: RouteUrls) { + switch (pathname) { + case RouteUrls.SendCryptoAsset: + case RouteUrls.FundChooseCurrency: + return true; + default: + return false; + } +} diff --git a/src/app/features/current-account/current-account-avatar.tsx b/src/app/features/current-account/current-account-avatar.tsx index 39296fac380..7e5cc23bd15 100644 --- a/src/app/features/current-account/current-account-avatar.tsx +++ b/src/app/features/current-account/current-account-avatar.tsx @@ -3,26 +3,27 @@ import { memo } from 'react'; import { CircleProps } from 'leather-styles/jsx'; import { useCurrentAccountDisplayName } from '@app/common/hooks/account/use-account-names'; -import { useDrawers } from '@app/common/hooks/use-drawers'; -import { AccountAvatar } from '@app/components/account/account-avatar'; import { useCurrentAccountIndex } from '@app/store/accounts/account'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; +import { AccountAvatar } from '@app/ui/components/account/account-avatar/account-avatar'; -export const CurrentAccountAvatar = memo((props: CircleProps) => { +interface CurrentAccountAvatar extends CircleProps { + toggleSwitchAccount(): void; +} +export const CurrentAccountAvatar = memo(({ toggleSwitchAccount }: CurrentAccountAvatar) => { const accountIndex = useCurrentAccountIndex(); const accounts = useStacksAccounts(); const currentAccount = accounts[accountIndex] as StacksAccount | undefined; const name = useCurrentAccountDisplayName(); - const { setIsShowingSwitchAccountsState } = useDrawers(); + if (!currentAccount) return null; return ( setIsShowingSwitchAccountsState(true)} + onClick={() => toggleSwitchAccount()} publicKey={currentAccount.stxPublicKey} - {...props} /> ); }); diff --git a/src/app/features/current-account/popup-header.tsx b/src/app/features/current-account/popup-header.tsx deleted file mode 100644 index b0c2cfc9748..00000000000 --- a/src/app/features/current-account/popup-header.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Suspense } from 'react'; - -import { Box, HStack, styled } from 'leather-styles/jsx'; - -import { BtcBalance } from '@app/components/balance-btc'; -import { StxBalance } from '@app/components/balance-stx'; -import { LoadingRectangle } from '@app/components/loading-rectangle'; -import { NetworkModeBadge } from '@app/components/network-mode-badge'; -import { CurrentAccountAvatar } from '@app/features/current-account/current-account-avatar'; -import { CurrentAccountName } from '@app/features/current-account/current-account-name'; -import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { Flag } from '@app/ui/components/flag/flag'; - -interface PopupHeaderLayoutProps { - children: React.ReactNode; -} -function PopupHeaderLayout({ children }: PopupHeaderLayoutProps) { - return ( - - {children} - - ); -} - -interface PopupHeaderProps { - displayAddresssBalanceOf?: 'all' | 'stx'; -} -function PopupHeaderSuspense({ displayAddresssBalanceOf = 'stx' }: PopupHeaderProps) { - const account = useCurrentStacksAccount(); - const isBitcoinEnabled = useConfigBitcoinEnabled(); - return ( - - } - > - - - - - - - - {account && displayAddresssBalanceOf === 'stx' && ( - - )} - {isBitcoinEnabled && displayAddresssBalanceOf === 'all' && } - - - - - ); -} - -function PopupHeaderFallback() { - return ( - - - - ); -} - -export function PopupHeader(props: PopupHeaderProps) { - return ( - }> - - - ); -} diff --git a/src/app/features/edit-nonce-drawer/components/edit-nonce-field.tsx b/src/app/features/dialogs/edit-nonce-dialog/components/edit-nonce-field.tsx similarity index 100% rename from src/app/features/edit-nonce-drawer/components/edit-nonce-field.tsx rename to src/app/features/dialogs/edit-nonce-dialog/components/edit-nonce-field.tsx diff --git a/src/app/features/edit-nonce-drawer/components/edit-nonce-form.tsx b/src/app/features/dialogs/edit-nonce-dialog/components/edit-nonce-form.tsx similarity index 100% rename from src/app/features/edit-nonce-drawer/components/edit-nonce-form.tsx rename to src/app/features/dialogs/edit-nonce-dialog/components/edit-nonce-form.tsx diff --git a/src/app/features/edit-nonce-drawer/edit-nonce-drawer.tsx b/src/app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog.tsx similarity index 76% rename from src/app/features/edit-nonce-drawer/edit-nonce-drawer.tsx rename to src/app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog.tsx index 1d72b3382dd..3d517a7d150 100644 --- a/src/app/features/edit-nonce-drawer/edit-nonce-drawer.tsx +++ b/src/app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog.tsx @@ -8,25 +8,15 @@ import { StacksSendFormValues, StacksTransactionFormValues } from '@shared/model import { useOnMount } from '@app/common/hooks/use-on-mount'; import { openInNewTab } from '@app/common/utils/open-in-new-tab'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { Link } from '@app/ui/components/link/link'; import { EditNonceForm } from './components/edit-nonce-form'; const url = 'https://leather.gitbook.io/guides/transactions/nonces'; -function CustomFeeMessaging() { - return ( - - If your transaction has been pending for a long time, its nonce might not be correct. - openInNewTab(url)}> - Learn more. - - - ); -} - -export function EditNonceDrawer() { +export function EditNonceDialog() { const { errors, setFieldError, setFieldValue, validateField, values } = useFormikContext< StacksSendFormValues | StacksTransactionFormValues >(); @@ -36,7 +26,6 @@ export function EditNonceDrawer() { const { search } = useLocation(); useOnMount(() => setLoadedNextNonce(values.nonce)); - const onGoBack = useCallback(() => { if (search) { return navigate('..' + search, { replace: true }); @@ -59,11 +48,16 @@ export function EditNonceDrawer() { }, [loadedNextNonce, onGoBack, setFieldError, setFieldValue, values.nonce]); return ( - + }> - + + If your transaction has been pending for a long time, its nonce might not be correct. + openInNewTab(url)}> + Learn more. + + - + ); } diff --git a/src/app/features/dialogs/high-fee-dialog/high-fee-dialog.tsx b/src/app/features/dialogs/high-fee-dialog/high-fee-dialog.tsx new file mode 100644 index 00000000000..36cc180da11 --- /dev/null +++ b/src/app/features/dialogs/high-fee-dialog/high-fee-dialog.tsx @@ -0,0 +1,77 @@ +import { useEffect, useState } from 'react'; + +import { useFormikContext } from 'formik'; +import { HStack, Stack } from 'leather-styles/jsx'; + +import { + BitcoinSendFormValues, + StacksSendFormValues, + StacksTransactionFormValues, +} from '@shared/models/form.model'; + +import { openInNewTab } from '@app/common/utils/open-in-new-tab'; +import { Button } from '@app/ui/components/button/button'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Header } from '@app/ui/components/containers/headers/header'; +import { Link } from '@app/ui/components/link/link'; +import { Caption } from '@app/ui/components/typography/caption'; +import { Title } from '@app/ui/components/typography/title'; +import { ErrorIcon } from '@app/ui/icons'; + +interface HighFeeDialogProps { + learnMoreUrl: string; + isShowing?: boolean; +} + +export function HighFeeDialog({ learnMoreUrl, isShowing = false }: HighFeeDialogProps) { + const [isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation] = useState(isShowing); + + useEffect(() => { + return () => { + if (isShowingHighFeeConfirmation) setIsShowingHighFeeConfirmation(false); + }; + }, [isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation]); + + const { handleSubmit, values } = useFormikContext< + BitcoinSendFormValues | StacksSendFormValues | StacksTransactionFormValues + >(); + return ( + } + isShowing={isShowingHighFeeConfirmation} + onClose={() => setIsShowingHighFeeConfirmation(false)} + footer={ +
+ + +
+ } + > + + + + + Are you sure you want to pay {values.fee} {values.feeCurrency} in fees for this + transaction? + + + + This action cannot be undone and the fees won't be returned, even if the transaction + fails. + openInNewTab(learnMoreUrl)} size="sm"> + Learn more + + + +
+ ); +} diff --git a/src/app/features/increase-fee-drawer/components/fee-multiplier-button.tsx b/src/app/features/dialogs/increase-fee-dialog/components/fee-multiplier-button.tsx similarity index 100% rename from src/app/features/increase-fee-drawer/components/fee-multiplier-button.tsx rename to src/app/features/dialogs/increase-fee-dialog/components/fee-multiplier-button.tsx diff --git a/src/app/features/increase-fee-drawer/components/fee-multiplier.tsx b/src/app/features/dialogs/increase-fee-dialog/components/fee-multiplier.tsx similarity index 100% rename from src/app/features/increase-fee-drawer/components/fee-multiplier.tsx rename to src/app/features/dialogs/increase-fee-dialog/components/fee-multiplier.tsx diff --git a/src/app/features/increase-fee-drawer/components/increase-fee-actions.tsx b/src/app/features/dialogs/increase-fee-dialog/components/increase-fee-actions.tsx similarity index 85% rename from src/app/features/increase-fee-drawer/components/increase-fee-actions.tsx rename to src/app/features/dialogs/increase-fee-dialog/components/increase-fee-actions.tsx index 4d4a757f7fb..170855c8486 100644 --- a/src/app/features/increase-fee-drawer/components/increase-fee-actions.tsx +++ b/src/app/features/dialogs/increase-fee-dialog/components/increase-fee-actions.tsx @@ -1,5 +1,4 @@ import { useFormikContext } from 'formik'; -import { Flex } from 'leather-styles/jsx'; import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; import { useWalletType } from '@app/common/use-wallet-type'; @@ -19,20 +18,20 @@ export function IncreaseFeeActions(props: IncreaseFeeActionsProps) { const actionText = whenWallet({ ledger: 'Confirm on Ledger', software: 'Submit' }); return ( - - - + ); } diff --git a/src/app/features/increase-fee-drawer/components/increase-fee-field.tsx b/src/app/features/dialogs/increase-fee-dialog/components/increase-fee-field.tsx similarity index 98% rename from src/app/features/increase-fee-drawer/components/increase-fee-field.tsx rename to src/app/features/dialogs/increase-fee-dialog/components/increase-fee-field.tsx index a12414bd881..5cc073b37c1 100644 --- a/src/app/features/increase-fee-drawer/components/increase-fee-field.tsx +++ b/src/app/features/dialogs/increase-fee-dialog/components/increase-fee-field.tsx @@ -53,7 +53,7 @@ export function IncreaseFeeField(props: IncreaseFeeFieldProps): React.JSX.Elemen bg="transparent" border="default" borderRadius="sm" - height="64px" + height="inputHeight" display="block" p="space.04" placeholder="Enter a custom fee" diff --git a/src/app/features/increase-fee-drawer/hooks/use-btc-increase-fee.ts b/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts similarity index 100% rename from src/app/features/increase-fee-drawer/hooks/use-btc-increase-fee.ts rename to src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts diff --git a/src/app/features/increase-fee-drawer/hooks/use-selected-tx.ts b/src/app/features/dialogs/increase-fee-dialog/hooks/use-selected-tx.ts similarity index 100% rename from src/app/features/increase-fee-drawer/hooks/use-selected-tx.ts rename to src/app/features/dialogs/increase-fee-dialog/hooks/use-selected-tx.ts diff --git a/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx b/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx new file mode 100644 index 00000000000..22b89a5c723 --- /dev/null +++ b/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx @@ -0,0 +1,110 @@ +import { Suspense } from 'react'; +import { Outlet, useLocation, useNavigate } from 'react-router-dom'; + +import { Formik } from 'formik'; +import { Flex, Stack } from 'leather-styles/jsx'; + +import { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; +import { RouteUrls } from '@shared/route-urls'; + +import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balance'; +import { useLocationStateWithCache } from '@app/common/hooks/use-location-state'; +import { formatMoney } from '@app/common/money/format-money'; +import { btcToSat } from '@app/common/money/unit-conversion'; +import { getBitcoinTxValue } from '@app/common/transactions/bitcoin/utils'; +import { BitcoinCustomFeeInput } from '@app/components/bitcoin-custom-fee/bitcoin-custom-fee-input'; +import { BitcoinTransactionItem } from '@app/components/bitcoin-transaction-item/bitcoin-transaction-item'; +import { LoadingSpinner } from '@app/components/loading-spinner'; +import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Header } from '@app/ui/components/containers/headers/header'; +import { Spinner } from '@app/ui/components/spinner'; +import { Caption } from '@app/ui/components/typography/caption'; + +import { IncreaseFeeActions } from './components/increase-fee-actions'; +import { useBtcIncreaseFee } from './hooks/use-btc-increase-fee'; + +export function IncreaseBtcFeeDialog() { + const tx = useLocationStateWithCache('btcTx') as BitcoinTx; + const navigate = useNavigate(); + const location = useLocation(); + + const btcTx = tx; + const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); + const currentBitcoinAddress = nativeSegwitSigner.address; + const { btcAvailableAssetBalance } = useBtcAssetBalance(currentBitcoinAddress); + const { isBroadcasting, sizeInfo, onSubmit, validationSchema, recipient } = + useBtcIncreaseFee(btcTx); + + const balance = formatMoney(btcAvailableAssetBalance.balance); + + const onClose = () => { + navigate(RouteUrls.Home); + }; + + if (!tx) return null; + + const initialFeeRate = `${(tx.fee / sizeInfo.txVBytes).toFixed(0)}`; + + return ( + <> + + <> + } + footer={ +
+ navigate(RouteUrls.Home)} /> +
+ } + > + + + +
+ } + > + + If your transaction is pending for a long time, its fee might not be high enough + to be included in a block. Update the fee for a higher value and try again. + + + {btcTx && } + {isBroadcasting && } + + + + + + {btcAvailableAssetBalance && Balance: {balance}} + + + +
+ + + + + + ); +} diff --git a/src/app/features/increase-fee-drawer/increase-fee-sent-drawer.tsx b/src/app/features/dialogs/increase-fee-dialog/increase-fee-sent-dialog.tsx similarity index 60% rename from src/app/features/increase-fee-drawer/increase-fee-sent-drawer.tsx rename to src/app/features/dialogs/increase-fee-dialog/increase-fee-sent-dialog.tsx index 67fff526d0d..0edbf888d6e 100644 --- a/src/app/features/increase-fee-drawer/increase-fee-sent-drawer.tsx +++ b/src/app/features/dialogs/increase-fee-dialog/increase-fee-sent-dialog.tsx @@ -4,21 +4,26 @@ import { Flex } from 'leather-styles/jsx'; import { RouteUrls } from '@shared/route-urls'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { CheckmarkIcon } from '@app/ui/icons/checkmark-icon'; -export function IncreaseFeeSentDrawer() { +export function IncreaseFeeSentDialog() { const location = useLocation(); const navigate = useNavigate(); const isShowing = location.pathname === RouteUrls.IncreaseFeeSent; return ( <> - navigate(RouteUrls.Home)} title="Confirmed"> + navigate(RouteUrls.Home)} + header={
} + > - +
); diff --git a/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx b/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx new file mode 100644 index 00000000000..bd4291f0722 --- /dev/null +++ b/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx @@ -0,0 +1,152 @@ +import { Suspense, useCallback, useEffect } from 'react'; +import { Outlet, useLocation, useNavigate, useSearchParams } from 'react-router-dom'; + +import BigNumber from 'bignumber.js'; +import { Formik } from 'formik'; +import { Flex, Stack } from 'leather-styles/jsx'; +import * as yup from 'yup'; + +import { RouteUrls } from '@shared/route-urls'; + +import { useRefreshAllAccountData } from '@app/common/hooks/account/use-refresh-all-account-data'; +import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; +import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; +import { microStxToStx, stxToMicroStx } from '@app/common/money/unit-conversion'; +import { stacksValue } from '@app/common/stacks-utils'; +import { safelyFormatHexTxid } from '@app/common/utils/safe-handle-txid'; +import { stxFeeValidator } from '@app/common/validation/forms/fee-validators'; +import { LoadingSpinner } from '@app/components/loading-spinner'; +import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item'; +import { useStacksBroadcastTransaction } from '@app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction'; +import { useToast } from '@app/features/toasts/use-toast'; +import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useSubmittedTransactionsActions } from '@app/store/submitted-transactions/submitted-transactions.hooks'; +import { useRawDeserializedTxState, useRawTxIdState } from '@app/store/transactions/raw.hooks'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Header } from '@app/ui/components/containers/headers/header'; +import { Spinner } from '@app/ui/components/spinner'; +import { Caption } from '@app/ui/components/typography/caption'; + +import { IncreaseFeeActions } from './components/increase-fee-actions'; +import { IncreaseFeeField } from './components/increase-fee-field'; +import { useSelectedTx } from './hooks/use-selected-tx'; + +export function IncreaseStxFeeDialog() { + const [rawTxId, setRawTxId] = useRawTxIdState(); + const { isLoading, setIsIdle } = useLoading(LoadingKeys.INCREASE_FEE_DRAWER); + const navigate = useNavigate(); + const location = useLocation(); + const [searchParams] = useSearchParams(); + const txIdFromParams = searchParams.get('txId'); + const toast = useToast(); + const refreshAccountData = useRefreshAllAccountData(); + const tx = useSelectedTx(); + const [, setTxId] = useRawTxIdState(); + const { data: balances } = useCurrentStacksAccountBalances(); + const { availableBalance } = useStxBalance(); + const submittedTransactionsActions = useSubmittedTransactionsActions(); + const rawTx = useRawDeserializedTxState(); + const { stacksBroadcastTransaction } = useStacksBroadcastTransaction('STX'); + + const fee = Number(rawTx?.auth.spendingCondition?.fee); + + useEffect(() => { + if (tx?.tx_status !== 'pending' && rawTx) { + setTxId(null); + toast.info('Your transaction went through! No need to speed it up.'); + } + }, [rawTx, tx?.tx_status, setTxId, toast]); + + useEffect(() => { + if (!rawTxId && txIdFromParams) { + setRawTxId(txIdFromParams); + } + if (isLoading && !rawTxId) { + setIsIdle(); + } + }, [isLoading, rawTxId, setIsIdle, setRawTxId, txIdFromParams]); + + const onSubmit = useCallback( + async (values: any) => { + if (!tx || !rawTx) return; + rawTx.setFee(stxToMicroStx(values.fee).toString()); + const txId = tx.tx_id || safelyFormatHexTxid(rawTx.txid()); + await refreshAccountData(); + submittedTransactionsActions.transactionReplacedByFee(txId); + await stacksBroadcastTransaction(rawTx); + }, + [tx, rawTx, refreshAccountData, submittedTransactionsActions, stacksBroadcastTransaction] + ); + + if (!tx || !fee) return ; + + const validationSchema = yup.object({ fee: stxFeeValidator(availableBalance) }); + + const onClose = () => { + setRawTxId(null); + navigate(RouteUrls.Home); + }; + + return ( + <> + + {props => ( + <> + } + footer={ +
+ { + setTxId(null); + navigate(RouteUrls.Home); + }} + isDisabled={stxToMicroStx(props.values.fee).isEqualTo(fee)} + /> +
+ } + > + + + + + } + > + + If your transaction is pending for a long time, its fee might not be high enough + to be included in a block. Update the fee for a higher value and try again. + + + {tx && } + + + {balances?.stx.unlockedStx.amount && ( + + Balance: + {stacksValue({ value: availableBalance.amount, fixedDecimals: true })} + + )} + + + + +
+ + + )} +
+ + ); +} diff --git a/src/app/features/leather-intro-dialog/confetti-config.ts b/src/app/features/dialogs/leather-intro-dialog/confetti-config.ts similarity index 100% rename from src/app/features/leather-intro-dialog/confetti-config.ts rename to src/app/features/dialogs/leather-intro-dialog/confetti-config.ts diff --git a/src/app/features/leather-intro-dialog/leather-intro-dialog.tsx b/src/app/features/dialogs/leather-intro-dialog/leather-intro-dialog.tsx similarity index 90% rename from src/app/features/leather-intro-dialog/leather-intro-dialog.tsx rename to src/app/features/dialogs/leather-intro-dialog/leather-intro-dialog.tsx index e6e004686fb..dc59d5f6e00 100644 --- a/src/app/features/leather-intro-dialog/leather-intro-dialog.tsx +++ b/src/app/features/dialogs/leather-intro-dialog/leather-intro-dialog.tsx @@ -4,8 +4,6 @@ import { Outlet, Route, useNavigate } from 'react-router-dom'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { delay } from '@app/common/utils'; import { openInNewTab } from '@app/common/utils/open-in-new-tab'; -import { useAppDispatch } from '@app/store'; -import { settingsActions } from '@app/store/settings/settings.actions'; import { LeatherIntroDialog, @@ -38,8 +36,6 @@ export function useLeatherIntroDialogContext() { function LeatherIntroDialogContainer() { const analytics = useAnalytics(); const navigate = useNavigate(); - const dispatch = useAppDispatch(); - async function onRevealNewName() { void analytics.track('new_brand_reveal_name'); await delay(4000); @@ -48,7 +44,6 @@ function LeatherIntroDialogContainer() { async function onAcceptTerms() { void analytics.track('new_brand_accept_terms'); - dispatch(settingsActions.setHasApprovedNewBrand()); navigate('../', { replace: true }); } diff --git a/src/app/features/leather-intro-dialog/leather-intro-steps.tsx b/src/app/features/dialogs/leather-intro-dialog/leather-intro-steps.tsx similarity index 98% rename from src/app/features/leather-intro-dialog/leather-intro-steps.tsx rename to src/app/features/dialogs/leather-intro-dialog/leather-intro-steps.tsx index 9371c107d99..53312a25d0a 100644 --- a/src/app/features/leather-intro-dialog/leather-intro-steps.tsx +++ b/src/app/features/dialogs/leather-intro-dialog/leather-intro-steps.tsx @@ -99,7 +99,7 @@ export function LeatherIntroDialogPart2() { Learn more → - + Leather Wallet will now be provided by Leather Wallet LLC [a subsidiary of Nassau Machines Inc]. Please review and accept Leather Wallet{' '} diff --git a/src/app/features/switch-account-drawer/components/account-list-unavailable.tsx b/src/app/features/dialogs/switch-account-dialog/components/account-list-unavailable.tsx similarity index 100% rename from src/app/features/switch-account-drawer/components/account-list-unavailable.tsx rename to src/app/features/dialogs/switch-account-dialog/components/account-list-unavailable.tsx diff --git a/src/app/features/switch-account-drawer/components/switch-account-list-item.tsx b/src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx similarity index 95% rename from src/app/features/switch-account-drawer/components/switch-account-list-item.tsx rename to src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx index 432b4a4e798..323882e01b3 100644 --- a/src/app/features/switch-account-drawer/components/switch-account-list-item.tsx +++ b/src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx @@ -9,8 +9,7 @@ import { AccountListItemLayout } from '@app/components/account/account-list-item import { AccountNameLayout } from '@app/components/account/account-name'; import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; - -import { AccountAvatarItem } from '../../../components/account/account-avatar-item'; +import { AccountAvatarItem } from '@app/ui/components/account/account-avatar/account-avatar-item'; interface SwitchAccountListItemProps { handleClose(): void; diff --git a/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx b/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx new file mode 100644 index 00000000000..491e323d65b --- /dev/null +++ b/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx @@ -0,0 +1,83 @@ +import { memo } from 'react'; +import { Virtuoso } from 'react-virtuoso'; + +import { Box } from 'leather-styles/jsx'; + +import { useCreateAccount } from '@app/common/hooks/account/use-create-account'; +import { useWalletType } from '@app/common/use-wallet-type'; +import { useCurrentAccountIndex } from '@app/store/accounts/account'; +import { useFilteredBitcoinAccounts } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; +import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useHasLedgerKeys } from '@app/store/ledger/ledger.selectors'; +import { Button } from '@app/ui/components/button/button'; +import { Dialog, DialogProps, getHeightOffset } from '@app/ui/components/containers/dialog/dialog'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Header } from '@app/ui/components/containers/headers/header'; +import { virtuosoHeight, virtuosoStyles } from '@app/ui/shared/virtuoso'; + +import { AccountListUnavailable } from './components/account-list-unavailable'; +import { SwitchAccountListItem } from './components/switch-account-list-item'; + +export const SwitchAccountDialog = memo(({ isShowing, onClose }: DialogProps) => { + const currentAccountIndex = useCurrentAccountIndex(); + const createAccount = useCreateAccount(); + const { whenWallet } = useWalletType(); + const isLedger = useHasLedgerKeys(); + + const stacksAccounts = useStacksAccounts(); + const bitcoinAccounts = useFilteredBitcoinAccounts(); + const btcAddressesNum = bitcoinAccounts.length / 2; + const stacksAddressesNum = stacksAccounts.length; + + const onCreateAccount = () => { + createAccount(); + onClose(); + }; + + if (isShowing && stacksAddressesNum === 0 && btcAddressesNum === 0) { + return ; + } + // #4370 SMELL without this early return the wallet crashes on new install with + // : Wallet is neither of type `ledger` nor `software` + // FIXME remove this when adding Create Account to Ledger in #2502 #4983 + if (!isShowing) return null; + + const accountNum = stacksAddressesNum || btcAddressesNum; + + return ( + } + isShowing={isShowing} + onClose={onClose} + footer={whenWallet({ + software: ( +
+ +
+ ), + ledger: null, + })} + > + ( + + + + )} + /> +
+ ); +}); diff --git a/src/app/features/errors/app-error-boundary.tsx b/src/app/features/errors/app-error-boundary.tsx index f13964586c5..e0b32cee18b 100644 --- a/src/app/features/errors/app-error-boundary.tsx +++ b/src/app/features/errors/app-error-boundary.tsx @@ -2,8 +2,6 @@ import { Box, Stack, styled } from 'leather-styles/jsx'; import { Prism } from '@app/common/clarity-prism'; import { HasChildren } from '@app/common/has-children'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { Header } from '@app/components/header'; import { ErrorBoundary, FallbackProps, useErrorHandler } from '@app/features/errors/error-boundary'; import { openGithubIssue } from '@app/features/errors/utils'; import { useErrorStackTraceState } from '@app/store/ui/ui.hooks'; @@ -13,8 +11,6 @@ import { Title } from '@app/ui/components/typography/title'; function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { const [value] = useErrorStackTraceState(); - useRouteHeader(
); - return ( Something went wrong diff --git a/src/app/features/high-fee-drawer/components/high-fee-confirmation.tsx b/src/app/features/high-fee-drawer/components/high-fee-confirmation.tsx deleted file mode 100644 index 51aaa6015b2..00000000000 --- a/src/app/features/high-fee-drawer/components/high-fee-confirmation.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useFormikContext } from 'formik'; -import { HStack, Stack } from 'leather-styles/jsx'; - -import { - BitcoinSendFormValues, - StacksSendFormValues, - StacksTransactionFormValues, -} from '@shared/models/form.model'; - -import { useDrawers } from '@app/common/hooks/use-drawers'; -import { openInNewTab } from '@app/common/utils/open-in-new-tab'; -import { Button } from '@app/ui/components/button/button'; -import { Link } from '@app/ui/components/link/link'; -import { Caption } from '@app/ui/components/typography/caption'; -import { Title } from '@app/ui/components/typography/title'; - -export function HighFeeConfirmation({ learnMoreUrl }: { learnMoreUrl: string }) { - const { handleSubmit, values } = useFormikContext< - BitcoinSendFormValues | StacksSendFormValues | StacksTransactionFormValues - >(); - const { setIsShowingHighFeeConfirmation } = useDrawers(); - - return ( - - - Are you sure you want to pay {values.fee} {values.feeCurrency} in fees for this transaction? - - - This action cannot be undone and the fees won't be returned, even if the transaction fails.{' '} - openInNewTab(learnMoreUrl)} size="sm"> - Learn more - - - - - - - - ); -} diff --git a/src/app/features/high-fee-drawer/high-fee-drawer.tsx b/src/app/features/high-fee-drawer/high-fee-drawer.tsx deleted file mode 100644 index fa8d6d877df..00000000000 --- a/src/app/features/high-fee-drawer/high-fee-drawer.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect } from 'react'; - -import { useDrawers } from '@app/common/hooks/use-drawers'; -import { ControlledDrawer } from '@app/components/drawer/controlled-drawer'; -import { ErrorIcon } from '@app/ui/icons/error-icon'; - -import { HighFeeConfirmation } from './components/high-fee-confirmation'; - -export function HighFeeDrawer(props: { learnMoreUrl: string }) { - const { learnMoreUrl } = props; - const { isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation } = useDrawers(); - - useEffect(() => { - return () => { - if (isShowingHighFeeConfirmation) setIsShowingHighFeeConfirmation(false); - }; - }, [isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation]); - - return ( - } - isShowing={isShowingHighFeeConfirmation} - onClose={() => setIsShowingHighFeeConfirmation(false)} - > - {isShowingHighFeeConfirmation && } - - ); -} diff --git a/src/app/features/hiro-messages/components/in-app-message-item.tsx b/src/app/features/hiro-messages/components/in-app-message-item.tsx index 9fca95bb6f5..b68c1faaa95 100644 --- a/src/app/features/hiro-messages/components/in-app-message-item.tsx +++ b/src/app/features/hiro-messages/components/in-app-message-item.tsx @@ -41,7 +41,7 @@ export function HiroMessageItem(props: HiroMessageItemProps) { )} - {hasApprovedNewBrand ? : } + ); } @@ -20,12 +17,3 @@ function LeatherMetaTags() { ); } - -function HiroMetaTags() { - return ( - <> - Hiro Wallet - - - ); -} diff --git a/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx b/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx deleted file mode 100644 index 83f8cca436e..00000000000 --- a/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useNavigate } from 'react-router-dom'; - -import { Formik } from 'formik'; -import { Stack } from 'leather-styles/jsx'; - -import { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; -import { RouteUrls } from '@shared/route-urls'; - -import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balance'; -import { formatMoney } from '@app/common/money/format-money'; -import { btcToSat } from '@app/common/money/unit-conversion'; -import { getBitcoinTxValue } from '@app/common/transactions/bitcoin/utils'; -import { BitcoinCustomFeeInput } from '@app/components/bitcoin-custom-fee/bitcoin-custom-fee-input'; -import { BitcoinTransactionItem } from '@app/components/bitcoin-transaction-item/bitcoin-transaction-item'; -import { LoadingSpinner } from '@app/components/loading-spinner'; -import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { Caption } from '@app/ui/components/typography/caption'; - -import { useBtcIncreaseFee } from '../hooks/use-btc-increase-fee'; -import { IncreaseFeeActions } from './increase-fee-actions'; - -interface IncreaseBtcFeeFormProps { - btcTx: BitcoinTx; -} - -export function IncreaseBtcFeeForm({ btcTx }: IncreaseBtcFeeFormProps) { - const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); - const navigate = useNavigate(); - const currentBitcoinAddress = nativeSegwitSigner.address; - const { btcAvailableAssetBalance } = useBtcAssetBalance(currentBitcoinAddress); - const { isBroadcasting, sizeInfo, onSubmit, validationSchema, recipient } = - useBtcIncreaseFee(btcTx); - - const balance = formatMoney(btcAvailableAssetBalance.balance); - - if (isBroadcasting) { - return ; - } - - const initialFeeRate = `${(btcTx.fee / sizeInfo.txVBytes).toFixed(0)}`; - return ( - - - {btcTx && } - - - - - - {btcAvailableAssetBalance && Balance: {balance}} - - navigate(RouteUrls.Home)} /> - - - ); -} diff --git a/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx b/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx deleted file mode 100644 index 71baa6d5f36..00000000000 --- a/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { useCallback, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import BigNumber from 'bignumber.js'; -import { Formik } from 'formik'; -import { Stack } from 'leather-styles/jsx'; -import * as yup from 'yup'; - -import { RouteUrls } from '@shared/route-urls'; - -import { useRefreshAllAccountData } from '@app/common/hooks/account/use-refresh-all-account-data'; -import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; -import { microStxToStx, stxToMicroStx } from '@app/common/money/unit-conversion'; -import { stacksValue } from '@app/common/stacks-utils'; -import { safelyFormatHexTxid } from '@app/common/utils/safe-handle-txid'; -import { stxFeeValidator } from '@app/common/validation/forms/fee-validators'; -import { LoadingSpinner } from '@app/components/loading-spinner'; -import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item'; -import { useStacksBroadcastTransaction } from '@app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction'; -import { useToast } from '@app/features/toasts/use-toast'; -import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; -import { useSubmittedTransactionsActions } from '@app/store/submitted-transactions/submitted-transactions.hooks'; -import { useRawDeserializedTxState, useRawTxIdState } from '@app/store/transactions/raw.hooks'; -import { Caption } from '@app/ui/components/typography/caption'; - -import { useSelectedTx } from '../hooks/use-selected-tx'; -import { IncreaseFeeActions } from './increase-fee-actions'; -import { IncreaseFeeField } from './increase-fee-field'; - -export function IncreaseStxFeeForm() { - const toast = useToast(); - const refreshAccountData = useRefreshAllAccountData(); - const tx = useSelectedTx(); - const navigate = useNavigate(); - const [, setTxId] = useRawTxIdState(); - const { data: balances } = useCurrentStacksAccountBalances(); - const { availableBalance } = useStxBalance(); - const submittedTransactionsActions = useSubmittedTransactionsActions(); - const rawTx = useRawDeserializedTxState(); - const { stacksBroadcastTransaction } = useStacksBroadcastTransaction('STX'); - - const fee = Number(rawTx?.auth.spendingCondition?.fee); - - useEffect(() => { - if (tx?.tx_status !== 'pending' && rawTx) { - setTxId(null); - toast.info('Your transaction went through! No need to speed it up.'); - } - }, [rawTx, tx?.tx_status, setTxId, toast]); - - const onSubmit = useCallback( - async (values: any) => { - if (!tx || !rawTx) return; - rawTx.setFee(stxToMicroStx(values.fee).toString()); - const txId = tx.tx_id || safelyFormatHexTxid(rawTx.txid()); - await refreshAccountData(); - submittedTransactionsActions.transactionReplacedByFee(txId); - await stacksBroadcastTransaction(rawTx); - }, - [tx, rawTx, refreshAccountData, submittedTransactionsActions, stacksBroadcastTransaction] - ); - - if (!tx || !fee) return ; - - const validationSchema = yup.object({ fee: stxFeeValidator(availableBalance) }); - - return ( - - {props => ( - - {tx && } - - - {balances?.stx.unlockedStx.amount && ( - - Balance: {stacksValue({ value: availableBalance.amount, fixedDecimals: true })} - - )} - - { - setTxId(null); - navigate(RouteUrls.Home); - }} - isDisabled={stxToMicroStx(props.values.fee).isEqualTo(fee)} - /> - - )} - - ); -} diff --git a/src/app/features/increase-fee-drawer/increase-btc-fee-drawer.tsx b/src/app/features/increase-fee-drawer/increase-btc-fee-drawer.tsx deleted file mode 100644 index 16ade832348..00000000000 --- a/src/app/features/increase-fee-drawer/increase-btc-fee-drawer.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useLocation, useNavigate } from 'react-router-dom'; - -import { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; -import { RouteUrls } from '@shared/route-urls'; - -import { useLocationStateWithCache } from '@app/common/hooks/use-location-state'; - -import { IncreaseBtcFeeForm } from './components/increase-btc-fee-form'; -import { IncreaseFeeDrawer } from './increase-fee-drawer'; - -function useIncreaseBtcFeeDrawerState() { - return { - tx: useLocationStateWithCache('btcTx') as BitcoinTx, - }; -} - -export function IncreaseBtcFeeDrawer() { - const { tx } = useIncreaseBtcFeeDrawerState(); - const navigate = useNavigate(); - const location = useLocation(); - - const onClose = () => { - navigate(RouteUrls.Home); - }; - - if (!tx) return null; - - return ( - } - onClose={onClose} - isShowing={location.pathname === RouteUrls.IncreaseBtcFee} - /> - ); -} diff --git a/src/app/features/increase-fee-drawer/increase-fee-drawer.tsx b/src/app/features/increase-fee-drawer/increase-fee-drawer.tsx deleted file mode 100644 index b642400598a..00000000000 --- a/src/app/features/increase-fee-drawer/increase-fee-drawer.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Suspense } from 'react'; -import { Outlet } from 'react-router-dom'; - -import { Flex, Stack } from 'leather-styles/jsx'; - -import { BaseDrawer } from '@app/components/drawer/base-drawer'; -import { Spinner } from '@app/ui/components/spinner'; -import { Caption } from '@app/ui/components/typography/caption'; - -interface IncreaseFeeDrawerProps { - feeForm: React.JSX.Element; - onClose(): void; - isShowing: boolean; -} -export function IncreaseFeeDrawer({ feeForm, onClose, isShowing }: IncreaseFeeDrawerProps) { - return ( - <> - - - - - - } - > - - If your transaction is pending for a long time, its fee might not be high enough to be - included in a block. Update the fee for a higher value and try again. - - {feeForm && feeForm} - - - - - - ); -} diff --git a/src/app/features/increase-fee-drawer/increase-stx-fee-drawer.tsx b/src/app/features/increase-fee-drawer/increase-stx-fee-drawer.tsx deleted file mode 100644 index 9c81618759e..00000000000 --- a/src/app/features/increase-fee-drawer/increase-stx-fee-drawer.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useEffect } from 'react'; -import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; - -import { RouteUrls } from '@shared/route-urls'; - -import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; -import { useRawTxIdState } from '@app/store/transactions/raw.hooks'; - -import { IncreaseStxFeeForm } from './components/increase-stx-fee-form'; -import { IncreaseFeeDrawer } from './increase-fee-drawer'; - -export function IncreaseStxFeeDrawer() { - const [rawTxId, setRawTxId] = useRawTxIdState(); - const { isLoading, setIsIdle } = useLoading(LoadingKeys.INCREASE_FEE_DRAWER); - const navigate = useNavigate(); - const location = useLocation(); - const [searchParams] = useSearchParams(); - const txIdFromParams = searchParams.get('txId'); - - const onClose = () => { - setRawTxId(null); - navigate(RouteUrls.Home); - }; - - useEffect(() => { - if (!rawTxId && txIdFromParams) { - setRawTxId(txIdFromParams); - } - if (isLoading && !rawTxId) { - setIsIdle(); - } - }, [isLoading, rawTxId, setIsIdle, setRawTxId, txIdFromParams]); - - return ( - } - onClose={onClose} - isShowing={location.pathname === RouteUrls.IncreaseStxFee} - /> - ); -} diff --git a/src/app/features/ledger/components/ledger-wrapper.tsx b/src/app/features/ledger/components/ledger-wrapper.tsx index 7f5ca90632b..c591ed2997e 100644 --- a/src/app/features/ledger/components/ledger-wrapper.tsx +++ b/src/app/features/ledger/components/ledger-wrapper.tsx @@ -6,7 +6,7 @@ interface LedgerWrapperProps extends BoxProps { export function LedgerWrapper({ image, children, ...props }: LedgerWrapperProps) { return ( - + {image && {image}} {children} diff --git a/src/app/features/ledger/flows/jwt-signing/ledger-sign-jwt-container.tsx b/src/app/features/ledger/flows/jwt-signing/ledger-sign-jwt-container.tsx index c1b718944f7..d28a93ce2b7 100644 --- a/src/app/features/ledger/flows/jwt-signing/ledger-sign-jwt-container.tsx +++ b/src/app/features/ledger/flows/jwt-signing/ledger-sign-jwt-container.tsx @@ -16,7 +16,6 @@ import { useKeyActions } from '@app/common/hooks/use-key-actions'; import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; import { makeLedgerCompatibleUnsignedAuthResponsePayload } from '@app/common/unsafe-auth-response'; import { delay } from '@app/common/utils'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; import { getStacksAppVersion, prepareLedgerDeviceStacksAppConnection, @@ -26,6 +25,8 @@ import { useCurrentStacksAccount, useStacksAccounts, } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { useLedgerNavigate } from '../../hooks/use-ledger-navigate'; import { checkLockedDeviceError, useLedgerResponseState } from '../../utils/generic-ledger-utils'; @@ -180,15 +181,18 @@ export function LedgerSignJwtContainer() { return ( - + } onClose={onCancelConnectLedger} - pauseOnClickOutside - waitingOnPerformedActionMessage="Ledger device in use" > - + ); } diff --git a/src/app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg-container.tsx b/src/app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg-container.tsx index c16b0d498f0..6554b51c2d2 100644 --- a/src/app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg-container.tsx +++ b/src/app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg-container.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Outlet, useLocation } from 'react-router-dom'; +import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import { bytesToHex, signatureVrsToRsv } from '@stacks/common'; import { serializeCV } from '@stacks/transactions'; @@ -12,7 +12,6 @@ import { isError } from '@shared/utils'; import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; import { appEvents } from '@app/common/publish-subscribe'; import { delay } from '@app/common/utils'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; import { getStacksAppVersion, prepareLedgerDeviceStacksAppConnection, @@ -22,6 +21,8 @@ import { } from '@app/features/ledger/utils/stacks-ledger-utils'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { useLedgerAnalytics } from '../../hooks/use-ledger-analytics.hook'; import { useLedgerNavigate } from '../../hooks/use-ledger-navigate'; @@ -50,6 +51,7 @@ function LedgerSignMsgData({ children }: LedgerSignMsgDataProps) { type LedgerSignMsgProps = LedgerSignMsgData; function LedgerSignStacksMsg({ account, unsignedMessage }: LedgerSignMsgProps) { useScrollLock(true); + const navigate = useNavigate(); const location = useLocation(); const ledgerNavigate = useLedgerNavigate(); @@ -150,16 +152,19 @@ function LedgerSignStacksMsg({ account, unsignedMessage }: LedgerSignMsgProps) { return ( - navigate(-1) : undefined} isShowing - isWaitingOnPerformedAction={awaitingDeviceConnection || canUserCancelAction} + header={ +
+ } onClose={ledgerNavigate.cancelLedgerAction} - pauseOnClickOutside - waitingOnPerformedActionMessage="Ledger device in use" > - + ); } diff --git a/src/app/features/ledger/generic-flows/request-keys/request-keys-flow.tsx b/src/app/features/ledger/generic-flows/request-keys/request-keys-flow.tsx index 67b4f00dc8c..d1aaec30355 100644 --- a/src/app/features/ledger/generic-flows/request-keys/request-keys-flow.tsx +++ b/src/app/features/ledger/generic-flows/request-keys/request-keys-flow.tsx @@ -1,7 +1,8 @@ import { Outlet, useNavigate } from 'react-router-dom'; import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { LedgerRequestKeysContext, LedgerRequestKeysProvider } from './ledger-request-keys.context'; @@ -20,15 +21,13 @@ export function RequestKeysFlow({ return ( - } onClose={onCancelConnectLedger ? onCancelConnectLedger : () => navigate('../')} - pauseOnClickOutside - waitingOnPerformedActionMessage="Ledger device in use" > - + ); } diff --git a/src/app/features/ledger/generic-flows/tx-signing/tx-signing-flow.tsx b/src/app/features/ledger/generic-flows/tx-signing/tx-signing-flow.tsx index ded18d87988..1dcf101f95a 100644 --- a/src/app/features/ledger/generic-flows/tx-signing/tx-signing-flow.tsx +++ b/src/app/features/ledger/generic-flows/tx-signing/tx-signing-flow.tsx @@ -1,8 +1,9 @@ -import { Outlet } from 'react-router-dom'; +import { Outlet, useNavigate } from 'react-router-dom'; import { useLocationState } from '@app/common/hooks/use-location-state'; import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { useActionCancellableByUser } from '../../utils/stacks-ledger-utils'; import { LedgerTxSigningContext, LedgerTxSigningProvider } from './ledger-sign-tx.context'; @@ -17,22 +18,26 @@ export function TxSigningFlow({ awaitingDeviceConnection, closeAction, }: TxSigningFlowProps) { + const navigate = useNavigate(); useScrollLock(true); const allowUserToGoBack = useLocationState('goBack'); const canUserCancelAction = useActionCancellableByUser(); return ( - navigate(-1) : undefined} isShowing - isWaitingOnPerformedAction={awaitingDeviceConnection || canUserCancelAction} + header={ +
+ } onClose={closeAction} - pauseOnClickOutside - waitingOnPerformedActionMessage="Ledger device in use" > - + ); } diff --git a/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.layout.tsx b/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.layout.tsx index 870f03076bb..a77b4b54a75 100644 --- a/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.layout.tsx +++ b/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.layout.tsx @@ -34,7 +34,7 @@ export function ConnectLedgerErrorLayout(props: ConnectLedgerErrorLayoutProps) { const { warningText, onTryAgain, appName } = props; return ( - + diff --git a/src/app/features/ledger/generic-steps/connect-device/connect-ledger-start.tsx b/src/app/features/ledger/generic-steps/connect-device/connect-ledger-start.tsx index 18f1987f45f..43606978d31 100644 --- a/src/app/features/ledger/generic-steps/connect-device/connect-ledger-start.tsx +++ b/src/app/features/ledger/generic-steps/connect-device/connect-ledger-start.tsx @@ -5,7 +5,8 @@ import { closeWindow } from '@shared/utils'; import { doesBrowserSupportWebUsbApi, whenPageMode } from '@app/common/utils'; import { openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { immediatelyAttemptLedgerConnection } from '../../hooks/use-when-reattempt-ledger-connection'; import { ConnectLedger } from './connect-ledger'; @@ -35,12 +36,12 @@ export function ConnectLedgerStart() { } return ( - navigate('../')}> + } onClose={() => navigate('../')}> connectChain('bitcoin')} connectStacks={() => connectChain('stacks')} showInstructions /> - + ); } diff --git a/src/app/features/ledger/generic-steps/connect-device/connect-ledger.tsx b/src/app/features/ledger/generic-steps/connect-device/connect-ledger.tsx index 3397c1dfd50..c137d18f1a4 100644 --- a/src/app/features/ledger/generic-steps/connect-device/connect-ledger.tsx +++ b/src/app/features/ledger/generic-steps/connect-device/connect-ledger.tsx @@ -57,9 +57,9 @@ export function ConnectLedger(props: ConnectLedgerProps) { return ( - + - {} + {} diff --git a/src/app/features/ledger/generic-steps/unsupported-browser/unsupported-browser.layout.tsx b/src/app/features/ledger/generic-steps/unsupported-browser/unsupported-browser.layout.tsx index 7e237f351a9..d41937fd3e6 100644 --- a/src/app/features/ledger/generic-steps/unsupported-browser/unsupported-browser.layout.tsx +++ b/src/app/features/ledger/generic-steps/unsupported-browser/unsupported-browser.layout.tsx @@ -2,8 +2,9 @@ import { useNavigate } from 'react-router-dom'; import { styled } from 'leather-styles/jsx'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; import { UnsupportedBrowserImg } from '@app/features/ledger/illustrations/ledger-illu-unsupported-browser'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { Link } from '@app/ui/components/link/link'; import { LedgerTitle } from '../../components/ledger-title'; @@ -13,16 +14,21 @@ export function UnsupportedBrowserLayout() { const navigate = useNavigate(); return ( - navigate(-1)}> + } isShowing onClose={() => navigate(-1)}> }> Your browser isn't supported - {' '} - To connect your Ledger with Leather try{' '} - Chrome or{' '} - Brave. + {'To connect your Ledger with Leather try '} + + Chrome + + {' or '} + + Brave + + . - + ); } diff --git a/src/app/features/message-signer/hash-drawer.tsx b/src/app/features/message-signer/hash-drawer.tsx index 42a3d09b096..8e1c439ed8f 100644 --- a/src/app/features/message-signer/hash-drawer.tsx +++ b/src/app/features/message-signer/hash-drawer.tsx @@ -16,6 +16,7 @@ function ShowHashButton(props: ShowHashButtonProps) { interface HashDrawerProps { hash: string; } + export function HashDrawer(props: HashDrawerProps) { const { hash } = props; const [showHash, setShowHash] = useState(false); @@ -35,7 +36,7 @@ export function HashDrawer(props: HashDrawerProps) { {showHash ? 'Hide hash' : 'Show hash'} - + @@ -48,7 +49,7 @@ export function HashDrawer(props: HashDrawerProps) { color="ink.text-subdued" lineHeight="1.6" wordBreak="break-all" - textStyle="caption.02" + textStyle="caption.01" > {displayHash} diff --git a/src/app/features/message-signer/message-preview-box.tsx b/src/app/features/message-signer/message-preview-box.tsx index 0b7a1e4a67c..32009cb5745 100644 --- a/src/app/features/message-signer/message-preview-box.tsx +++ b/src/app/features/message-signer/message-preview-box.tsx @@ -8,12 +8,7 @@ interface MessageBoxProps { } export function MessagePreviewBox({ message, hash }: MessageBoxProps) { return ( - + - {txId} + {txId} ) : null} diff --git a/src/app/features/psbt-signer/components/psbt-request-actions.tsx b/src/app/features/psbt-signer/components/psbt-request-actions.tsx index 2600573de04..56e6e3c2d97 100644 --- a/src/app/features/psbt-signer/components/psbt-request-actions.tsx +++ b/src/app/features/psbt-signer/components/psbt-request-actions.tsx @@ -1,6 +1,5 @@ -import { Box, HStack } from 'leather-styles/jsx'; - import { Button } from '@app/ui/components/button/button'; +import { Footer } from '@app/ui/components/containers/footers/footer'; interface PsbtRequestActionsProps { isLoading?: boolean; @@ -9,24 +8,13 @@ interface PsbtRequestActionsProps { } export function PsbtRequestActions({ isLoading, onCancel, onSignPsbt }: PsbtRequestActionsProps) { return ( - - - - - - +
+ + +
); } diff --git a/src/app/features/psbt-signer/components/psbt-request-details-section.layout.tsx b/src/app/features/psbt-signer/components/psbt-request-details-section.layout.tsx index 7bfb7a99d9b..d3674a54c53 100644 --- a/src/app/features/psbt-signer/components/psbt-request-details-section.layout.tsx +++ b/src/app/features/psbt-signer/components/psbt-request-details-section.layout.tsx @@ -4,7 +4,15 @@ import { HasChildren } from '@app/common/has-children'; export function PsbtRequestDetailsSectionLayout({ children, ...props }: HasChildren & StackProps) { return ( - + {children} ); diff --git a/src/app/features/psbt-signer/components/psbt-request-fee.tsx b/src/app/features/psbt-signer/components/psbt-request-fee.tsx index fc00033b5a6..ba81fc77ec2 100644 --- a/src/app/features/psbt-signer/components/psbt-request-fee.tsx +++ b/src/app/features/psbt-signer/components/psbt-request-fee.tsx @@ -17,7 +17,7 @@ export function PsbtRequestFee(props: { fee: Money }) { Transaction fee {formatMoney(fee)} - + {i18nFormatCurrency(calculateBitcoinFiatValue(fee))} diff --git a/src/app/features/psbt-signer/components/psbt-request-header.tsx b/src/app/features/psbt-signer/components/psbt-request-header.tsx index 29ee9c44328..97dc691edae 100644 --- a/src/app/features/psbt-signer/components/psbt-request-header.tsx +++ b/src/app/features/psbt-signer/components/psbt-request-header.tsx @@ -12,7 +12,7 @@ export function PsbtRequestHeader({ name, origin }: PsbtRequestHeaderProps) { const caption = displayName ? `Requested by ${displayName}` : null; return ( - + Approve
diff --git a/src/app/features/psbt-signer/components/psbt-signer.layout.tsx b/src/app/features/psbt-signer/components/psbt-signer.layout.tsx deleted file mode 100644 index 1016de2b254..00000000000 --- a/src/app/features/psbt-signer/components/psbt-signer.layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Stack } from 'leather-styles/jsx'; - -import { HasChildren } from '@app/common/has-children'; - -export function PsbtSignerLayout({ children }: HasChildren) { - return ( - - {children} - - ); -} diff --git a/src/app/features/psbt-signer/psbt-signer.tsx b/src/app/features/psbt-signer/psbt-signer.tsx index 85dc31a1fdb..1a719fa80be 100644 --- a/src/app/features/psbt-signer/psbt-signer.tsx +++ b/src/app/features/psbt-signer/psbt-signer.tsx @@ -1,27 +1,22 @@ import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; +import { PsbtSelectors } from '@tests/selectors/requests.selectors'; + import { getPsbtTxInputs, getPsbtTxOutputs } from '@shared/crypto/bitcoin/bitcoin.utils'; import { RouteUrls } from '@shared/route-urls'; import { closeWindow, isError } from '@shared/utils'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { SignPsbtArgs } from '@app/common/psbt/requests'; -import { PopupHeader } from '@app/features/current-account/popup-header'; import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentAccountTaprootIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; +import { Button } from '@app/ui/components/button/button'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Card } from '@app/ui/layout/card/card'; +import { CardContent } from '@app/ui/layout/card/card-content'; -import { PsbtInputsAndOutputs } from './components/psbt-inputs-and-outputs/psbt-inputs-and-outputs'; -import { PsbtInputsOutputsTotals } from './components/psbt-inputs-outputs-totals/psbt-inputs-outputs-totals'; -import { PsbtRequestActions } from './components/psbt-request-actions'; -import { PsbtRequestDetailsHeader } from './components/psbt-request-details-header'; -import { PsbtRequestDetailsLayout } from './components/psbt-request-details.layout'; -import { PsbtRequestFee } from './components/psbt-request-fee'; -import { PsbtRequestHeader } from './components/psbt-request-header'; -import { PsbtRequestRaw } from './components/psbt-request-raw'; -import { PsbtRequestSighashWarningLabel } from './components/psbt-request-sighash-warning-label'; -import { PsbtSignerLayout } from './components/psbt-signer.layout'; +import * as Psbt from './components'; import { useParsedPsbt } from './hooks/use-parsed-psbt'; import { usePsbtSigner } from './hooks/use-psbt-signer'; import { PsbtSignerContext, PsbtSignerProvider } from './psbt-signer.context'; @@ -42,8 +37,6 @@ export function PsbtSigner(props: PsbtSignerProps) { const { address: addressTaproot } = useCurrentAccountTaprootIndexZeroSigner(); const { getRawPsbt, getPsbtAsTransaction } = usePsbtSigner(); - useRouteHeader(); - useOnOriginTabClose(() => closeWindow()); const psbtRaw = useMemo(() => { @@ -91,28 +84,49 @@ export function PsbtSigner(props: PsbtSignerProps) { shouldDefaultToAdvancedView, }; - if (shouldDefaultToAdvancedView && psbtRaw) return ; + if (shouldDefaultToAdvancedView && psbtRaw) return ; return ( - - - - {isPsbtMutable ? : null} - - - - {psbtRaw ? : null} - - - - - onSignPsbt({ addressNativeSegwitTotal, addressTaprootTotal, fee, inputs: psbtTxInputs }) + + + + } - /> + > + + + + {isPsbtMutable ? : null} + + + + {psbtRaw ? : null} + + + + ); } diff --git a/src/app/features/retrieve-taproot-to-native-segwit/components/retrieve-taproot-to-native-segwit.layout.tsx b/src/app/features/retrieve-taproot-to-native-segwit/components/retrieve-taproot-to-native-segwit.layout.tsx index 8bae581829b..b7199b3b33d 100644 --- a/src/app/features/retrieve-taproot-to-native-segwit/components/retrieve-taproot-to-native-segwit.layout.tsx +++ b/src/app/features/retrieve-taproot-to-native-segwit/components/retrieve-taproot-to-native-segwit.layout.tsx @@ -1,9 +1,11 @@ import { Flex, styled } from 'leather-styles/jsx'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; import { Button } from '@app/ui/components/button/button'; import { Callout } from '@app/ui/components/callout/callout'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Header } from '@app/ui/components/containers/headers/header'; interface RetrieveTaprootToNativeSegwitLayoutProps { isBroadcasting: boolean; @@ -16,8 +18,19 @@ export function RetrieveTaprootToNativeSegwitLayout( ) { const { onClose, onApproveTransaction, isBroadcasting, children } = props; return ( - onClose()}> - + } + onClose={() => onClose()} + footer={ +
+ +
+ } + > + Retrieve Bitcoin deposited to
Taproot addresses @@ -34,20 +47,12 @@ export function RetrieveTaprootToNativeSegwitLayout( This transaction may take upwards of 30 minutes to confirm.
{children} - + We recommend you check the URL for each "Uninscribed UTXO" listed above to ensure it displays no inscription. If it does display one, do not proceed with retrieval or you may lose it! -
-
+ ); } diff --git a/src/app/features/secret-key-displayer/secret-key-displayer.tsx b/src/app/features/secret-key-displayer/secret-key-displayer.tsx index 45282345f16..302aedb2f1d 100644 --- a/src/app/features/secret-key-displayer/secret-key-displayer.tsx +++ b/src/app/features/secret-key-displayer/secret-key-displayer.tsx @@ -6,12 +6,12 @@ import { RouteUrls } from '@shared/route-urls'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; -import { SecretKeyDisplayerLayout } from './secret-key-displayer.layout'; +import { SecretKeyLayout } from '../../ui/components/secret-key/secret-key.layout'; -interface SecretKeyDisplayerProps { +interface SecretKeyProps { secretKey: string; } -export const SecretKeyDisplayer = memo(({ secretKey }: SecretKeyDisplayerProps) => { +export const SecretKey = memo(({ secretKey }: SecretKeyProps) => { const { onCopy, hasCopied } = useClipboard(secretKey || ''); const { pathname } = useLocation(); const analytics = useAnalytics(); @@ -27,7 +27,7 @@ export const SecretKeyDisplayer = memo(({ secretKey }: SecretKeyDisplayerProps) return ( <> - onClick?.(e)} - px="space.04" - py="space.04" - textStyle="label.02" - width="100%" - {...props} - > - {children} -
- ); -} diff --git a/src/app/features/settings-dropdown/components/settings-menu-wrapper.tsx b/src/app/features/settings-dropdown/components/settings-menu-wrapper.tsx deleted file mode 100644 index dfe90672396..00000000000 --- a/src/app/features/settings-dropdown/components/settings-menu-wrapper.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { forwardRef } from 'react'; - -import { Box, BoxProps } from 'leather-styles/jsx'; - -interface MenuWrapperProps extends BoxProps { - isShowing: boolean; -} -export const MenuWrapper = forwardRef( - ({ isShowing, ...props }: MenuWrapperProps, ref) => - isShowing ? ( - - ) : null -); diff --git a/src/app/features/settings-dropdown/settings-dropdown.tsx b/src/app/features/settings-dropdown/settings-dropdown.tsx deleted file mode 100644 index 173350a2d29..00000000000 --- a/src/app/features/settings-dropdown/settings-dropdown.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { useCallback, useRef } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; - -import { SettingsSelectors } from '@tests/selectors/settings.selectors'; -import { Box, Flex, HStack } from 'leather-styles/jsx'; - -import { RouteUrls } from '@shared/route-urls'; - -import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useDrawers } from '@app/common/hooks/use-drawers'; -import { useKeyActions } from '@app/common/hooks/use-key-actions'; -import { useModifierKey } from '@app/common/hooks/use-modifier-key'; -import { useOnClickOutside } from '@app/common/hooks/use-onclickoutside'; -import { useWalletType } from '@app/common/use-wallet-type'; -import { whenPageMode } from '@app/common/utils'; -import { openInNewTab, openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab'; -import { Divider } from '@app/components/layout/divider'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { useHasLedgerKeys, useLedgerDeviceTargetId } from '@app/store/ledger/ledger.selectors'; -import { useCurrentNetworkId } from '@app/store/networks/networks.selectors'; -import { Caption } from '@app/ui/components/typography/caption'; -import { ExternalLinkIcon } from '@app/ui/icons/external-link-icon'; - -import { openFeedbackDialog } from '../feedback-button/feedback-button'; -import { extractDeviceNameFromKnownTargetIds } from '../ledger/utils/generic-ledger-utils'; -import { AdvancedMenuItems } from './components/advanced-menu-items'; -import { LedgerDeviceItemRow } from './components/ledger-item-row'; -import { SettingsMenuItem as MenuItem } from './components/settings-menu-item'; -import { MenuWrapper } from './components/settings-menu-wrapper'; - -export function SettingsDropdown() { - const ref = useRef(null); - const hasGeneratedWallet = !!useCurrentStacksAccount(); - const { lockWallet } = useKeyActions(); - - const { isShowingSettings, setIsShowingSettings } = useDrawers(); - const currentNetworkId = useCurrentNetworkId(); - const navigate = useNavigate(); - const analytics = useAnalytics(); - const { walletType } = useWalletType(); - const targetId = useLedgerDeviceTargetId(); - - const { isPressed: showAdvancedMenuOptions } = useModifierKey('alt', 120); - const location = useLocation(); - - const handleClose = useCallback(() => setIsShowingSettings(false), [setIsShowingSettings]); - - const wrappedCloseCallback = useCallback( - (callback: () => void) => () => { - callback(); - handleClose(); - }, - [handleClose] - ); - - const isLedger = useHasLedgerKeys(); - - useOnClickOutside(ref, isShowingSettings ? handleClose : null, ['settings-menu-btn']); - - // RouteUrls.Activity is nested off / so we need to use a link relative to the route - const linkRelativeType = - location.pathname === `${RouteUrls.Home}${RouteUrls.Activity}` ? 'route' : 'path'; - - return ( - - {isLedger && targetId && ( - - )} - {hasGeneratedWallet && walletType === 'software' && ( - <> - { - navigate(RouteUrls.ViewSecretKey); - })} - > - View Secret Key - - - )} - { - void analytics.track('click_change_theme_menu_item'); - navigate(RouteUrls.ChangeTheme, { - relative: linkRelativeType, - state: { backgroundLocation: location }, - }); - })} - > - Change theme - - {whenPageMode({ - full: null, - popup: ( - { - void analytics.track('click_open_in_new_tab_menu_item'); - openIndexPageInNewTab(location.pathname); - }} - > - - Open in new tab - - - - ), - })} - { - openInNewTab('https://leather.gitbook.io/guides/installing/contact-support'); - })} - > - - Get support - - - - openFeedbackDialog())}>Give feedback - {hasGeneratedWallet ? : null} - { - void analytics.track('click_change_network_menu_item'); - navigate(RouteUrls.SelectNetwork, { - relative: linkRelativeType, - state: { backgroundLocation: location }, - }); - })} - > - - Change network - {currentNetworkId} - - - - - - {showAdvancedMenuOptions && ( - - )} - {hasGeneratedWallet && walletType === 'software' && ( - { - void analytics.track('lock_session'); - void lockWallet(); - navigate(RouteUrls.Unlock); - })} - data-testid="settings-lock" - > - Lock - - )} - - navigate(RouteUrls.SignOutConfirm, { - relative: linkRelativeType, - state: { backgroundLocation: location }, - }) - )} - data-testid={SettingsSelectors.SignOutListItem} - > - Sign out - - - ); -} diff --git a/src/app/features/settings-dropdown/components/advanced-menu-items.tsx b/src/app/features/settings/components/advanced-menu-items.tsx similarity index 77% rename from src/app/features/settings-dropdown/components/advanced-menu-items.tsx rename to src/app/features/settings/components/advanced-menu-items.tsx index 1dfcf684f02..ecc60793435 100644 --- a/src/app/features/settings-dropdown/components/advanced-menu-items.tsx +++ b/src/app/features/settings/components/advanced-menu-items.tsx @@ -10,10 +10,9 @@ import { isNumber } from '@shared/utils'; import { Divider } from '@app/components/layout/divider'; import { useToast } from '@app/features/toasts/use-toast'; +import { DropdownMenu } from '@app/ui/components/dropdown-menu/dropdown-menu'; import { Caption } from '@app/ui/components/typography/caption'; -import { SettingsMenuItem as MenuItem } from './settings-menu-item'; - const isAnEmptyLogsArrayByteThreshold = 7; function isSmallEnoughToBeConsiderdEmptyCache(logSizeInBytes?: number) { @@ -21,12 +20,8 @@ function isSmallEnoughToBeConsiderdEmptyCache(logSizeInBytes?: number) { return logSizeInBytes < isAnEmptyLogsArrayByteThreshold; } -interface AdvancedMenuItemsProps { - closeHandler(fn: () => void): () => void; - settingsShown: boolean; -} -export function AdvancedMenuItems({ closeHandler, settingsShown }: AdvancedMenuItemsProps) { - const { result: logSizeInBytes } = useAsync(async () => getLogSizeInBytes(), [settingsShown]); +export function AdvancedMenuItems() { + const { result: logSizeInBytes } = useAsync(async () => getLogSizeInBytes(), []); const toast = useToast(); const diagnosticLogText = useMemo(() => { @@ -40,28 +35,28 @@ export function AdvancedMenuItems({ closeHandler, settingsShown }: AdvancedMenuI return ( <> - { + { await copyLogsToClipboard(); toast.success('Copied to clipboard'); - })} + }} > Copy diagnostics to clipboard Contains private wallet usage activity - - { + + { await clearBrowserStorageLogs(); toast.success('Diagnostic logs cleared'); - })} + }} > Clear diagnostic information {diagnosticLogText} - + ); diff --git a/src/app/features/settings-dropdown/components/ledger-item-row.tsx b/src/app/features/settings/components/ledger-item-row.tsx similarity index 100% rename from src/app/features/settings-dropdown/components/ledger-item-row.tsx rename to src/app/features/settings/components/ledger-item-row.tsx diff --git a/src/app/pages/select-network/components/network-list-item.layout.tsx b/src/app/features/settings/network/components/network-list-item.layout.tsx similarity index 79% rename from src/app/pages/select-network/components/network-list-item.layout.tsx rename to src/app/features/settings/network/components/network-list-item.layout.tsx index 07602bcc7f3..20e6c9efad8 100644 --- a/src/app/pages/select-network/components/network-list-item.layout.tsx +++ b/src/app/features/settings/network/components/network-list-item.layout.tsx @@ -1,3 +1,4 @@ +import { NetworkSelectors } from '@tests/selectors/network.selectors'; import { SettingsSelectors } from '@tests/selectors/settings.selectors'; import { Flex, Stack, styled } from 'leather-styles/jsx'; @@ -5,9 +6,7 @@ import { NetworkConfiguration } from '@shared/constants'; import { getUrlHostname } from '@app/common/utils'; import { Button } from '@app/ui/components/button/button'; -import { TrashIcon } from '@app/ui/icons/trash-icon'; - -import { NetworkStatusIndicator } from './network-status-indicator'; +import { CheckmarkIcon, CloudOffIcon, TrashIcon } from '@app/ui/icons'; interface NetworkListItemLayoutProps { networkId: string; @@ -34,6 +33,13 @@ export function NetworkListItemLayout({ width="100%" variant="ghost" key={networkId} + _hover={ + unselectable + ? undefined + : { + bg: 'ink.component-background-hover', + } + } px="space.05" py="space.04" onClick={unselectable ? undefined : onSelectNetwork} @@ -51,7 +57,11 @@ export function NetworkListItemLayout({ {getUrlHostname(network.chain.stacks.url)} - + {!isOnline ? ( + + ) : isActive ? ( + + ) : null} {isCustom && ( + + } + > + {Object.keys(networks).map(id => ( + { + selectNetwork(id); + onClose(); + }} + isCustom={!defaultNetworkIds.includes(id)} + onRemoveNetwork={id => { + if (id === currentNetwork.id) networksActions.changeNetwork('mainnet'); + removeNetwork(id); + }} + /> + ))} + + ); +} diff --git a/src/app/features/settings/settings.tsx b/src/app/features/settings/settings.tsx new file mode 100644 index 00000000000..b1d51062872 --- /dev/null +++ b/src/app/features/settings/settings.tsx @@ -0,0 +1,214 @@ +import { useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +import { SettingsSelectors } from '@tests/selectors/settings.selectors'; +import { css } from 'leather-styles/css'; +import { Flex, Stack, styled } from 'leather-styles/jsx'; + +import { RouteUrls } from '@shared/route-urls'; + +import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; +import { useKeyActions } from '@app/common/hooks/use-key-actions'; +import { useModifierKey } from '@app/common/hooks/use-modifier-key'; +import { useWalletType } from '@app/common/use-wallet-type'; +import { openInNewTab, openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab'; +import { AppVersion } from '@app/components/app-version'; +import { Divider } from '@app/components/layout/divider'; +import { NetworkDialog } from '@app/features/settings/network/network'; +import { SignOut } from '@app/features/settings/sign-out/sign-out-confirm'; +import { ThemeDialog } from '@app/features/settings/theme/theme-dialog'; +import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { useHasLedgerKeys, useLedgerDeviceTargetId } from '@app/store/ledger/ledger.selectors'; +import { useCurrentNetworkId } from '@app/store/networks/networks.selectors'; +import { DropdownMenu } from '@app/ui/components/dropdown-menu/dropdown-menu'; +import { Flag } from '@app/ui/components/flag/flag'; +import { Caption } from '@app/ui/components/typography/caption'; +import { + ExitIcon, + ExpandIcon, + ExternalLinkIcon, + KeyIcon, + LockIcon, + MegaphoneIcon, + SunInCloudIcon, + SupportIcon, + SwapIcon, + WorldIcon, +} from '@app/ui/icons/'; + +import { openFeedbackDialog } from '../feedback-button/feedback-button'; +import { extractDeviceNameFromKnownTargetIds } from '../ledger/utils/generic-ledger-utils'; +import { AdvancedMenuItems } from './components/advanced-menu-items'; +import { LedgerDeviceItemRow } from './components/ledger-item-row'; + +interface SettingsProps { + triggerButton: React.ReactNode; + toggleSwitchAccount(): void; +} +export function Settings({ triggerButton, toggleSwitchAccount }: SettingsProps) { + const [showSignOut, setShowSignOut] = useState(false); + const [showChangeTheme, setShowChangeTheme] = useState(false); + const [showChangeNetwork, setShowChangeNetwork] = useState(false); + const hasGeneratedWallet = !!useCurrentStacksAccount(); + const { lockWallet } = useKeyActions(); + + const currentNetworkId = useCurrentNetworkId(); + const navigate = useNavigate(); + const analytics = useAnalytics(); + const { walletType } = useWalletType(); + const targetId = useLedgerDeviceTargetId(); + + const location = useLocation(); + + const isLedger = useHasLedgerKeys(); + const { isPressed: showAdvancedMenuOptions } = useModifierKey('alt', 120); + + return ( + <> + + {triggerButton} + + + + {isLedger && targetId && ( + + + + )} + {hasGeneratedWallet && ( + + } textStyle="label.02"> + Switch account + + + )} + {hasGeneratedWallet && walletType === 'software' && ( + navigate(RouteUrls.ViewSecretKey)} + > + } textStyle="label.02"> + View Secret Key + + + )} + + { + void analytics.track('click_open_in_new_tab_menu_item'); + openIndexPageInNewTab(location.pathname); + }} + > + } textStyle="label.02"> + Maximize + + + + + { + void analytics.track('click_change_network_menu_item'); + setShowChangeNetwork(!showChangeNetwork); + }} + > + }> + + Change network + + {currentNetworkId} + + + + + + { + void analytics.track('click_change_theme_menu_item'); + setShowChangeTheme(!showChangeTheme); + }} + > + }> + + Change theme + + + + + + + { + openInNewTab('https://leather.gitbook.io/guides/installing/contact-support'); + }} + > + } textStyle="label.02"> + + Get support + + + + + openFeedbackDialog()}> + } textStyle="label.02"> + Give feedback + + + + + + + {showAdvancedMenuOptions && } + {hasGeneratedWallet && walletType === 'software' && ( + { + void analytics.track('lock_session'); + void lockWallet(); + navigate(RouteUrls.Unlock); + }} + data-testid={SettingsSelectors.LockListItem} + > + } textStyle="label.02"> + Lock + + + )} + + setShowSignOut(!showSignOut)} + data-testid={SettingsSelectors.SignOutListItem} + > + } textStyle="label.02"> + Sign out + + + + + + + + setShowSignOut(!showSignOut)} /> + setShowChangeTheme(!showChangeTheme)} + /> + setShowChangeNetwork(!showChangeNetwork)} + /> + + ); +} diff --git a/src/app/features/settings/sign-out/sign-out-confirm.tsx b/src/app/features/settings/sign-out/sign-out-confirm.tsx new file mode 100644 index 00000000000..f4e15f1308a --- /dev/null +++ b/src/app/features/settings/sign-out/sign-out-confirm.tsx @@ -0,0 +1,36 @@ +import { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { RouteUrls } from '@shared/route-urls'; + +import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; +import { useKeyActions } from '@app/common/hooks/use-key-actions'; + +import { SignOutDialog } from './sign-out'; + +interface SignOutProps { + isShowing: boolean; + onClose(): void; +} + +export function SignOut({ isShowing = false, onClose }: SignOutProps) { + const analytics = useAnalytics(); + + useEffect(() => void analytics.track('sign-out'), [analytics]); + const { signOut } = useKeyActions(); + const navigate = useNavigate(); + // #4370 SMELL without this early return the wallet crashes on new install with + // : Wallet is neither of type `ledger` nor `software` + if (!isShowing) return null; + return ( + { + void signOut().finally(() => { + navigate(RouteUrls.Onboarding); + }); + }} + onClose={onClose} + /> + ); +} diff --git a/src/app/features/settings/sign-out/sign-out.tsx b/src/app/features/settings/sign-out/sign-out.tsx new file mode 100644 index 00000000000..a2112056695 --- /dev/null +++ b/src/app/features/settings/sign-out/sign-out.tsx @@ -0,0 +1,105 @@ +import { SettingsSelectors } from '@tests/selectors/settings.selectors'; +import { useFormik } from 'formik'; +import { Flex, HStack, styled } from 'leather-styles/jsx'; + +import { useWalletType } from '@app/common/use-wallet-type'; +import { Button } from '@app/ui/components/button/button'; +import { Callout } from '@app/ui/components/callout/callout'; +import { Dialog, DialogProps } from '@app/ui/components/containers/dialog/dialog'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Header } from '@app/ui/components/containers/headers/header'; + +interface SignOutDialogProps extends DialogProps { + onUserDeleteWallet(): void; +} +export function SignOutDialog({ isShowing, onUserDeleteWallet, onClose }: SignOutDialogProps) { + const { whenWallet, walletType } = useWalletType(); + const form = useFormik({ + initialValues: { + confirmBackup: whenWallet({ ledger: true, software: false }), + confirmPasswordDisable: whenWallet({ ledger: true, software: false }), + }, + onSubmit() { + onUserDeleteWallet(); + }, + }); + + const canSignOut = form.values.confirmBackup && form.values.confirmPasswordDisable; + + return ( + } + isShowing={isShowing} + onClose={onClose} + footer={ +
+ + +
+ } + > + + {whenWallet({ + software: + "Back up your Secret Key before signing out. You'll be asked for your Secret Key on your next login.", + ledger: + "When you sign out, you'll need to reconnect your Ledger to sign back into your wallet.", + })} + + +
+ + + + + + I have backed up my Secret Key. + + + + + + + + I understand that my password will not give me access to my wallet after I sign out. + + + +
+
+
+ ); +} diff --git a/src/app/features/theme-drawer/theme-list.tsx b/src/app/features/settings/theme/theme-dialog.tsx similarity index 71% rename from src/app/features/theme-drawer/theme-list.tsx rename to src/app/features/settings/theme/theme-dialog.tsx index 4cdd87616e7..ac2217059ab 100644 --- a/src/app/features/theme-drawer/theme-list.tsx +++ b/src/app/features/settings/theme/theme-dialog.tsx @@ -1,13 +1,13 @@ import { useCallback } from 'react'; -import { Flex, FlexProps } from 'leather-styles/jsx'; - import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { UserSelectedTheme, themeLabelMap, useThemeSwitcher } from '@app/common/theme-provider'; +import { Dialog, DialogProps } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { ThemeListItem } from './theme-list-item'; -export function ThemeList(props: FlexProps) { +export function ThemeDialog({ isShowing, onClose }: DialogProps) { const themes = Object.keys(themeLabelMap) as UserSelectedTheme[]; const analytics = useAnalytics(); const { setUserSelectedTheme } = useThemeSwitcher(); @@ -25,7 +25,11 @@ export function ThemeList(props: FlexProps) { const { userSelectedTheme } = useThemeSwitcher(); return ( - + } + isShowing={isShowing} + onClose={onClose} + > {themes.map(theme => ( ))} - + ); } diff --git a/src/app/features/theme-drawer/theme-list-item-layout.tsx b/src/app/features/settings/theme/theme-list-item.tsx similarity index 54% rename from src/app/features/theme-drawer/theme-list-item-layout.tsx rename to src/app/features/settings/theme/theme-list-item.tsx index 716e05ea1ee..33c72298fd9 100644 --- a/src/app/features/theme-drawer/theme-list-item-layout.tsx +++ b/src/app/features/settings/theme/theme-list-item.tsx @@ -1,17 +1,21 @@ +import { useCallback } from 'react'; + import { Box, Flex, styled } from 'leather-styles/jsx'; -import { CheckmarkIcon } from '@app/ui/icons/checkmark-icon'; +import { UserSelectedTheme, getThemeLabel } from '@app/common/theme-provider'; +import { CheckmarkIcon } from '@app/ui/icons'; interface ThemeListItemProps { - themeLabel: string; + theme: UserSelectedTheme; + onThemeSelected(theme: UserSelectedTheme): void; isActive: boolean; - onThemeItemSelect(): void; } -export function ThemeListItemLayout({ - themeLabel, - isActive, - onThemeItemSelect, -}: ThemeListItemProps) { +export function ThemeListItem({ theme, onThemeSelected, isActive }: ThemeListItemProps) { + const themeLabel = getThemeLabel(theme); + const onThemeItemSelect = useCallback(() => { + onThemeSelected(theme); + }, [onThemeSelected, theme]); + return ( - + {domainName} - + {domainVersion} {domainChainName} diff --git a/src/app/features/stacks-message-signer/stacks-message-signing.tsx b/src/app/features/stacks-message-signer/stacks-message-signing.tsx index a4193804182..d4baf58a350 100644 --- a/src/app/features/stacks-message-signer/stacks-message-signing.tsx +++ b/src/app/features/stacks-message-signer/stacks-message-signing.tsx @@ -13,8 +13,6 @@ import { } from '@shared/signature/signature-types'; import { closeWindow } from '@shared/utils'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { PopupHeader } from '@app/features/current-account/popup-header'; import { MessageSigningHeader } from '@app/features/message-signer/message-signing-header'; import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; @@ -56,7 +54,6 @@ export function StacksMessageSigning({ onCancelMessageSigning, payload, }: StacksMessageSigningProps) { - useRouteHeader(); useOnOriginTabClose(() => closeWindow()); if (!tabId) return null; diff --git a/src/app/features/stacks-transaction-request/contract-call-details/contract-call-details.tsx b/src/app/features/stacks-transaction-request/contract-call-details/contract-call-details.tsx index cd36cc7e396..a2e6d6ed277 100644 --- a/src/app/features/stacks-transaction-request/contract-call-details/contract-call-details.tsx +++ b/src/app/features/stacks-transaction-request/contract-call-details/contract-call-details.tsx @@ -27,6 +27,7 @@ function ContractCallDetailsSuspense() { py="32px" gap="space.05" width="100%" + background="ink.background-primary" > Function and arguments diff --git a/src/app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary.ts b/src/app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary.ts index bdcab79db67..309f3b9acca 100644 --- a/src/app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary.ts +++ b/src/app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary.ts @@ -34,7 +34,6 @@ export function useStacksTransactionSummary(token: CryptoCurrencies) { function formSentSummaryTxState(txId: string, signedTx: StacksTransaction, decimals?: number) { return { state: { - hasHeaderTitle: true, txLink: { blockchain: 'stacks', txid: txId || '', diff --git a/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx b/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx index 96fdb31a3f1..f3383c43070 100644 --- a/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx +++ b/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import { StacksTransaction } from '@stacks/transactions'; @@ -12,14 +13,12 @@ import { RouteUrls } from '@shared/route-urls'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useOnMount } from '@app/common/hooks/use-on-mount'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { stxToMicroStx } from '@app/common/money/unit-conversion'; import { stxFeeValidator } from '@app/common/validation/forms/fee-validators'; import { nonceValidator } from '@app/common/validation/nonce-validators'; import { NonceSetter } from '@app/components/nonce-setter'; -import { PopupHeader } from '@app/features/current-account/popup-header'; +import { HighFeeDialog } from '@app/features/dialogs/high-fee-dialog/high-fee-dialog'; import { RequestingTabClosedWarningMessage } from '@app/features/errors/requesting-tab-closed-error-msg'; -import { HighFeeDrawer } from '@app/features/high-fee-drawer/high-fee-drawer'; import { ContractCallDetails } from '@app/features/stacks-transaction-request/contract-call-details/contract-call-details'; import { ContractDeployDetails } from '@app/features/stacks-transaction-request/contract-deploy-details/contract-deploy-details'; import { PageTop } from '@app/features/stacks-transaction-request/page-top'; @@ -52,6 +51,7 @@ export function StacksTransactionSigner({ onSignStacksTransaction, isMultisig, }: StacksTransactionSignerProps) { + const [isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation] = useState(false); const transactionRequest = useTransactionRequestState(); const { data: stxFees } = useCalculateStacksTxFees(stacksTransaction); const analytics = useAnalytics(); @@ -60,8 +60,6 @@ export function StacksTransactionSigner({ const { data: nextNonce } = useNextNonce(); const { search } = useLocation(); - useRouteHeader(); - useOnMount(() => { void analytics.track('view_transaction_signing'), [analytics]; }); @@ -133,8 +131,13 @@ export function StacksTransactionSigner({ )} - - + setIsShowingHighFeeConfirmation(true)} + /> + )} diff --git a/src/app/features/stacks-transaction-request/submit-action.tsx b/src/app/features/stacks-transaction-request/submit-action.tsx index 85c7b757881..45fc22d70e3 100644 --- a/src/app/features/stacks-transaction-request/submit-action.tsx +++ b/src/app/features/stacks-transaction-request/submit-action.tsx @@ -5,14 +5,16 @@ import { HIGH_FEE_AMOUNT_STX } from '@shared/constants'; import { StacksTransactionFormValues } from '@shared/models/form.model'; import { isEmpty } from '@shared/utils'; -import { useDrawers } from '@app/common/hooks/use-drawers'; import { useTransactionError } from '@app/features/stacks-transaction-request/hooks/use-transaction-error'; import { Button } from '@app/ui/components/button/button'; -export function SubmitAction() { +interface SubmitActionProps { + setIsShowingHighFeeConfirmation(): void; +} +export function SubmitAction({ setIsShowingHighFeeConfirmation }: SubmitActionProps) { const { handleSubmit, values, validateForm, isSubmitting } = useFormikContext(); - const { isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation } = useDrawers(); + const error = useTransactionError(); const isDisabled = !!error || Number(values.fee) < 0; @@ -21,7 +23,7 @@ export function SubmitAction() { // Check for errors before showing the high fee confirmation const formErrors = await validateForm(); if (isEmpty(formErrors) && Number(values.fee) > HIGH_FEE_AMOUNT_STX) { - return setIsShowingHighFeeConfirmation(!isShowingHighFeeConfirmation); + return setIsShowingHighFeeConfirmation(); } handleSubmit(); }; diff --git a/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx b/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx index 2c2d0c0b984..f88783c820f 100644 --- a/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx +++ b/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react'; +import { memo, useState } from 'react'; import { Navigate } from 'react-router-dom'; import { STXTransferPayload, TransactionTypes } from '@stacks/connect'; @@ -8,9 +8,9 @@ import { RouteUrls } from '@shared/route-urls'; import { closeWindow } from '@shared/utils'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useDrawers } from '@app/common/hooks/use-drawers'; import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; import { stacksValue } from '@app/common/stacks-utils'; +import { SwitchAccountDialog } from '@app/features/dialogs/switch-account-dialog/switch-account-dialog'; import { ErrorMessage } from '@app/features/stacks-transaction-request/transaction-error/error-message'; import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; @@ -24,7 +24,7 @@ interface InsufficientFundsActionButtonsProps { } function InsufficientFundsActionButtons({ eventName }: InsufficientFundsActionButtonsProps) { const analytics = useAnalytics(); - const { setIsShowingSwitchAccountsState } = useDrawers(); + const [isShowingSwitchAccount, setIsShowingSwitchAccount] = useState(false); const onGetStx = () => { void analytics.track(eventName); @@ -34,8 +34,12 @@ function InsufficientFundsActionButtons({ eventName }: InsufficientFundsActionBu return ( <> + setIsShowingSwitchAccount(false)} + /> - diff --git a/src/app/features/switch-account-drawer/components/create-account-action.tsx b/src/app/features/switch-account-drawer/components/create-account-action.tsx deleted file mode 100644 index c3de671b39b..00000000000 --- a/src/app/features/switch-account-drawer/components/create-account-action.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Flex } from 'leather-styles/jsx'; - -import { Button } from '@app/ui/components/button/button'; - -interface CreateAccountActionProps { - onCreateAccount(): void; -} -export function CreateAccountAction({ onCreateAccount }: CreateAccountActionProps) { - return ( - - - - ); -} diff --git a/src/app/features/switch-account-drawer/components/switch-account-list.tsx b/src/app/features/switch-account-drawer/components/switch-account-list.tsx deleted file mode 100644 index 3b51ffbbda5..00000000000 --- a/src/app/features/switch-account-drawer/components/switch-account-list.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { memo } from 'react'; -import { Virtuoso } from 'react-virtuoso'; - -import { Box } from 'leather-styles/jsx'; - -import { useWalletType } from '@app/common/use-wallet-type'; - -import { SwitchAccountListItem } from './switch-account-list-item'; - -interface SwitchAccountListProps { - handleClose(): void; - currentAccountIndex: number; - addressesNum: number; -} -export const SwitchAccountList = memo( - ({ currentAccountIndex, handleClose, addressesNum }: SwitchAccountListProps) => { - const { whenWallet } = useWalletType(); - - return ( - ( - - - - )} - /> - ); - } -); diff --git a/src/app/features/switch-account-drawer/switch-account-drawer.tsx b/src/app/features/switch-account-drawer/switch-account-drawer.tsx deleted file mode 100644 index 765fdef4070..00000000000 --- a/src/app/features/switch-account-drawer/switch-account-drawer.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { memo } from 'react'; - -import { Box } from 'leather-styles/jsx'; - -import { useCreateAccount } from '@app/common/hooks/account/use-create-account'; -import { useWalletType } from '@app/common/use-wallet-type'; -import { ControlledDrawer } from '@app/components/drawer/controlled-drawer'; -import { useCurrentAccountIndex } from '@app/store/accounts/account'; -import { useFilteredBitcoinAccounts } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; -import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { useShowSwitchAccountsState } from '@app/store/ui/ui.hooks'; - -import { AccountListUnavailable } from './components/account-list-unavailable'; -import { CreateAccountAction } from './components/create-account-action'; -import { SwitchAccountList } from './components/switch-account-list'; - -export const SwitchAccountDrawer = memo(() => { - const [isShowing, setShowSwitchAccountsState] = useShowSwitchAccountsState(); - - const currentAccountIndex = useCurrentAccountIndex(); - const createAccount = useCreateAccount(); - const { whenWallet } = useWalletType(); - - const stacksAccounts = useStacksAccounts(); - const bitcoinAccounts = useFilteredBitcoinAccounts(); - const btcAddressesNum = bitcoinAccounts.length / 2; - const stacksAddressesNum = stacksAccounts.length; - - const onClose = () => setShowSwitchAccountsState(false); - - const onCreateAccount = () => { - createAccount(); - setShowSwitchAccountsState(false); - }; - - if (isShowing && stacksAddressesNum === 0 && btcAddressesNum === 0) { - return ; - } - - return isShowing ? ( - - - - {whenWallet({ - software: , - ledger: <>, - })} - - - ) : null; -}); diff --git a/src/app/features/theme-drawer/theme-drawer.tsx b/src/app/features/theme-drawer/theme-drawer.tsx deleted file mode 100644 index 52c68d3627f..00000000000 --- a/src/app/features/theme-drawer/theme-drawer.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useNavigate } from 'react-router-dom'; - -import { useLocationState } from '@app/common/hooks/use-location-state'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; -import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; - -import { ThemeList } from './theme-list'; - -export function ThemesDrawer() { - useBackgroundLocationRedirect(); - const navigate = useNavigate(); - const backgroundLocation = useLocationState('backgroundLocation'); - return ( - navigate(backgroundLocation ?? '..')}> - - - ); -} diff --git a/src/app/features/theme-drawer/theme-list-item.tsx b/src/app/features/theme-drawer/theme-list-item.tsx deleted file mode 100644 index 6437bda0a9e..00000000000 --- a/src/app/features/theme-drawer/theme-list-item.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useCallback } from 'react'; - -import { UserSelectedTheme, getThemeLabel } from '@app/common/theme-provider'; - -import { ThemeListItemLayout } from './theme-list-item-layout'; - -interface ThemeListItemProps { - theme: UserSelectedTheme; - onThemeSelected(theme: UserSelectedTheme): void; - isActive: boolean; -} -export function ThemeListItem({ theme, onThemeSelected, isActive }: ThemeListItemProps) { - const themeLabel = getThemeLabel(theme); - const itemSelectHandler = useCallback(() => { - onThemeSelected(theme); - }, [onThemeSelected, theme]); - - return ( - - ); -} diff --git a/src/app/pages/bitcoin-contract-list/bitcoin-contract-list.tsx b/src/app/pages/bitcoin-contract-list/bitcoin-contract-list.tsx index ba8eb71cd97..de64497e615 100644 --- a/src/app/pages/bitcoin-contract-list/bitcoin-contract-list.tsx +++ b/src/app/pages/bitcoin-contract-list/bitcoin-contract-list.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { Flex, styled } from 'leather-styles/jsx'; +import { Flex, Stack, styled } from 'leather-styles/jsx'; import { BitcoinContractListItem, @@ -11,7 +11,6 @@ import { FullPageLoadingSpinner } from '@app/components/loading-spinner'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; import { BitcoinContractListItemLayout } from './components/bitcoin-contract-list-item-layout'; -import { BitcoinContractListLayout } from './components/bitcoin-contract-list-layout'; export function BitcoinContractList() { const { getAllActiveBitcoinContracts } = useBitcoinContracts(); @@ -36,7 +35,7 @@ export function BitcoinContractList() { if (isLoading) return ; return ( - + {bitcoinContracts.length === 0 || isError ? ( @@ -58,6 +57,6 @@ export function BitcoinContractList() { ); }) )} - + ); } diff --git a/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx b/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx index 12fb05fa080..3d5d9161ed9 100644 --- a/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx +++ b/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx @@ -38,7 +38,7 @@ export function BitcoinContractListItemLayout({ return ( handleOpenTxLink({ txid, diff --git a/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-layout.tsx b/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-layout.tsx deleted file mode 100644 index 3074f22f036..00000000000 --- a/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-layout.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { ReactNode } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { Stack } from 'leather-styles/jsx'; - -import { RouteUrls } from '@shared/route-urls'; - -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { Header } from '@app/components/header'; - -interface BitcoinContractListProps { - children: ReactNode; -} -export function BitcoinContractListLayout({ children }: BitcoinContractListProps) { - const navigate = useNavigate(); - useRouteHeader(
navigate(RouteUrls.Home)} />); - return ( - - {children} - - ); -} diff --git a/src/app/pages/bitcoin-contract-request/bitcoin-contract-request.tsx b/src/app/pages/bitcoin-contract-request/bitcoin-contract-request.tsx index 46150148e84..19d7ddcdc02 100644 --- a/src/app/pages/bitcoin-contract-request/bitcoin-contract-request.tsx +++ b/src/app/pages/bitcoin-contract-request/bitcoin-contract-request.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { DlcSelectors } from '@tests/selectors/requests.selectors'; import { Stack } from 'leather-styles/jsx'; import { RouteUrls } from '@shared/route-urls'; @@ -13,11 +14,13 @@ import { import { useOnMount } from '@app/common/hooks/use-on-mount'; import { initialSearchParams } from '@app/common/initial-search-params'; import { useCurrentAccountNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Card } from '@app/ui/layout/card/card'; +import { CardContent } from '@app/ui/layout/card/card-content'; import { BitcoinContractOfferDetailsSimple } from './components/bitcoin-contract-offer/bitcoin-contract-offer-details'; import { BitcoinContractRequestActions } from './components/bitcoin-contract-request-actions'; import { BitcoinContractRequestHeader } from './components/bitcoin-contract-request-header'; -import { BitcoinContractRequestLayout } from './components/bitcoin-contract-request-layout'; import { BitcoinContractRequestWarningLabel } from './components/bitcoin-contract-request-warning-label'; export function BitcoinContractRequest() { @@ -102,35 +105,51 @@ export function BitcoinContractRequest() { return ( <> + {/* TODO - need to test this properly.. Tried using steps from test.describe('Bitcoin Contract Request Test' */} {!isLoading && bitcoinAddress && bitcoinContractOfferDetails && ( - - - - + + + } + > + + - - - - + + + + {/* TODO test this as it was placed after action buttons */} + + + )} ); diff --git a/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-actions.tsx b/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-actions.tsx index 10a34eafb81..9e64a0a5a90 100644 --- a/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-actions.tsx +++ b/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-actions.tsx @@ -1,5 +1,4 @@ import { BitcoinContractRequestSelectors } from '@tests/selectors/bitcoin-contract-request.selectors'; -import { Box, HStack } from 'leather-styles/jsx'; import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balance'; import { Button } from '@app/ui/components/button/button'; @@ -22,35 +21,24 @@ export function BitcoinContractRequestActions({ const canAccept = btcAvailableAssetBalance.balance.amount.isGreaterThan(requiredAmount); return ( - - - - - - + <> + + + ); } diff --git a/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-layout.tsx b/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-layout.tsx deleted file mode 100644 index 171c52142ab..00000000000 --- a/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-layout.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Stack } from 'leather-styles/jsx'; - -interface BitcoinContractRequestLayoutProps { - children: React.ReactNode; -} -export function BitcoinContractRequestLayout({ children }: BitcoinContractRequestLayoutProps) { - return ( - - {children} - - ); -} diff --git a/src/app/pages/choose-account/choose-account.tsx b/src/app/pages/choose-account/choose-account.tsx index 90f75fe195c..41fa8bfe5ca 100644 --- a/src/app/pages/choose-account/choose-account.tsx +++ b/src/app/pages/choose-account/choose-account.tsx @@ -7,7 +7,6 @@ import { closeWindow } from '@shared/utils'; import { useCancelAuthRequest } from '@app/common/authentication/use-cancel-auth-request'; import { useAppDetails } from '@app/common/hooks/auth/use-app-details'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { RequesterFlag } from '@app/components/requester-flag'; import { ChooseAccountsList } from '@app/pages/choose-account/components/accounts'; import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; @@ -18,7 +17,6 @@ export const ChooseAccount = memo(() => { const cancelAuthentication = useCancelAuthRequest(); - useRouteHeader(<>); useOnOriginTabClose(() => closeWindow()); const handleUnmount = async () => cancelAuthentication(); @@ -31,7 +29,7 @@ export const ChooseAccount = memo(() => { return ( <> - + {url && } diff --git a/src/app/pages/choose-account/components/accounts.tsx b/src/app/pages/choose-account/components/accounts.tsx index 70817f1fc87..b852b778ac2 100644 --- a/src/app/pages/choose-account/components/accounts.tsx +++ b/src/app/pages/choose-account/components/accounts.tsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { Virtuoso } from 'react-virtuoso'; import { Box, FlexProps, HStack, styled } from 'leather-styles/jsx'; +import { token } from 'leather-styles/tokens'; import { RouteUrls } from '@shared/route-urls'; @@ -13,13 +14,14 @@ import { useWalletType } from '@app/common/use-wallet-type'; import { slugify } from '@app/common/utils'; import { AccountTotalBalance } from '@app/components/account-total-balance'; import { AcccountAddresses } from '@app/components/account/account-addresses'; -import { AccountAvatar } from '@app/components/account/account-avatar'; import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; import { usePressable } from '@app/components/item-hover'; import { useNativeSegwitAccountIndexAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; +import { AccountAvatar } from '@app/ui/components/account/account-avatar/account-avatar'; import { PlusIcon } from '@app/ui/icons/plus-icon'; +import { virtuosoHeight, virtuosoStyles } from '@app/ui/shared/virtuoso'; interface AccountTitlePlaceholderProps { account: StacksAccount; @@ -43,33 +45,28 @@ const ChooseAccountItem = memo( const accountSlug = useMemo(() => slugify(`Account ${account?.index + 1}`), [account?.index]); return ( - // Padding required on outer element to prevent jumpy virtualized list - - } - accountName={ - }> - {name} - - } - avatar={ - - } - balanceLabel={ - - } - data-testid={`account-${accountSlug}-${account.index}`} - index={account.index} - isLoading={isLoading} - isSelected={false} - onSelectAccount={() => onSelectAccount(account.index)} - /> - + } + accountName={ + }> + {name} + + } + avatar={ + + } + balanceLabel={} + data-testid={`account-${accountSlug}-${account.index}`} + index={account.index} + isLoading={isLoading} + isSelected={false} + onSelectAccount={() => onSelectAccount(account.index)} + /> ); } ); @@ -83,7 +80,7 @@ const AddAccountAction = memo(() => { }; return ( - + Generate new account @@ -113,19 +110,27 @@ export const ChooseAccountsList = memo(() => { }; if (!accounts) return null; + const accountNum = accounts.length; return ( - + {whenWallet({ software: , ledger: <> })} ( - + + + )} /> diff --git a/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx b/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx index 3745bd094c2..6010708f8f1 100644 --- a/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx +++ b/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx @@ -1,18 +1,18 @@ import { useCallback, useMemo } from 'react'; import { Outlet, useNavigate } from 'react-router-dom'; +import { Box, styled } from 'leather-styles/jsx'; + import { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model'; import { RouteUrls } from '@shared/route-urls'; import { isDefined } from '@shared/utils'; import { useStxCryptoCurrencyAssetBalance } from '@app/common/hooks/balance/stx/use-stx-crypto-currency-asset-balance'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { useWalletType } from '@app/common/use-wallet-type'; -import { ChooseAssetContainer } from '@app/components/crypto-assets/choose-crypto-asset/choose-asset-container'; -import { ChooseCryptoAssetLayout } from '@app/components/crypto-assets/choose-crypto-asset/choose-crypto-asset.layout'; import { CryptoAssetList } from '@app/components/crypto-assets/choose-crypto-asset/crypto-asset-list'; -import { ModalHeader } from '@app/components/modal-header'; import { useCheckLedgerBlockchainAvailable } from '@app/store/accounts/blockchain/utils'; +import { Card } from '@app/ui/layout/card/card'; +import { Page } from '@app/ui/layout/page/page.layout'; export function ChooseCryptoAssetToFund() { const stxCryptoCurrencyAssetBalance = useStxCryptoCurrencyAssetBalance(); @@ -33,9 +33,7 @@ export function ChooseCryptoAssetToFund() { [stxCryptoCurrencyAssetBalance, checkBlockchainAvailable, whenWallet] ); - useRouteHeader( navigate(RouteUrls.Home)} title=" " />); - - const navigateToSendForm = useCallback( + const navigateToFund = useCallback( (cryptoAssetBalance: AllTransferableCryptoAssetBalances) => { const { asset } = cryptoAssetBalance; @@ -47,14 +45,23 @@ export function ChooseCryptoAssetToFund() { return ( <> - - - - - + + + choose asset
to fund + + } + > + + + +
+
); diff --git a/src/app/pages/fund/components/fund-account-tile.tsx b/src/app/pages/fund/components/fund-account-tile.tsx index 2396a3a7552..5338f789f11 100644 --- a/src/app/pages/fund/components/fund-account-tile.tsx +++ b/src/app/pages/fund/components/fund-account-tile.tsx @@ -6,12 +6,12 @@ interface FundAccountTileProps { description: string; icon: string; onClickTile(): void; - ReceiveStxIcon?(): React.JSX.Element; + ReceiveIcon?(): React.JSX.Element; testId: string; title?: string; } export function FundAccountTile(props: FundAccountTileProps) { - const { attributes, description, icon, onClickTile, ReceiveStxIcon, testId, title } = props; + const { attributes, description, icon, onClickTile, ReceiveIcon, testId, title } = props; return ( - - {ReceiveStxIcon ? : null} + + {ReceiveIcon ? : null} - - - Let's get funds into your wallet - + Let's get {symbol}
+ into your wallet + - - Choose an exchange to fund your account with {name} ({nameAbbr}) or deposit from - elsewhere. Exchanges with “Fast checkout” make it easier to purchase {nameAbbr} for direct - deposit into your wallet with a credit card. - -
+ + Choose an exchange to fund your account with {name} ({nameAbbr}) or deposit from elsewhere. + Exchanges with “Fast checkout” make it easier to purchase {nameAbbr} for direct deposit into + your wallet with a credit card. + {children} -
+ ); } diff --git a/src/app/pages/fund/components/icon-components.tsx b/src/app/pages/fund/components/icon-components.tsx index b6ab10ba3a4..c8cf0505a03 100644 --- a/src/app/pages/fund/components/icon-components.tsx +++ b/src/app/pages/fund/components/icon-components.tsx @@ -8,7 +8,7 @@ export function StacksIconComponent() { return ( <> - + @@ -20,7 +20,7 @@ export function StacksIconComponent() { export function BitcoinIconComponent() { return ( <> - + diff --git a/src/app/pages/fund/components/receive-funds-item.tsx b/src/app/pages/fund/components/receive-funds-item.tsx index 3a5854963b8..587a14e5d8b 100644 --- a/src/app/pages/fund/components/receive-funds-item.tsx +++ b/src/app/pages/fund/components/receive-funds-item.tsx @@ -33,7 +33,7 @@ export function ReceiveFundsItem({ onReceive, symbol }: ReceiveStxItemProps) { description={cryptoDescriptions[symbol].title} icon={QRCodeIcon} onClickTile={onReceive} - ReceiveStxIcon={cryptoDescriptions[symbol].IconComponent} + ReceiveIcon={cryptoDescriptions[symbol].IconComponent} testId={FundPageSelectors.BtnReceiveStx} /> ); diff --git a/src/app/pages/fund/fiat-providers-list.tsx b/src/app/pages/fund/fiat-providers-list.tsx index 67c7680a46e..446abe09cf3 100644 --- a/src/app/pages/fund/fiat-providers-list.tsx +++ b/src/app/pages/fund/fiat-providers-list.tsx @@ -48,22 +48,10 @@ export function FiatProvidersList(props: FiatProvidersProps) { return ( navigate(RouteUrls.FundChooseCurrency)} title=" " />); - if (!address || !balance) return ; return ( <> diff --git a/src/app/pages/home/components/account-actions.tsx b/src/app/pages/home/components/account-actions.tsx index 02ac19609f8..14643d65c47 100644 --- a/src/app/pages/home/components/account-actions.tsx +++ b/src/app/pages/home/components/account-actions.tsx @@ -2,7 +2,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { ChainID } from '@stacks/transactions'; import { HomePageSelectors } from '@tests/selectors/home.selectors'; -import { Flex, FlexProps } from 'leather-styles/jsx'; +import { Flex } from 'leather-styles/jsx'; import { RouteUrls } from '@shared/route-urls'; @@ -11,12 +11,12 @@ import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote- import { useCurrentAccountNativeSegwitIndexZeroSignerNullable } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; +import { IconButton } from '@app/ui/components/icon-button/icon-button'; import { CreditCardIcon, InboxIcon, SwapIcon } from '@app/ui/icons'; -import { ActionButton } from './action-button'; import { SendButton } from './send-button'; -export function AccountActions(props: FlexProps) { +export function AccountActions() { const navigate = useNavigate(); const location = useLocation(); const isBitcoinEnabled = useConfigBitcoinEnabled(); @@ -30,9 +30,9 @@ export function AccountActions(props: FlexProps) { : `${RouteUrls.Home}${RouteUrls.ReceiveStx}`; return ( - + - } label="Receive" @@ -40,7 +40,7 @@ export function AccountActions(props: FlexProps) { /> {(!!stacksAccount || !!btcAccount) && ( - } label="Buy" @@ -49,7 +49,7 @@ export function AccountActions(props: FlexProps) { )} {whenStacksChainId(currentNetwork.chain.stacks.chainId)({ [ChainID.Mainnet]: ( - } label="Swap" diff --git a/src/app/pages/home/components/account-area.tsx b/src/app/pages/home/components/account-area.tsx deleted file mode 100644 index c611ba8e090..00000000000 --- a/src/app/pages/home/components/account-area.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { memo } from 'react'; - -import { HStack, HstackProps, Stack } from 'leather-styles/jsx'; - -import { CurrentAccountAvatar } from '@app/features/current-account/current-account-avatar'; -import { CurrentAccountName } from '@app/features/current-account/current-account-name'; -import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; - -import { AccountTotalBalance } from '../../../components/account-total-balance'; - -export const CurrentAccount = memo((props: HstackProps) => { - const currentAccount = useCurrentStacksAccount(); - const btcAddress = useCurrentAccountNativeSegwitAddressIndexZero(); - - if (!currentAccount) return null; - return ( - - - - - - - - - - ); -}); diff --git a/src/app/pages/home/components/account-info-card.tsx b/src/app/pages/home/components/account-info-card.tsx deleted file mode 100644 index 9a0e2ff6059..00000000000 --- a/src/app/pages/home/components/account-info-card.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { SettingsSelectors } from '@tests/selectors/settings.selectors'; -import { Box, Divider, Flex, styled } from 'leather-styles/jsx'; - -import { useCurrentAccountDisplayName } from '@app/common/hooks/account/use-account-names'; -import { useTotalBalance } from '@app/common/hooks/balance/use-total-balance'; -import { useDrawers } from '@app/common/hooks/use-drawers'; -import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { Link } from '@app/ui/components/link/link'; -import { ChevronDownIcon } from '@app/ui/icons/chevron-down-icon'; - -import { AccountActions } from './account-actions'; - -export function AccountInfoCard() { - const name = useCurrentAccountDisplayName(); - - const account = useCurrentStacksAccount(); - const btcAddress = useCurrentAccountNativeSegwitAddressIndexZero(); - const totalBalance = useTotalBalance({ btcAddress, stxAddress: account?.address || '' }); - - const { setIsShowingSwitchAccountsState } = useDrawers(); - - return ( - - setIsShowingSwitchAccountsState(true)} - > - - - {name} - - - - - - - - - {totalBalance?.totalUsdBalance} - - - - - - - ); -} diff --git a/src/app/pages/home/components/action-button.tsx b/src/app/pages/home/components/action-button.tsx deleted file mode 100644 index 0c1234c0f80..00000000000 --- a/src/app/pages/home/components/action-button.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Flex, styled } from 'leather-styles/jsx'; - -import { Button } from '@app/ui/components/button/button'; - -import AccessibleIcon from './accessible-icon'; - -interface ActionButtonProps extends React.ComponentProps { - icon: React.ReactNode; - label: string; -} - -export function ActionButton({ icon, label, ...rest }: ActionButtonProps) { - return ( - - ); -} diff --git a/src/app/pages/home/components/home-tabs.tsx b/src/app/pages/home/components/home-tabs.tsx index f19f8ba83e8..ab5bca87814 100644 --- a/src/app/pages/home/components/home-tabs.tsx +++ b/src/app/pages/home/components/home-tabs.tsx @@ -11,25 +11,27 @@ import { Tabs } from '@app/ui/components/tabs/tabs'; interface HomeTabsProps { children: React.ReactNode; } -// TODO #4013: Abstract this to generic RouteTab once choose-fee-tab updated + export function HomeTabs({ children }: HomeTabsProps) { const navigate = useNavigate(); const location = useLocation(); return ( - + navigate(slug)} defaultValue={location.pathname}> Assets - + Activity }> - {children} + + {children} + ); diff --git a/src/app/pages/home/components/home.layout.tsx b/src/app/pages/home/components/home.layout.tsx deleted file mode 100644 index cb10533830d..00000000000 --- a/src/app/pages/home/components/home.layout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; - -import { HomePageSelectors } from '@tests/selectors/home.selectors'; -import { Stack } from 'leather-styles/jsx'; - -import { AccountInfoCard } from './account-info-card'; - -type HomeLayoutProps = Record<'currentAccount' | 'children', React.ReactNode>; -export function HomeLayout({ children }: HomeLayoutProps) { - return ( - - - - {children} - - - ); -} diff --git a/src/app/pages/home/components/send-button.tsx b/src/app/pages/home/components/send-button.tsx index 76fc5aa68d0..58df5cc0150 100644 --- a/src/app/pages/home/components/send-button.tsx +++ b/src/app/pages/home/components/send-button.tsx @@ -13,10 +13,9 @@ import { useTransferableStacksFungibleTokenAssetBalances, } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { IconButton } from '@app/ui/components/icon-button/icon-button'; import { SendIcon } from '@app/ui/icons'; -import { ActionButton } from './action-button'; - function SendButtonSuspense() { const navigate = useNavigate(); const { whenWallet } = useWalletType(); @@ -26,7 +25,7 @@ function SendButtonSuspense() { const isDisabled = !stxAssetBalance && ftAssets?.length === 0; return ( - } @@ -34,10 +33,10 @@ function SendButtonSuspense() { whenWallet({ ledger: () => whenPageMode({ - full: () => navigate(RouteUrls.SendCryptoAsset, { state: { hasHeaderTitle: true } }), + full: () => navigate(RouteUrls.SendCryptoAsset), popup: () => openIndexPageInNewTab(RouteUrls.SendCryptoAsset), })(), - software: () => navigate(RouteUrls.SendCryptoAsset, { state: { hasHeaderTitle: true } }), + software: () => navigate(RouteUrls.SendCryptoAsset), })() } disabled={isDisabled} @@ -45,7 +44,7 @@ function SendButtonSuspense() { ); } -const SendButtonFallback = memo(() => } disabled />); +const SendButtonFallback = memo(() => } disabled />); export function SendButton() { return ( diff --git a/src/app/pages/home/home.tsx b/src/app/pages/home/home.tsx index d6187512d63..675fa68984b 100644 --- a/src/app/pages/home/home.tsx +++ b/src/app/pages/home/home.tsx @@ -1,40 +1,59 @@ +import { useState } from 'react'; import { Route, useNavigate } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; +import { useCurrentAccountDisplayName } from '@app/common/hooks/account/use-account-names'; import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state'; +import { useTotalBalance } from '@app/common/hooks/balance/use-total-balance'; import { useOnMount } from '@app/common/hooks/use-on-mount'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { Header } from '@app/components/header'; import { ActivityList } from '@app/features/activity-list/activity-list'; import { AssetsList } from '@app/features/asset-list/asset-list'; +import { SwitchAccountDialog } from '@app/features/dialogs/switch-account-dialog/switch-account-dialog'; import { FeedbackButton } from '@app/features/feedback-button/feedback-button'; -import { InAppMessages } from '@app/features/hiro-messages/in-app-messages'; import { homePageModalRoutes } from '@app/routes/app-routes'; import { ModalBackgroundWrapper } from '@app/routes/components/modal-background-wrapper'; +import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { AccountCard } from '@app/ui/components/account/account.card'; +import { HomeLayout } from '@app/ui/pages/home.layout'; -import { CurrentAccount } from './components/account-area'; +import { AccountActions } from './components/account-actions'; import { HomeTabs } from './components/home-tabs'; -import { HomeLayout } from './components/home.layout'; export function Home() { + const [isShowingSwitchAccount, setIsShowingSwitchAccount] = useState(false); const { decodedAuthRequest } = useOnboardingState(); const navigate = useNavigate(); + const name = useCurrentAccountDisplayName(); - useRouteHeader( - <> - -
- - ); + const account = useCurrentStacksAccount(); + const btcAddress = useCurrentAccountNativeSegwitAddressIndexZero(); + const totalBalance = useTotalBalance({ btcAddress, stxAddress: account?.address || '' }); useOnMount(() => { if (decodedAuthRequest) navigate(RouteUrls.ChooseAccount); }); return ( - }> + setIsShowingSwitchAccount(false)} + /> + } + toggleSwitchAccount={() => setIsShowingSwitchAccount(!isShowingSwitchAccount)} + > + + + } + > diff --git a/src/app/pages/onboarding/allow-diagnostics/allow-diagnostics-layout.tsx b/src/app/pages/onboarding/allow-diagnostics/allow-diagnostics-layout.tsx index ccb2bc099aa..ce7e6ddf6a4 100644 --- a/src/app/pages/onboarding/allow-diagnostics/allow-diagnostics-layout.tsx +++ b/src/app/pages/onboarding/allow-diagnostics/allow-diagnostics-layout.tsx @@ -1,10 +1,11 @@ -import { Dialog } from '@radix-ui/themes'; import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; -import { css } from 'leather-styles/css'; -import { Box, Flex, HStack, Stack, styled } from 'leather-styles/jsx'; +import { Box, Flex, Stack, styled } from 'leather-styles/jsx'; import { Button } from '@app/ui/components/button/button'; -import { CheckmarkIcon } from '@app/ui/icons/checkmark-icon'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Flag } from '@app/ui/components/flag/flag'; +import { CheckmarkIcon } from '@app/ui/icons/'; import { LogomarkIcon } from '@app/ui/icons/logomark-icon'; interface ReasonToAllowDiagnosticsProps { @@ -12,12 +13,9 @@ interface ReasonToAllowDiagnosticsProps { } function ReasonToAllowDiagnostics({ text }: ReasonToAllowDiagnosticsProps) { return ( - - - - - {text} - + }> + {text} + ); } @@ -25,51 +23,56 @@ interface AllowDiagnosticsLayoutProps { onUserAllowDiagnostics(): void; onUserDenyDiagnostics(): void; } -export function AllowDiagnosticsLayout(props: AllowDiagnosticsLayoutProps) { - const { onUserAllowDiagnostics, onUserDenyDiagnostics } = props; +export function AllowDiagnosticsLayout({ + onUserAllowDiagnostics, + onUserDenyDiagnostics, +}: AllowDiagnosticsLayoutProps) { + // this dialog cannot close without a footer action has no header return ( - - - - - Help us improve - - + null} + footer={ +
+ + + + +
+ } + > + + + + + + + Help us improve + Leather would like to gather deidentified service usage data to help improve the experience of the wallet. - + - - - - - -
-
+ + ); } diff --git a/src/app/pages/onboarding/allow-diagnostics/allow-diagnostics.tsx b/src/app/pages/onboarding/allow-diagnostics/allow-diagnostics.tsx index a7c44eab07f..4d1dd6e5263 100644 --- a/src/app/pages/onboarding/allow-diagnostics/allow-diagnostics.tsx +++ b/src/app/pages/onboarding/allow-diagnostics/allow-diagnostics.tsx @@ -5,8 +5,6 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { Header } from '@app/components/header'; import { settingsActions } from '@app/store/settings/settings.actions'; import { AllowDiagnosticsLayout } from './allow-diagnostics-layout'; @@ -19,8 +17,6 @@ export function AllowDiagnosticsModal() { useEffect(() => void analytics.page('view', `${pathname}`), [analytics, pathname]); - useRouteHeader(
); - const setDiagnosticsPermissionsAndGoToOnboarding = useCallback( (areDiagnosticsAllowed: boolean) => { dispatch(settingsActions.setHasAllowedAnalytics(areDiagnosticsAllowed)); diff --git a/src/app/pages/onboarding/back-up-secret-key/back-up-secret-key.tsx b/src/app/pages/onboarding/back-up-secret-key/back-up-secret-key.tsx index 210ab4f6ab0..8721ef746e3 100644 --- a/src/app/pages/onboarding/back-up-secret-key/back-up-secret-key.tsx +++ b/src/app/pages/onboarding/back-up-secret-key/back-up-secret-key.tsx @@ -1,22 +1,19 @@ import { memo, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import { HStack, Stack, styled } from 'leather-styles/jsx'; + import { RouteUrls } from '@shared/route-urls'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { Header } from '@app/components/header'; -import { TwoColumnLayout } from '@app/components/secret-key/two-column.layout'; -import { SecretKeyDisplayer } from '@app/features/secret-key-displayer/secret-key-displayer'; +import { SecretKey } from '@app/features/secret-key-displayer/secret-key-displayer'; import { useDefaultWalletSecretKey } from '@app/store/in-memory-key/in-memory-key.selectors'; - -import { BackUpSecretKeyContent } from './components/back-up-secret-key.content'; +import { EyeSlashIcon, KeyIcon, LockIcon } from '@app/ui/icons/'; +import { TwoColumnLayout } from '@app/ui/pages/two-column.layout'; export const BackUpSecretKeyPage = memo(() => { const secretKey = useDefaultWalletSecretKey(); const navigate = useNavigate(); - useRouteHeader(
); - useEffect(() => { if (!secretKey) navigate(RouteUrls.Onboarding); }, [navigate, secretKey]); @@ -25,8 +22,37 @@ export const BackUpSecretKeyPage = memo(() => { return ( } - rightColumn={} - /> + title={<>Back up your Secret Key} + content={ + <> + You'll need it to access your wallet on a new device, or this one if you lose your + password — so back it up somewhere safe! + + } + action={ + + + + + Your Secret Key gives
access to your wallet +
+
+ + + + Never share your
Secret Key with anyone +
+
+ + + + Store it somewhere
100% private and secure +
+
+
+ } + > + +
); }); diff --git a/src/app/pages/onboarding/back-up-secret-key/components/back-up-secret-key.content.tsx b/src/app/pages/onboarding/back-up-secret-key/components/back-up-secret-key.content.tsx deleted file mode 100644 index 050fd314f75..00000000000 --- a/src/app/pages/onboarding/back-up-secret-key/components/back-up-secret-key.content.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { HStack, Stack, styled } from 'leather-styles/jsx'; - -import { KeyIcon } from '@app/ui/icons'; -import { EyeSlashIcon } from '@app/ui/icons/eye-slash-icon'; -import { LockIcon } from '@app/ui/icons/lock-icon'; - -export function BackUpSecretKeyContent(): React.JSX.Element { - return ( - <> - - Back up your
Secret Key -
- - Here's your Secret Key: 24 words that give you access to your new wallet. - - - You'll need it to access your wallet on a new device, or this one if you lose your password - — so back it up somewhere safe! - - - - - - Your Secret Key gives access to your wallet - - - - Never share your Secret Key with anyone - - - - Store it somewhere 100% private and secure - - - - ); -} diff --git a/src/app/pages/onboarding/set-password/components/password-bars.tsx b/src/app/pages/onboarding/set-password/components/password-bars.tsx index afa5cc44e98..1aabe5f3610 100644 --- a/src/app/pages/onboarding/set-password/components/password-bars.tsx +++ b/src/app/pages/onboarding/set-password/components/password-bars.tsx @@ -6,21 +6,19 @@ interface PasswordStrengthBarsProps { export function PasswordStrengthBars({ bars }: PasswordStrengthBarsProps) { return ( - {bars.map((bar: string, index: number) => { - return ( - - ); - })} + {bars.map((bar: string, index: number) => ( + + ))} ); } diff --git a/src/app/pages/onboarding/set-password/set-password.tsx b/src/app/pages/onboarding/set-password/set-password.tsx index b581478f9b9..dcc6e3efc09 100644 --- a/src/app/pages/onboarding/set-password/set-password.tsx +++ b/src/app/pages/onboarding/set-password/set-password.tsx @@ -3,7 +3,6 @@ import { useNavigate } from 'react-router-dom'; import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; import { Form, Formik } from 'formik'; -import { styled } from 'leather-styles/jsx'; import { debounce } from 'ts-debounce'; import * as yup from 'yup'; @@ -14,16 +13,14 @@ import { useFinishAuthRequest } from '@app/common/authentication/use-finish-auth import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state'; import { useKeyActions } from '@app/common/hooks/use-key-actions'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { blankPasswordValidation, validatePassword, } from '@app/common/validation/validate-password'; -import { Header } from '@app/components/header'; -import { TwoColumnLayout } from '@app/components/secret-key/two-column.layout'; import { OnboardingGate } from '@app/routes/onboarding-gate'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { Button } from '@app/ui/components/button/button'; +import { TwoColumnLayout } from '@app/ui/pages/two-column.layout'; import { PasswordField } from './components/password-field'; @@ -53,8 +50,6 @@ function SetPasswordPage() { const { decodedAuthRequest } = useOnboardingState(); const analytics = useAnalytics(); - useRouteHeader(
navigate(-1)} />); - useEffect(() => { void analytics.page('view', '/set-password'); }, [analytics]); @@ -125,48 +120,33 @@ function SetPasswordPage() { {({ dirty, isSubmitting, isValid }) => (
- - Set a password - - - Your password protects your Secret Key on this device only. - - - You'll need just your Secret Key to access your wallet on another device, or this - one if you lose your password. - + Set a
+ password } - rightColumn={ + content={ <> - - Your password - - - + Your password protects your Secret Key on this device only. To access your wallet on + another device, you'll need just your Secret Key. } - /> + > + <> + + + +
)} diff --git a/src/app/pages/onboarding/sign-in/components/sign-in.content.tsx b/src/app/pages/onboarding/sign-in/components/sign-in.content.tsx deleted file mode 100644 index 497c0bd6290..00000000000 --- a/src/app/pages/onboarding/sign-in/components/sign-in.content.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { styled } from 'leather-styles/jsx'; - -import { Link } from '@app/ui/components/link/link'; - -export function SignInContent({ - onClick, - twentyFourWordMode, -}: { - onClick(): void; - twentyFourWordMode: boolean; -}): React.JSX.Element { - return ( - <> - - Sign in
with your
- Secret Key -
- - Speed things up by pasting your entire Secret Key in one go. - - - {twentyFourWordMode ? 'Have a 12-word Secret Key?' : 'Use 24 word Secret Key'} - - - ); -} diff --git a/src/app/pages/onboarding/sign-in/mnemonic-form.tsx b/src/app/pages/onboarding/sign-in/mnemonic-form.tsx index e0c2f0b1060..2a9e327e497 100644 --- a/src/app/pages/onboarding/sign-in/mnemonic-form.tsx +++ b/src/app/pages/onboarding/sign-in/mnemonic-form.tsx @@ -1,22 +1,21 @@ import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; import { Form, Formik } from 'formik'; -import { Flex, styled } from 'leather-styles/jsx'; +import { Stack } from 'leather-styles/jsx'; import { isEmpty } from '@shared/utils'; import { createNullArrayOfLength } from '@app/common/utils'; import { ErrorLabel } from '@app/components/error-label'; -import { SecretKeyGrid } from '@app/components/secret-key/secret-key-grid'; import { useSignIn } from '@app/pages/onboarding/sign-in/hooks/use-sign-in'; import { Button } from '@app/ui/components/button/button'; - -import { MnemonicWordInput } from '../../../components/secret-key/mnemonic-key/mnemonic-word-input'; +import { MnemonicWordInput } from '@app/ui/components/secret-key/mnemonic-key/mnemonic-word-input'; import { getMnemonicErrorFields, getMnemonicErrorMessage, hasMnemonicFormValues, -} from '../../../components/secret-key/mnemonic-key/utils/error-handling'; -import { validationSchema } from '../../../components/secret-key/mnemonic-key/utils/validation'; +} from '@app/ui/components/secret-key/mnemonic-key/utils/error-handling'; +import { validationSchema } from '@app/ui/components/secret-key/mnemonic-key/utils/validation'; +import { SecretKeyGrid } from '@app/ui/components/secret-key/secret-key-grid'; interface MnemonicFormProps { mnemonic: (string | null)[]; @@ -69,24 +68,21 @@ export function MnemonicForm({ mnemonic, setMnemonic, twentyFourWordMode }: Mnem return (
- - Your Secret Key - - - {mnemonicFieldArray.map((_, i) => ( - { - (document.activeElement as HTMLInputElement).blur(); - updateEntireKey(key, setFieldValue); - }} - onUpdateWord={w => mnemonicWordUpdate(i, w)} - /> - ))} - - + + + {mnemonicFieldArray.map((_, i) => ( + { + (document.activeElement as HTMLInputElement).blur(); + updateEntireKey(key, setFieldValue); + }} + onUpdateWord={w => mnemonicWordUpdate(i, w)} + /> + ))} + {(showMnemonicErrors || error) && ( {showMnemonicErrors ? mnemonicErrorMessage : error} @@ -100,6 +96,7 @@ export function MnemonicForm({ mnemonic, setMnemonic, twentyFourWordMode }: Mnem aria-busy={isLoading} width="100%" type="submit" + variant="solid" onClick={e => { e.preventDefault(); return handleSubmit(); @@ -107,7 +104,7 @@ export function MnemonicForm({ mnemonic, setMnemonic, twentyFourWordMode }: Mnem > Continue - +
); }} diff --git a/src/app/pages/onboarding/sign-in/sign-in.tsx b/src/app/pages/onboarding/sign-in/sign-in.tsx index faad25e2dcb..45083dfd0e6 100644 --- a/src/app/pages/onboarding/sign-in/sign-in.tsx +++ b/src/app/pages/onboarding/sign-in/sign-in.tsx @@ -1,24 +1,14 @@ import { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { RouteUrls } from '@shared/route-urls'; - -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { createNullArrayOfLength } from '@app/common/utils'; -import { Header } from '@app/components/header'; -import { TwoColumnLayout } from '@app/components/secret-key/two-column.layout'; import { MnemonicForm } from '@app/pages/onboarding/sign-in/mnemonic-form'; - -import { SignInContent } from './components/sign-in.content'; +import { Link } from '@app/ui/components/link/link'; +import { TwoColumnLayout } from '@app/ui/pages/two-column.layout'; export function SignIn() { - const navigate = useNavigate(); - const [twentyFourWordMode, setTwentyFourWordMode] = useState(true); const [mnemonic, setMnemonic] = useState<(string | null)[]>([]); - useRouteHeader(
navigate(RouteUrls.Onboarding)} hideActions />); - useEffect(() => { const emptyMnemonicArray = twentyFourWordMode ? createNullArrayOfLength(24) @@ -27,22 +17,29 @@ export function SignIn() { }, [twentyFourWordMode]); return ( - <> - setTwentyFourWordMode(!twentyFourWordMode)} - twentyFourWordMode={twentyFourWordMode} - /> - } - rightColumn={ - - } + + Sign in
with your
Secret Key + + } + content={<>Speed things up by pasting your entire Secret Key in one go.} + action={ + setTwentyFourWordMode(!twentyFourWordMode)} + textStyle="label.03" + width="fit-content" + variant="text" + > + {twentyFourWordMode ? 'Have a 12-word Secret Key?' : 'Use 24 word Secret Key'} + + } + > + - +
); } diff --git a/src/app/pages/onboarding/welcome/welcome.layout.tsx b/src/app/pages/onboarding/welcome/welcome.layout.tsx deleted file mode 100644 index 64cee800550..00000000000 --- a/src/app/pages/onboarding/welcome/welcome.layout.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; -import { Box, Flex, styled } from 'leather-styles/jsx'; - -import { useViewportMinWidth } from '@app/common/hooks/use-media-query'; -import { Button } from '@app/ui/components/button/button'; -import { Link } from '@app/ui/components/link/link'; -import { LettermarkIcon } from '@app/ui/icons/lettermark-icon'; -import { LogomarkIcon } from '@app/ui/icons/logomark-icon'; - -interface WelcomeLayoutProps { - tagline: React.ReactNode; - subheader: React.ReactNode; - isGeneratingWallet: boolean; - onSelectConnectLedger(): void; - onStartOnboarding(): void; - onRestoreWallet(): void; -} -export function WelcomeLayout({ - tagline, - subheader, - isGeneratingWallet, - onStartOnboarding, - onSelectConnectLedger, - onRestoreWallet, -}: WelcomeLayoutProps): React.JSX.Element { - const isAtleastBreakpointMd = useViewportMinWidth('md'); - - return ( - - - - - - {tagline} - - - {subheader} - - - - - - - - Use existing key - - - - Use Ledger - - - - - - - - - - - ); -} diff --git a/src/app/pages/onboarding/welcome/welcome.tsx b/src/app/pages/onboarding/welcome/welcome.tsx index b513e63bcaf..ae3a1d54166 100644 --- a/src/app/pages/onboarding/welcome/welcome.tsx +++ b/src/app/pages/onboarding/welcome/welcome.tsx @@ -7,12 +7,10 @@ import { closeWindow } from '@shared/utils'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state'; import { useKeyActions } from '@app/common/hooks/use-key-actions'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { doesBrowserSupportWebUsbApi, isPopupMode, whenPageMode } from '@app/common/utils'; import { openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab'; import { useHasUserRespondedToAnalyticsConsent } from '@app/store/settings/settings.selectors'; - -import { WelcomeLayout } from './welcome.layout'; +import { WelcomeLayout } from '@app/ui/pages/welcome.layout'; export function WelcomePage() { const hasResponded = useHasUserRespondedToAnalyticsConsent(); @@ -23,7 +21,6 @@ export function WelcomePage() { const [isGeneratingWallet, setIsGeneratingWallet] = useState(false); - useRouteHeader(<>); const startOnboarding = useCallback(async () => { if (isPopupMode()) { openIndexPageInNewTab(RouteUrls.Onboarding); @@ -79,8 +76,6 @@ export function WelcomePage() { return ( <> startOnboarding()} diff --git a/src/app/pages/receive/components/address-qr-code.tsx b/src/app/pages/receive/components/address-qr-code.tsx deleted file mode 100644 index 1361219b762..00000000000 --- a/src/app/pages/receive/components/address-qr-code.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { memo, useMemo } from 'react'; - -import { createQR } from '@vkontakte/vk-qr'; -import { Box, Flex } from 'leather-styles/jsx'; -import { token } from 'leather-styles/tokens'; - -export const QrCode = memo(({ principal, ...rest }: { principal: string }) => { - const qrSvg = useMemo( - () => - createQR(principal, { - ecc: 0, - qrSize: 180, - backgroundColor: token('colors.ink.background-primary'), - foregroundColor: token('colors.ink.text-primary'), - }), - [principal] - ); - - const qr = ; // Bad? - - return ( - - {qr} - {qr} - - ); -}); diff --git a/src/app/pages/receive/components/receive-btc-warning.tsx b/src/app/pages/receive/components/receive-btc-warning.tsx index 7e03b1112c4..357bd610d31 100644 --- a/src/app/pages/receive/components/receive-btc-warning.tsx +++ b/src/app/pages/receive/components/receive-btc-warning.tsx @@ -4,7 +4,7 @@ export function ReceiveBtcModalWarning({ message }: { message: string }) { return ( + } + dataTestId={HomePageSelectors.ReceiveBtcTaprootQrCodeBtn} + onCopyAddress={async () => { + await copyToClipboard(btcAddressTaproot); + toast.success('Copied to clipboard!'); + }} + onClickQrCode={onClickQrOrdinal} + title="Ordinal inscription" + /> + } + onClickQrCode={onClickQrStamp} + onCopyAddress={async () => { + await copyToClipboard(btcAddressNativeSegwit); + toast.success('Copied to clipboard!'); + }} + title="Bitcoin Stamp" + /> + } + onCopyAddress={async () => { + await copyToClipboard(stxAddress); + toast.success('Copied to clipboard!'); + }} + onClickQrCode={onClickQrStacksNft} + title="Stacks NFT" + /> + + ); +} diff --git a/src/app/pages/receive/components/receive-item.tsx b/src/app/pages/receive/components/receive-item.tsx index 2b9060b5c22..f4813db8ec3 100644 --- a/src/app/pages/receive/components/receive-item.tsx +++ b/src/app/pages/receive/components/receive-item.tsx @@ -1,4 +1,4 @@ -import { Button } from '@app/ui/components/button/button'; +import { IconButton } from '@app/ui/components/icon-button/icon-button'; import { ItemLayoutWithButtons } from '@app/ui/components/item-layout/item-layout-with-buttons'; import { CopyIcon } from '@app/ui/icons/copy-icon'; import { QrCodeIcon } from '@app/ui/icons/qr-code-icon'; @@ -30,18 +30,14 @@ export function ReceiveItem({ caption={truncateMiddle(address, 6)} buttons={ <> - + } onClick={onCopyAddress} /> {onClickQrCode && ( - + /> )} } diff --git a/src/app/pages/receive/components/receive-items.tsx b/src/app/pages/receive/components/receive-items.tsx deleted file mode 100644 index 7a42aa5d28b..00000000000 --- a/src/app/pages/receive/components/receive-items.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Divider, Stack, styled } from 'leather-styles/jsx'; - -interface ReceiveItemListProps { - children: React.ReactNode; - title?: string; -} -export function ReceiveItemList({ children, title }: ReceiveItemListProps) { - return ( - <> - {title && ( - - {title} - - )} - - - {children} - - - ); -} diff --git a/src/app/pages/receive/components/receive-tokens.layout.tsx b/src/app/pages/receive/components/receive-tokens.layout.tsx index 63e70a2c2db..8e01e7a8b63 100644 --- a/src/app/pages/receive/components/receive-tokens.layout.tsx +++ b/src/app/pages/receive/components/receive-tokens.layout.tsx @@ -1,15 +1,17 @@ +import QRCode from 'react-qr-code'; import { useNavigate } from 'react-router-dom'; import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; import { Box, Flex, styled } from 'leather-styles/jsx'; +import { token } from 'leather-styles/tokens'; import { useLocationState } from '@app/common/hooks/use-location-state'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; import { AddressDisplayer } from '@app/ui/components/address-displayer/address-displayer'; import { Button } from '@app/ui/components/button/button'; - -import { QrCode } from './address-qr-code'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Header } from '@app/ui/components/containers/headers/header'; interface ReceiveTokensLayoutProps { address: string; @@ -26,14 +28,47 @@ export function ReceiveTokensLayout(props: ReceiveTokensLayoutProps) { const backgroundLocation = useLocationState('backgroundLocation'); return ( - navigate(backgroundLocation ?? '..')}> + + Receive
{title} + + } + onGoBack={() => navigate(backgroundLocation ?? '..')} + /> + } + isShowing + onClose={() => navigate(backgroundLocation ?? '..')} + footer={ +
+ +
+ } + > {warning && warning} - - - {title} - + - + + + {accountName && ( @@ -51,10 +86,7 @@ export function ReceiveTokensLayout(props: ReceiveTokensLayoutProps) { - -
+ ); } diff --git a/src/app/pages/receive/components/receive-tokens.tsx b/src/app/pages/receive/components/receive-tokens.tsx new file mode 100644 index 00000000000..e1dfe81c93c --- /dev/null +++ b/src/app/pages/receive/components/receive-tokens.tsx @@ -0,0 +1,52 @@ +import { HomePageSelectors } from '@tests/selectors/home.selectors'; +import { css } from 'leather-styles/css'; +import { Stack } from 'leather-styles/jsx'; + +import { copyToClipboard } from '@app/common/utils/copy-to-clipboard'; +import { useToast } from '@app/features/toasts/use-toast'; +import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; +import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; + +import { receiveTabStyle } from '../receive-dialog'; +import { ReceiveItem } from './receive-item'; + +interface ReceiveTokensProps { + btcAddressNativeSegwit: string; + stxAddress: string; + onClickQrBtc(): void; + onClickQrStx(): void; +} +export function ReceiveTokens({ + btcAddressNativeSegwit, + stxAddress, + onClickQrBtc, + onClickQrStx, +}: ReceiveTokensProps) { + const toast = useToast(); + return ( + + } + dataTestId={HomePageSelectors.ReceiveBtcNativeSegwitQrCodeBtn} + onCopyAddress={async () => { + await copyToClipboard(btcAddressNativeSegwit); + toast.success('Copied to clipboard!'); + }} + onClickQrCode={onClickQrBtc} + title="Bitcoin" + /> + } + dataTestId={HomePageSelectors.ReceiveStxQrCodeBtn} + onCopyAddress={async () => { + await copyToClipboard(stxAddress); + toast.success('Copied to clipboard!'); + }} + onClickQrCode={onClickQrStx} + title="Stacks" + /> + + ); +} diff --git a/src/app/pages/receive/receive-btc.tsx b/src/app/pages/receive/receive-btc.tsx index 8ac2e749423..ee4bf21c0cd 100644 --- a/src/app/pages/receive/receive-btc.tsx +++ b/src/app/pages/receive/receive-btc.tsx @@ -3,7 +3,7 @@ import { useLocation } from 'react-router-dom'; import get from 'lodash.get'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; +import { copyToClipboard } from '@app/common/utils/copy-to-clipboard'; import { useToast } from '@app/features/toasts/use-toast'; import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; import { useCurrentAccountIndex } from '@app/store/accounts/account'; @@ -27,18 +27,14 @@ export function ReceiveBtcModal({ type = 'btc' }: ReceiveBtcModalType) { const activeAccountBtcAddress = useNativeSegwitAccountIndexAddressIndexZero(accountIndex); const btcAddress = get(state, 'btcAddress', activeAccountBtcAddress); - const { onCopy } = useClipboard(btcAddress); - - function copyToClipboard() { - void analytics.track('copy_btc_address_to_clipboard'); - toast.success('Copied to clipboard!'); - onCopy(); - } - return ( { + void analytics.track('copy_btc_address_to_clipboard'); + await copyToClipboard(btcAddress); + toast.success('Copied to clipboard!'); + }} title={type === 'btc-stamp' ? 'BITCOIN STAMP' : 'BTC'} /> ); diff --git a/src/app/pages/receive/receive-dialog.tsx b/src/app/pages/receive/receive-dialog.tsx new file mode 100644 index 00000000000..1060e68bac8 --- /dev/null +++ b/src/app/pages/receive/receive-dialog.tsx @@ -0,0 +1,134 @@ +import { useLocation, useNavigate } from 'react-router-dom'; + +import { HomePageSelectors } from '@tests/selectors/home.selectors'; +import get from 'lodash.get'; + +import { RouteUrls } from '@shared/route-urls'; + +import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; +import { useLocationState } from '@app/common/hooks/use-location-state'; +import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; +import { useZeroIndexTaprootAddress } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; +import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; +import { Tabs } from '@app/ui/components/tabs/tabs'; + +import { ReceiveCollectibles } from './components/receive-collectibles'; +import { ReceiveTokens } from './components/receive-tokens'; + +type ReceiveDialog = 'full' | 'collectible'; + +export const receiveTabStyle = { + mt: 'space.03', + paddingX: 'space.05', + pb: 'space.05', + minHeight: '260px', +}; + +interface ReceiveDialogProps { + type?: 'full' | 'collectible'; +} + +export function ReceiveDialog({ type = 'full' }: ReceiveDialogProps) { + useBackgroundLocationRedirect(); + const analytics = useAnalytics(); + const backgroundLocation = useLocationState('backgroundLocation'); + const navigate = useNavigate(); + const location = useLocation(); + const btcAddressNativeSegwit = useCurrentAccountNativeSegwitAddressIndexZero(); + const stxAddress = useCurrentAccountStxAddressState(); + const accountIndex = get(location.state, 'accountIndex', undefined); + const btcAddressTaproot = useZeroIndexTaprootAddress(accountIndex); + + const title = + type === 'full' ? ( + <> + Choose asset
to receive + + ) : ( + <> + Receive
collectible + + ); + + function Collectibles() { + return ( + { + void analytics.track('select_inscription_to_add_new_collectible'); + navigate(`${RouteUrls.Home}${RouteUrls.ReceiveCollectibleOrdinal}`, { + state: { + btcAddressTaproot, + backgroundLocation, + }, + }); + }} + onClickQrStamp={() => + navigate(`${RouteUrls.Home}${RouteUrls.ReceiveBtcStamp}`, { + state: { backgroundLocation }, + }) + } + onClickQrStacksNft={() => + navigate(`${RouteUrls.Home}${RouteUrls.ReceiveStx}`, { + state: { backgroundLocation }, + }) + } + /> + ); + } + + return ( + navigate(backgroundLocation ?? '..')} + /> + } + onClose={() => navigate(backgroundLocation ?? '..')} + isShowing + > + {type === 'collectible' && } + {type === 'full' && ( + + + + Tokens + + + Collectibles + + + + + navigate(`${RouteUrls.Home}${RouteUrls.ReceiveBtc}`, { + state: { backgroundLocation }, + }) + } + onClickQrStx={() => + navigate(`${RouteUrls.Home}${RouteUrls.ReceiveStx}`, { + state: { backgroundLocation, btcAddressTaproot }, + }) + } + /> + + + + + + )} + + ); +} diff --git a/src/app/pages/receive/receive-modal.tsx b/src/app/pages/receive/receive-modal.tsx deleted file mode 100644 index e705dc3f779..00000000000 --- a/src/app/pages/receive/receive-modal.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { useLocation, useNavigate } from 'react-router-dom'; - -import { HomePageSelectors } from '@tests/selectors/home.selectors'; -import { Box, styled } from 'leather-styles/jsx'; -import get from 'lodash.get'; - -import { RouteUrls } from '@shared/route-urls'; - -import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; -import { useLocationState } from '@app/common/hooks/use-location-state'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; -import { useToast } from '@app/features/toasts/use-toast'; -import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; -import { useZeroIndexTaprootAddress } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; -import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; -import { OrdinalAvatarIcon } from '@app/ui/components/avatar/ordinal-avatar-icon'; -import { StampsAvatarIcon } from '@app/ui/components/avatar/stamps-avatar-icon'; -import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; - -import { ReceiveItem } from './components/receive-item'; -import { ReceiveItemList } from './components/receive-items'; - -type ReceiveModal = 'full' | 'collectible'; - -interface ReceiveModalProps { - type?: 'full' | 'collectible'; -} - -export function ReceiveModal({ type = 'full' }: ReceiveModalProps) { - useBackgroundLocationRedirect(); - const toast = useToast(); - const analytics = useAnalytics(); - const backgroundLocation = useLocationState('backgroundLocation'); - const navigate = useNavigate(); - const location = useLocation(); - const btcAddressNativeSegwit = useCurrentAccountNativeSegwitAddressIndexZero(); - const stxAddress = useCurrentAccountStxAddressState(); - const accountIndex = get(location.state, 'accountIndex', undefined); - const btcAddressTaproot = useZeroIndexTaprootAddress(accountIndex); - - const { onCopy: onCopyBtc } = useClipboard(btcAddressNativeSegwit); - const { onCopy: onCopyStx } = useClipboard(stxAddress); - const { onCopy: onCopyOrdinal } = useClipboard(btcAddressTaproot); - - function copyToClipboard(copyHandler: () => void, tracker = 'copy_address_to_clipboard') { - void analytics.track(tracker); - toast.success('Copied to clipboard!'); - copyHandler(); - } - - const title = - type === 'full' ? ( - <> - Choose asset -
- to receive - - ) : ( - <> - Receive -
- collectible - - ); - - return ( - <> - navigate(backgroundLocation ?? '..')}> - - - {title} - - {type === 'full' && ( - - } - dataTestId={HomePageSelectors.ReceiveBtcNativeSegwitQrCodeBtn} - onCopyAddress={() => copyToClipboard(onCopyBtc)} - onClickQrCode={() => - navigate(`${RouteUrls.Home}${RouteUrls.ReceiveBtc}`, { - state: { backgroundLocation }, - }) - } - title="Bitcoin" - /> - } - dataTestId={HomePageSelectors.ReceiveStxQrCodeBtn} - onCopyAddress={() => copyToClipboard(onCopyStx)} - onClickQrCode={() => - navigate(`${RouteUrls.Home}${RouteUrls.ReceiveStx}`, { - state: { backgroundLocation, btcAddressTaproot }, - }) - } - title="Stacks" - /> - - )} - - } - dataTestId={HomePageSelectors.ReceiveBtcTaprootQrCodeBtn} - onCopyAddress={() => - copyToClipboard(onCopyOrdinal, 'select_stamp_to_add_new_collectible') - } - onClickQrCode={() => { - void analytics.track('select_inscription_to_add_new_collectible'); - navigate(`${RouteUrls.Home}${RouteUrls.ReceiveCollectibleOrdinal}`, { - state: { - btcAddressTaproot, - backgroundLocation, - }, - }); - }} - title="Ordinal inscription" - /> - } - onClickQrCode={() => - navigate(`${RouteUrls.Home}${RouteUrls.ReceiveBtcStamp}`, { - state: { backgroundLocation }, - }) - } - onCopyAddress={() => - copyToClipboard(onCopyBtc, 'select_stamp_to_add_new_collectible') - } - title="Bitcoin Stamp" - /> - } - onCopyAddress={() => copyToClipboard(onCopyStx, 'select_nft_to_add_new_collectible')} - onClickQrCode={() => - navigate(`${RouteUrls.Home}${RouteUrls.ReceiveStx}`, { - state: { backgroundLocation }, - }) - } - title="Stacks NFT" - /> - - - - - ); -} diff --git a/src/app/pages/receive/receive-ordinal.tsx b/src/app/pages/receive/receive-ordinal.tsx index ed6e4118650..ddcaae56a0d 100644 --- a/src/app/pages/receive/receive-ordinal.tsx +++ b/src/app/pages/receive/receive-ordinal.tsx @@ -1,7 +1,7 @@ import { useLocation } from 'react-router-dom'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; +import { copyToClipboard } from '@app/common/utils/copy-to-clipboard'; import { useToast } from '@app/features/toasts/use-toast'; import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; @@ -13,18 +13,16 @@ export function ReceiveOrdinalModal() { const toast = useToast(); const analytics = useAnalytics(); const { state } = useLocation(); - const { onCopy } = useClipboard(state.btcAddressTaproot); - function copyToClipboard() { - void analytics.track('copy_address_to_add_new_inscription'); - toast.success('Copied to clipboard!'); - onCopy(); - } // #4028 FIXME - this doesn't open in new tab as it loses btcAddressTaproot amd crashes btcStamp and Stx are OK? return ( { + void analytics.track('copy_address_to_add_new_inscription'); + await copyToClipboard(state.btcAddressTaproot); + toast.success('Copied to clipboard!'); + }} title="ORD. INSCRIPTION" warning={} /> diff --git a/src/app/pages/receive/receive-stx.tsx b/src/app/pages/receive/receive-stx.tsx index 4d4708ade08..10002950e75 100644 --- a/src/app/pages/receive/receive-stx.tsx +++ b/src/app/pages/receive/receive-stx.tsx @@ -1,6 +1,6 @@ import { useCurrentAccountDisplayName } from '@app/common/hooks/account/use-account-names'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; +import { copyToClipboard } from '@app/common/utils/copy-to-clipboard'; import { useToast } from '@app/features/toasts/use-toast'; import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; @@ -12,22 +12,19 @@ export function ReceiveStxModal() { const toast = useToast(); const currentAccount = useCurrentStacksAccount(); const analytics = useAnalytics(); - const { onCopy } = useClipboard(currentAccount?.address ?? ''); const accountName = useCurrentAccountDisplayName(); - function copyToClipboard() { - void analytics.track('copy_stx_address_to_clipboard'); - toast.success('Copied to clipboard'); - onCopy(); - } - if (!currentAccount) return null; return ( { + void analytics.track('copy_stx_address_to_clipboard'); + await copyToClipboard(currentAccount.address); + toast.success('Copied to clipboard!'); + }} title="STX" /> ); diff --git a/src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx b/src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx index 642e425afb9..a8669b172d1 100644 --- a/src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx +++ b/src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx @@ -45,7 +45,7 @@ export function GetAddressesLayout(props: GetAddressesLayoutProps) { alignSelf="bottom" bg="ink.background-secondary" > - + By connecting you give permission to {requester} to see all addresses linked to this account diff --git a/src/app/pages/rpc-send-transfer/components/send-transfer-actions.tsx b/src/app/pages/rpc-send-transfer/components/send-transfer-actions.tsx deleted file mode 100644 index e61a2dce685..00000000000 --- a/src/app/pages/rpc-send-transfer/components/send-transfer-actions.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Button } from '@app/ui/components/button/button'; - -import { SendTransferFooter } from './send-transfer-footer'; - -interface SendTransferActionsProps { - action: string; - isLoading?: boolean; - onApprove(): void; -} -export function SendTransferActions({ action, isLoading, onApprove }: SendTransferActionsProps) { - return ( - - - - ); -} diff --git a/src/app/pages/rpc-send-transfer/components/send-transfer-footer.tsx b/src/app/pages/rpc-send-transfer/components/send-transfer-footer.tsx deleted file mode 100644 index 150d439d781..00000000000 --- a/src/app/pages/rpc-send-transfer/components/send-transfer-footer.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Flex } from 'leather-styles/jsx'; - -import { HasChildren } from '@app/common/has-children'; -import { whenPageMode } from '@app/common/utils'; - -export function SendTransferFooter({ children }: HasChildren) { - return ( - - {children} - - ); -} diff --git a/src/app/pages/rpc-send-transfer/rpc-send-transfer-confirmation.tsx b/src/app/pages/rpc-send-transfer/rpc-send-transfer-confirmation.tsx index 03231b49b59..21120194749 100644 --- a/src/app/pages/rpc-send-transfer/rpc-send-transfer-confirmation.tsx +++ b/src/app/pages/rpc-send-transfer/rpc-send-transfer-confirmation.tsx @@ -13,13 +13,14 @@ import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; import { formatMoney, formatMoneyPadded, i18nFormatCurrency } from '@app/common/money/format-money'; import { satToBtc } from '@app/common/money/unit-conversion'; +import { InfoCardFooter } from '@app/components/info-card/info-card'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; import { useBitcoinBroadcastTransaction } from '@app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction'; import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { Button } from '@app/ui/components/button/button'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; -import { SendTransferActions } from './components/send-transfer-actions'; import { SendTransferConfirmationDetails } from './components/send-transfer-confirmation-details'; import { useRpcSendTransferRequestParams } from './use-rpc-send-transfer'; @@ -61,7 +62,6 @@ export function RpcSendTransferConfirmation() { function formBtcTxSummaryState(txId: string) { return { - hasHeaderTitle: true, txLink: { blockchain: 'bitcoin', txid: txId || '', @@ -126,11 +126,16 @@ export function RpcSendTransferConfirmation() { total={totalSpend} feeRowValue={feeRowValue} /> - + + + ); } diff --git a/src/app/pages/rpc-send-transfer/rpc-send-transfer-container.tsx b/src/app/pages/rpc-send-transfer/rpc-send-transfer-container.tsx index a2340e4e06c..d7ff29bf6dc 100644 --- a/src/app/pages/rpc-send-transfer/rpc-send-transfer-container.tsx +++ b/src/app/pages/rpc-send-transfer/rpc-send-transfer-container.tsx @@ -4,9 +4,6 @@ import { Outlet, useOutletContext } from 'react-router-dom'; import { BtcFeeType } from '@shared/models/fees/bitcoin-fees.model'; import { closeWindow } from '@shared/utils'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { PopupHeader } from '@app/features/current-account/popup-header'; - import { RpcSendTransferContainerLayout } from './components/rpc-send-transfer-container.layout'; import { useRpcSendTransfer } from './use-rpc-send-transfer'; @@ -23,8 +20,6 @@ export function RpcSendTransferContainer() { const [selectedFeeType, setSelectedFeeType] = useState(null); const { origin } = useRpcSendTransfer(); - useRouteHeader(); - if (origin === null) { closeWindow(); throw new Error('Origin is null'); diff --git a/src/app/pages/rpc-send-transfer/rpc-send-transfer.tsx b/src/app/pages/rpc-send-transfer/rpc-send-transfer.tsx index ba7abe1c529..8809e1de7a4 100644 --- a/src/app/pages/rpc-send-transfer/rpc-send-transfer.tsx +++ b/src/app/pages/rpc-send-transfer/rpc-send-transfer.tsx @@ -3,9 +3,10 @@ import BigNumber from 'bignumber.js'; import { createMoney } from '@shared/models/money.model'; import { formatMoneyPadded } from '@app/common/money/format-money'; +import { InfoCardFooter } from '@app/components/info-card/info-card'; import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { Button } from '@app/ui/components/button/button'; -import { SendTransferActions } from './components/send-transfer-actions'; import { SendTransferDetails } from './components/send-transfer-details'; import { SendTransferHeader } from './components/send-transfer-header'; import { useRpcSendTransfer } from './use-rpc-send-transfer'; @@ -24,7 +25,11 @@ export function RpcSendTransfer() { amount={formattedMoney} currentAddress={nativeSegwitSigner.address} /> - + + + ); } diff --git a/src/app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message.tsx b/src/app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message.tsx index 192f19d16d1..2b5cffebb77 100644 --- a/src/app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message.tsx +++ b/src/app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message.tsx @@ -2,10 +2,8 @@ import { Outlet } from 'react-router-dom'; import { closeWindow } from '@shared/utils'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { Disclaimer } from '@app/components/disclaimer'; import { NoFeesWarningRow } from '@app/components/no-fees-warning-row'; -import { PopupHeader } from '@app/features/current-account/popup-header'; import { MessagePreviewBox } from '@app/features/message-signer/message-preview-box'; import { MessageSigningRequestLayout } from '@app/features/message-signer/message-signing-request.layout'; import { AccountGate } from '@app/routes/account-gate'; @@ -27,8 +25,6 @@ export function RpcSignBip322MessageRoute() { } function RpcSignBip322Message() { - useRouteHeader(); - const { origin, message, diff --git a/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx b/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx index a1a00da17ae..0ed40f404d2 100644 --- a/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx +++ b/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx @@ -61,7 +61,6 @@ export function useRpcSignPsbt() { const psbtTxSummaryState = { fee: formatMoneyPadded(fee), - hasHeaderTitle: true, sendingValue: formatMoney(transferTotalAsMoney), totalSpend: formatMoney(sumMoney([transferTotalAsMoney, fee])), txFiatValue: i18nFormatCurrency(calculateBitcoinFiatValue(transferTotalAsMoney)), diff --git a/src/app/pages/select-network/components/add-network-button.tsx b/src/app/pages/select-network/components/add-network-button.tsx deleted file mode 100644 index f26b43de8ed..00000000000 --- a/src/app/pages/select-network/components/add-network-button.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { SettingsSelectors } from '@tests/selectors/settings.selectors'; -import { Flex } from 'leather-styles/jsx'; - -import { Button } from '@app/ui/components/button/button'; - -interface AddNetworkButtonProps { - onAddNetwork(): void; -} -export function AddNetworkButton({ onAddNetwork }: AddNetworkButtonProps) { - return ( - - - - ); -} diff --git a/src/app/pages/select-network/components/network-list.layout.tsx b/src/app/pages/select-network/components/network-list.layout.tsx deleted file mode 100644 index 302dedf8429..00000000000 --- a/src/app/pages/select-network/components/network-list.layout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Flex, FlexProps } from 'leather-styles/jsx'; - -export function NetworkListLayout({ children, ...props }: FlexProps) { - return ( - - {children} - - ); -} diff --git a/src/app/pages/select-network/components/network-status-indicator.tsx b/src/app/pages/select-network/components/network-status-indicator.tsx deleted file mode 100644 index 5205ab80e70..00000000000 --- a/src/app/pages/select-network/components/network-status-indicator.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { CheckmarkIcon } from '@app/ui/icons/checkmark-icon'; -import { CloudOffIcon } from '@app/ui/icons/cloud-off-icon'; - -interface NetworkStatusIndicatorProps { - isActive: boolean; - isOnline: boolean; -} -export function NetworkStatusIndicator({ isActive, isOnline }: NetworkStatusIndicatorProps) { - return !isOnline ? : isActive ? : null; -} diff --git a/src/app/pages/select-network/select-network.tsx b/src/app/pages/select-network/select-network.tsx deleted file mode 100644 index 270036a87a9..00000000000 --- a/src/app/pages/select-network/select-network.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useNavigate } from 'react-router-dom'; - -import { WalletDefaultNetworkConfigurationIds } from '@shared/constants'; -import { RouteUrls } from '@shared/route-urls'; - -import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useLocationState } from '@app/common/hooks/use-location-state'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; -import { NetworkListLayout } from '@app/pages/select-network/components/network-list.layout'; -import { NetworkListItem } from '@app/pages/select-network/network-list-item'; -import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; -import { useCurrentNetworkState, useNetworksActions } from '@app/store/networks/networks.hooks'; -import { useNetworks } from '@app/store/networks/networks.selectors'; - -import { AddNetworkButton } from './components/add-network-button'; - -const defaultNetworkIds = Object.values(WalletDefaultNetworkConfigurationIds) as string[]; - -export function SelectNetwork() { - useBackgroundLocationRedirect(); - const navigate = useNavigate(); - const networks = useNetworks(); - const analytics = useAnalytics(); - const networksActions = useNetworksActions(); - const currentNetwork = useCurrentNetworkState(); - const backgroundLocation = useLocationState('backgroundLocation'); - - function addNetwork() { - void analytics.track('add_network'); - navigate(RouteUrls.AddNetwork); - } - - function removeNetwork(id: string) { - void analytics.track('remove_network'); - networksActions.removeNetwork(id); - } - - function selectNetwork(id: string) { - void analytics.track('change_network', { id }); - networksActions.changeNetwork(id); - closeNetworkModal(); - } - - function closeNetworkModal() { - navigate(backgroundLocation ?? '..'); - } - - return ( - - - {Object.keys(networks).map(id => ( - selectNetwork(id)} - isCustom={!defaultNetworkIds.includes(id)} - onRemoveNetwork={id => { - if (id === currentNetwork.id) networksActions.changeNetwork('mainnet'); - removeNetwork(id); - }} - /> - ))} - - - - ); -} diff --git a/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx b/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx index f23e1e15192..1bfa523827f 100644 --- a/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx +++ b/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx @@ -1,17 +1,17 @@ import { useNavigate } from 'react-router-dom'; +import { Box, styled } from 'leather-styles/jsx'; + import { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model'; import { RouteUrls } from '@shared/route-urls'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { useAllTransferableCryptoAssetBalances } from '@app/common/hooks/use-transferable-asset-balances.hooks'; import { useWalletType } from '@app/common/use-wallet-type'; -import { ChooseCryptoAssetLayout } from '@app/components/crypto-assets/choose-crypto-asset/choose-crypto-asset.layout'; import { CryptoAssetList } from '@app/components/crypto-assets/choose-crypto-asset/crypto-asset-list'; -import { ModalHeader } from '@app/components/modal-header'; import { useToast } from '@app/features/toasts/use-toast'; import { useConfigBitcoinSendEnabled } from '@app/query/common/remote-config/remote-config.query'; import { useCheckLedgerBlockchainAvailable } from '@app/store/accounts/blockchain/utils'; +import { Card } from '@app/ui/layout/card/card'; export function ChooseCryptoAsset() { const toast = useToast(); @@ -23,8 +23,6 @@ export function ChooseCryptoAsset() { const checkBlockchainAvailable = useCheckLedgerBlockchainAvailable(); - useRouteHeader(); - function navigateToSendForm(cryptoAssetBalance: AllTransferableCryptoAssetBalances) { const { asset } = cryptoAssetBalance; if (asset.symbol === 'BTC' && !isBitcoinSendEnabled) { @@ -45,16 +43,25 @@ export function ChooseCryptoAsset() { } return ( - - navigateToSendForm(cryptoAssetBalance)} - cryptoAssetBalances={allTransferableCryptoAssetBalances.filter(asset => - whenWallet({ - ledger: checkBlockchainAvailable(asset.blockchain), - software: true, - }) - )} - /> - + + choose asset
to send + + } + > + + navigateToSendForm(cryptoAssetBalance)} + cryptoAssetBalances={allTransferableCryptoAssetBalances.filter(asset => + whenWallet({ + ledger: checkBlockchainAvailable(asset.blockchain), + software: true, + }) + )} + variant="send" + /> + +
); } diff --git a/src/app/pages/send/locked-bitcoin-summary/locked-bitcoin-summary.tsx b/src/app/pages/send/locked-bitcoin-summary/locked-bitcoin-summary.tsx index a5650b7f630..058cf420cbd 100644 --- a/src/app/pages/send/locked-bitcoin-summary/locked-bitcoin-summary.tsx +++ b/src/app/pages/send/locked-bitcoin-summary/locked-bitcoin-summary.tsx @@ -5,7 +5,6 @@ import { HStack, styled } from 'leather-styles/jsx'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useBitcoinExplorerLink } from '@app/common/hooks/use-bitcoin-explorer-link'; import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { satToBtc } from '@app/common/money/unit-conversion'; import { InfoCard, @@ -13,7 +12,6 @@ import { InfoCardBtn, InfoCardFooter, } from '@app/components/info-card/info-card'; -import { ModalHeader } from '@app/components/modal-header'; import { useToast } from '@app/features/toasts/use-toast'; import { CheckmarkIcon } from '@app/ui/icons/checkmark-icon'; import { CopyIcon } from '@app/ui/icons/copy-icon'; @@ -39,8 +37,6 @@ export function LockBitcoinSummary() { toast.success('ID copied!'); } - useRouteHeader(); - return ( - navigate(-1)}> + } + isShowing + onGoBack={() => navigate(RouteUrls.Home)} + onClose={() => navigate(RouteUrls.Home)} + > - + ); diff --git a/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx b/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx index 6e73330b073..eb9cfe1a6ef 100644 --- a/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx +++ b/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx @@ -5,12 +5,14 @@ import { Box, Flex } from 'leather-styles/jsx'; import { RouteUrls } from '@shared/route-urls'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; import { ErrorLabel } from '@app/components/error-label'; import { InscriptionPreview } from '@app/components/inscription-preview-card/components/inscription-preview'; import { InscriptionPreviewCard } from '@app/components/inscription-preview-card/inscription-preview-card'; import { OrdinalAvatarIcon } from '@app/ui/components/avatar/ordinal-avatar-icon'; import { Button } from '@app/ui/components/button/button'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Header } from '@app/ui/components/containers/headers/header'; import { RecipientAddressTypeField } from '../send-crypto-asset-form/components/recipient-address-type-field'; import { CollectibleAsset } from './components/collectible-asset'; @@ -36,31 +38,46 @@ export function SendInscriptionForm() { }} onSubmit={chooseTransactionFee} > -
- navigate(RouteUrls.Home)}> - - - } - subtitle="Ordinal inscription" - title={inscription.title} - /> - - - } name="Ordinal inscription" /> - { + return ( + + } + onGoBack={() => navigate(-1)} + isShowing + onClose={() => navigate(RouteUrls.Home)} + footer={ +
+ +
+ } + > + + + } + subtitle="Ordinal inscription" + title={inscription.title} /> -
-
- {currentError && {currentError}} - -
-
-
-
+ + + } name="Ordinal inscription" /> + + + + {currentError && {currentError}} +
+ + + + ); + }} ); } diff --git a/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx b/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx index c4a0d5ab348..66d9027a03e 100644 --- a/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx +++ b/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx @@ -8,13 +8,14 @@ import { RouteUrls } from '@shared/route-urls'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { FormAddressDisplayer } from '@app/components/address-displayer/form-address-displayer'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; import { InfoCard, InfoCardRow, InfoCardSeparator } from '@app/components/info-card/info-card'; import { InscriptionPreview } from '@app/components/inscription-preview-card/components/inscription-preview'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; import { useAppDispatch } from '@app/store'; import { inscriptionSent } from '@app/store/ordinals/ordinals.slice'; import { Button } from '@app/ui/components/button/button'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { InscriptionPreviewCard } from '../../../components/inscription-preview-card/inscription-preview-card'; import { useBitcoinBroadcastTransaction } from '../../../query/bitcoin/transaction/use-bitcoin-broadcast-transaction'; @@ -70,7 +71,12 @@ export function SendInscriptionReview() { } return ( - navigate(RouteUrls.Home)}> + } + isShowing + onGoBack={() => navigate(-1)} + onClose={() => navigate(RouteUrls.Home)} + > } @@ -91,6 +97,6 @@ export function SendInscriptionReview() { Confirm and send transaction - + ); } diff --git a/src/app/pages/send/ordinal-inscription/sent-inscription-summary.tsx b/src/app/pages/send/ordinal-inscription/sent-inscription-summary.tsx index 89d616d09ef..72501652488 100644 --- a/src/app/pages/send/ordinal-inscription/sent-inscription-summary.tsx +++ b/src/app/pages/send/ordinal-inscription/sent-inscription-summary.tsx @@ -11,7 +11,6 @@ import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useBitcoinExplorerLink } from '@app/common/hooks/use-bitcoin-explorer-link'; import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; import { FormAddressDisplayer } from '@app/components/address-displayer/form-address-displayer'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; import { InfoCard, InfoCardBtn, @@ -20,6 +19,8 @@ import { } from '@app/components/info-card/info-card'; import { InscriptionPreview } from '@app/components/inscription-preview-card/components/inscription-preview'; import { useToast } from '@app/features/toasts/use-toast'; +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; import { CheckmarkIcon } from '@app/ui/icons/checkmark-icon'; import { CopyIcon } from '@app/ui/icons/copy-icon'; import { ExternalLinkIcon } from '@app/ui/icons/external-link-icon'; @@ -61,7 +62,11 @@ export function SendInscriptionSummary() { } return ( - navigate(RouteUrls.Home)}> + } + isShowing + onClose={() => navigate(RouteUrls.Home)} + > } @@ -84,6 +89,6 @@ export function SendInscriptionSummary() { } label="Copy ID" /> - + ); } diff --git a/src/app/pages/send/send-container.tsx b/src/app/pages/send/send-container.tsx deleted file mode 100644 index dab31762d6c..00000000000 --- a/src/app/pages/send/send-container.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Outlet } from 'react-router-dom'; - -import { Flex } from 'leather-styles/jsx'; - -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { whenPageMode } from '@app/common/utils'; -import { ModalHeader } from '@app/components/modal-header'; - -export function SendContainer() { - useRouteHeader(, true); - - return whenPageMode({ - full: ( - - - - ), - popup: ( - - - - ), - }); -} diff --git a/src/app/pages/send/send-crypto-asset-form/components/form-footer.tsx b/src/app/pages/send/send-crypto-asset-form/components/form-footer.tsx deleted file mode 100644 index 0333803e457..00000000000 --- a/src/app/pages/send/send-crypto-asset-form/components/form-footer.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Box, Flex } from 'leather-styles/jsx'; - -import { Money } from '@shared/models/money.model'; - -import { whenPageMode } from '@app/common/utils'; -import { AvailableBalance } from '@app/components/available-balance'; -import { PreviewButton } from '@app/components/preview-button'; - -export function FormFooter(props: { balance: Money; balanceTooltipLabel?: string }) { - const { balance, balanceTooltipLabel } = props; - - return ( - - - - - - - ); -} diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/account-list-item.tsx similarity index 95% rename from src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx rename to src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/account-list-item.tsx index 03b38962918..08da077101a 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/account-list-item.tsx @@ -7,11 +7,11 @@ import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names'; import { AccountTotalBalance } from '@app/components/account-total-balance'; import { AcccountAddresses } from '@app/components/account/account-addresses'; -import { AccountAvatarItem } from '@app/components/account/account-avatar-item'; import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; import { AccountNameLayout } from '@app/components/account/account-name'; import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; +import { AccountAvatarItem } from '@app/ui/components/account/account-avatar/account-avatar-item'; interface AccountListItemProps { stacksAccount: StacksAccount; diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx new file mode 100644 index 00000000000..6cf8c57b05f --- /dev/null +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx @@ -0,0 +1,48 @@ +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Virtuoso } from 'react-virtuoso'; + +import { Box } from 'leather-styles/jsx'; + +import { useFilteredBitcoinAccounts } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; +import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { Dialog, getHeightOffset } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; +import { virtuosoHeight, virtuosoStyles } from '@app/ui/shared/virtuoso'; + +import { AccountListItem } from './account-list-item'; + +export function RecipientAccountsDialog() { + const stacksAccounts = useStacksAccounts(); + const navigate = useNavigate(); + + const onGoBack = useCallback(() => navigate('..', { replace: true }), [navigate]); + const bitcoinAccounts = useFilteredBitcoinAccounts(); + const btcAddressesNum = bitcoinAccounts.length / 2; + const stacksAddressesNum = stacksAccounts.length; + + if (stacksAddressesNum === 0 && btcAddressesNum === 0) return null; + const accountNum = stacksAddressesNum || btcAddressesNum; + + return ( + } isShowing onClose={onGoBack}> + ( + + + + )} + totalCount={stacksAddressesNum || btcAddressesNum} + /> + + ); +} diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/select-account-button.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/select-account-button.tsx similarity index 100% rename from src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/select-account-button.tsx rename to src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/select-account-button.tsx diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/recipient-accounts-drawer.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/recipient-accounts-drawer.tsx deleted file mode 100644 index d14745b071e..00000000000 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/recipient-accounts-drawer.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { memo, useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { Virtuoso } from 'react-virtuoso'; - -import { Box } from 'leather-styles/jsx'; - -import { BaseDrawer } from '@app/components/drawer/base-drawer'; -import { useFilteredBitcoinAccounts } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; -import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; - -import { AccountListItem } from './account-list-item'; - -export const RecipientAccountsDrawer = memo(() => { - const stacksAccounts = useStacksAccounts(); - const navigate = useNavigate(); - - const onGoBack = useCallback(() => navigate('..', { replace: true }), [navigate]); - const bitcoinAccounts = useFilteredBitcoinAccounts(); - const btcAddressesNum = bitcoinAccounts.length / 2; - const stacksAddressesNum = stacksAccounts.length; - - if (stacksAddressesNum === 0 && btcAddressesNum === 0) return null; - - return ( - - - ( - - - - )} - style={{ paddingTop: '24px', height: '70vh' }} - totalCount={stacksAddressesNum || btcAddressesNum} - /> - - - ); -}); diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/components/recipient-address-displayer.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/components/recipient-address-displayer.tsx index ed568d29f4a..4510ac690f3 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/components/recipient-address-displayer.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/components/recipient-address-displayer.tsx @@ -23,7 +23,7 @@ export function RecipientAddressDisplayer({ address }: RecipientAddressDisplayer return ( {address} diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/recipient-field.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/recipient-field.tsx index 0c9dc2a79ce..613201c1013 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/recipient-field.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/recipient-field.tsx @@ -1,6 +1,6 @@ import { TextInputFieldError } from '@app/components/field-error'; -import { SelectAccountButton } from '../recipient-accounts-drawer/select-account-button'; +import { SelectAccountButton } from '../recipient-accounts-dialog/select-account-button'; import { RecipientAddressTypeField } from '../recipient-address-type-field'; import { RecipientIdentifierTypeDropdown } from '../recipient-type-dropdown/recipient-type-dropdown'; import { useRecipientSelectFields } from './hooks/use-recipient-select-fields'; diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-type-dropdown/recipient-type-dropdown.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-type-dropdown/recipient-type-dropdown.tsx index ee9ecdc1f36..1ee2a369dbe 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-type-dropdown/recipient-type-dropdown.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-type-dropdown/recipient-type-dropdown.tsx @@ -1,6 +1,6 @@ import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors'; -import { DropdownMenu } from '@app/ui/components/dowpdown-menu/dropdown-menu'; +import { DropdownMenu } from '@app/ui/components/dropdown-menu/dropdown-menu'; import { Flag } from '@app/ui/components/flag/flag'; import { ChevronDownIcon } from '@app/ui/icons'; diff --git a/src/app/pages/send/send-crypto-asset-form/components/send-crypto-asset-form.layout.tsx b/src/app/pages/send/send-crypto-asset-form/components/send-crypto-asset-form.layout.tsx deleted file mode 100644 index 38c13c25d95..00000000000 --- a/src/app/pages/send/send-crypto-asset-form/components/send-crypto-asset-form.layout.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors'; -import { Flex } from 'leather-styles/jsx'; - -interface SendCryptoAssetFormLayoutProps { - children: React.ReactNode; -} -export function SendCryptoAssetFormLayout({ children }: SendCryptoAssetFormLayoutProps) { - return ( - - {children} - - ); -} diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx index 8c8cc960982..20d63244dce 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx @@ -9,7 +9,6 @@ import { BtcFeeType } from '@shared/models/fees/bitcoin-fees.model'; import { createMoney } from '@shared/models/money.model'; import { RouteUrls } from '@shared/route-urls'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { formFeeRowValue } from '@app/common/send/utils'; import { useGenerateUnsignedNativeSegwitSingleRecipientTx } from '@app/common/transactions/bitcoin/use-generate-bitcoin-tx'; import { @@ -18,7 +17,6 @@ import { } from '@app/components/bitcoin-fees-list/bitcoin-fees-list'; import { useBitcoinFeesList } from '@app/components/bitcoin-fees-list/use-bitcoin-fees-list'; import { LoadingSpinner } from '@app/components/loading-spinner'; -import { ModalHeader } from '@app/components/modal-header'; import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choose-fee'; import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend'; import { useToast } from '@app/features/toasts/use-toast'; @@ -114,13 +112,6 @@ export function BrcChooseFee() { } } - function onGoBack() { - setSelectedFeeType(null); - navigate(-1); - } - - useRouteHeader(); - return isLoadingOrder ? ( ); - return ( - - + + - } - autoComplete="off" - /> - } name={ticker} symbol={ticker} /> - - -
  • 1. Create transfer inscription with amount to send
  • -
  • 2. Send transfer inscription to recipient of choice
  • -
    - { - openInNewTab( - 'https://leather.gitbook.io/guides/bitcoin/sending-brc-20-tokens' - ); - }} - textStyle="body.02" - > - Learn more - -
    -
    + + } + > + + + } + autoComplete="off" + /> + } name={ticker} symbol={ticker} /> + + +
  • 1. Create transfer inscription with amount to send
  • +
  • 2. Send transfer inscription to recipient of choice
  • +
    + { + openInNewTab( + 'https://leather.gitbook.io/guides/bitcoin/sending-brc-20-tokens' + ); + }} + textStyle="body.02" + > + Learn more + +
    +
    + - ); diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx index 6303f932bb1..8feeb3f3cf2 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx @@ -4,10 +4,8 @@ import { BtcFeeType } from '@shared/models/fees/bitcoin-fees.model'; import { BitcoinSendFormValues } from '@shared/models/form.model'; import { useLocationStateWithCache } from '@app/common/hooks/use-location-state'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { BitcoinFeesList } from '@app/components/bitcoin-fees-list/bitcoin-fees-list'; import { useBitcoinFeesList } from '@app/components/bitcoin-fees-list/use-bitcoin-fees-list'; -import { ModalHeader } from '@app/components/modal-header'; import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choose-fee'; import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend'; import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client'; @@ -26,7 +24,7 @@ export function useBtcChooseFeeState() { export function BtcChooseFee() { const { isSendingMax, txValues, utxos } = useBtcChooseFeeState(); const { selectedFeeType, setSelectedFeeType } = useSendBitcoinAssetContextState(); - const { amountAsMoney, onGoBack, previewTransaction } = useBtcChooseFee(); + const { amountAsMoney, previewTransaction } = useBtcChooseFee(); const { feesList, isLoading } = useBitcoinFeesList({ amount: amountAsMoney, @@ -41,8 +39,6 @@ export function BtcChooseFee() { isSendingMax ); - useRouteHeader(); - return ( <> ); - return ( - - - } - onSetIsSendingMax={onSetIsSendingMax} - isSendingMax={isSendingMax} - switchableAmount={ - - } - /> - } - name={btcBalance.asset.name} - symbol={symbol} - /> - - {currentNetwork.chain.bitcoin.bitcoinNetwork === 'testnet' && ( - - {'This is a Bitcoin testnet transaction. Funds have no value. '} - - Get testnet BTC here ↗ - - - )} - - - + + + + + } + > + + + } + onSetIsSendingMax={onSetIsSendingMax} + isSendingMax={isSendingMax} + switchableAmount={ + + } + /> + } + name={btcBalance.asset.name} + symbol={symbol} + /> + + {currentNetwork.chain.bitcoin.bitcoinNetwork === 'testnet' && ( + + This is a Bitcoin testnet transaction. + + Get testnet BTC here ↗ + + + )} + + + {/* This is for testing purposes only, to make sure the form is ready to be submitted. */} diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-choose-fee.tsx index 55f5dcb07d7..b47a92ffbd3 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-choose-fee.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-choose-fee.tsx @@ -1,7 +1,4 @@ -import { useNavigate } from 'react-router-dom'; - import { logger } from '@shared/logger'; -import { BtcFeeType } from '@shared/models/fees/bitcoin-fees.model'; import { createMoney } from '@shared/models/money.model'; import { btcToSat } from '@app/common/money/unit-conversion'; @@ -10,27 +7,20 @@ import { useGenerateUnsignedNativeSegwitSingleRecipientTx } from '@app/common/tr import { OnChooseFeeArgs } from '@app/components/bitcoin-fees-list/bitcoin-fees-list'; import { useSignBitcoinTx } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; -import { useSendBitcoinAssetContextState } from '../../family/bitcoin/components/send-bitcoin-asset-container'; import { useCalculateMaxBitcoinSpend } from '../../family/bitcoin/hooks/use-calculate-max-spend'; import { useSendFormNavigate } from '../../hooks/use-send-form-navigate'; import { useBtcChooseFeeState } from './btc-choose-fee'; export function useBtcChooseFee() { const { isSendingMax, txValues, utxos } = useBtcChooseFeeState(); - const navigate = useNavigate(); const sendFormNavigate = useSendFormNavigate(); const generateTx = useGenerateUnsignedNativeSegwitSingleRecipientTx(); - const { setSelectedFeeType } = useSendBitcoinAssetContextState(); const calcMaxSpend = useCalculateMaxBitcoinSpend(); const signTx = useSignBitcoinTx(); const amountAsMoney = createMoney(btcToSat(txValues.amount).toNumber(), 'BTC'); return { amountAsMoney, - onGoBack() { - setSelectedFeeType(BtcFeeType.Standard); - navigate(-1); - }, async previewTransaction({ feeRate, feeValue, time, isCustomFee }: OnChooseFeeArgs) { const resp = await generateTx( diff --git a/src/app/pages/send/send-crypto-asset-form/form/send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/send-form-confirmation.tsx index 3c905dcd75b..f2e1843bdb8 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/send-form-confirmation.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/send-form-confirmation.tsx @@ -1,7 +1,6 @@ import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors'; import { Stack } from 'leather-styles/jsx'; -import { whenPageMode } from '@app/common/utils'; import { FormAddressDisplayer } from '@app/components/address-displayer/form-address-displayer'; import { InfoCard, @@ -48,10 +47,7 @@ export function SendFormConfirmation({ return (
    - - {amountField} - {selectedAssetField} - - - - - - navigate(RouteUrls.EditNonce)} - > - Edit nonce - - - - + + + + + } + > + + {amountField} + {selectedAssetField} + + + + + + navigate(RouteUrls.EditNonce)} + > + Edit nonce + + + + + diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx index 8bb9e4fb5b7..d91454baed6 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx @@ -1,4 +1,4 @@ -import { Outlet, useNavigate, useParams } from 'react-router-dom'; +import { Outlet, useParams } from 'react-router-dom'; import { deserializeTransaction } from '@stacks/transactions'; import { Box, Stack } from 'leather-styles/jsx'; @@ -6,8 +6,6 @@ import { Box, Stack } from 'leather-styles/jsx'; import { CryptoCurrencies } from '@shared/models/currencies.model'; import { useLocationStateWithCache } from '@app/common/hooks/use-location-state'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { ModalHeader } from '@app/components/modal-header'; import { useStacksBroadcastTransaction } from '@app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction'; import { useStacksTransactionSummary } from '@app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; @@ -26,7 +24,6 @@ function useStacksSendFormConfirmationState() { export function StacksSendFormConfirmation() { const { tx, decimals, showFeeChangeWarning } = useStacksSendFormConfirmationState(); const { symbol = 'STX' } = useParams(); - const navigate = useNavigate(); const { stacksBroadcastTransaction, isBroadcasting } = useStacksBroadcastTransaction( symbol.toUpperCase() as CryptoCurrencies, @@ -51,15 +48,6 @@ export function StacksSendFormConfirmation() { memoDisplayText, } = formReviewTxSummary(stacksDeserializedTransaction, symbol, decimals); - useRouteHeader( - navigate('../', { relative: 'path', replace: true })} - title="Review" - /> - ); - const feeWarningTooltip = showFeeChangeWarning ? ( ) { // Validate and check high fee warning first const formErrors = formikHelpers.validateForm(); - if ( - !isShowingHighFeeConfirmation && - isEmpty(formErrors) && - new BigNumber(values.fee).isGreaterThan(HIGH_FEE_AMOUNT_STX) - ) { - setIsShowingHighFeeConfirmation(true); + if (isEmpty(formErrors) && new BigNumber(values.fee).isGreaterThan(HIGH_FEE_AMOUNT_STX)) { return false; } return true; diff --git a/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx index 870350795c6..0e95742f203 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx @@ -22,6 +22,7 @@ export function StxSendForm() { sendMaxBalance, stxFees: fees, validationSchema, + fee, } = useStxSendForm(); const amountField = ( @@ -47,6 +48,9 @@ export function StxSendForm() { amountField={amountField} selectedAssetField={selectedAssetField} fees={fees} + // FIXME 4370 - need to fix this as fee is actually NumberSchema; in FeeValidatorFactoryArgs + // this needs to be the STX fee so it can be validated against HIGH_FEE_AMOUNT_STX + fee={fee as unknown as string} availableTokenBalance={availableStxBalance} /> ); diff --git a/src/app/pages/send/send-crypto-asset-form/form/stx/use-stx-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stx/use-stx-send-form.tsx index 78ad2f2b77d..7da098a7a22 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stx/use-stx-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stx/use-stx-send-form.tsx @@ -50,12 +50,16 @@ export function useStxSendForm() { availableTokenBalance: availableStxBalance, }); + // FIXME - I don't this this is the fee, should be value.fee or something from the form + const fee = stxFeeValidator(availableStxBalance); + return { availableStxBalance, initialValues, onFormStateChange, sendMaxBalance, stxFees, + fee, validationSchema: yup.object({ amount: stxAmountValidator(availableStxBalance).concat( diff --git a/src/app/pages/send/send-crypto-asset-form/hooks/use-send-form-navigate.ts b/src/app/pages/send/send-crypto-asset-form/hooks/use-send-form-navigate.ts index df0de5edb5a..4243f5411d4 100644 --- a/src/app/pages/send/send-crypto-asset-form/hooks/use-send-form-navigate.ts +++ b/src/app/pages/send/send-crypto-asset-form/hooks/use-send-form-navigate.ts @@ -14,7 +14,6 @@ interface ConfirmationRouteState { decimals?: number; token?: string; tx: string; - hasHeaderTitle?: boolean; } interface ConfirmationRouteStacksSip10Args { @@ -49,7 +48,6 @@ export function useSendFormNavigate() { isSendingMax, utxos, values, - hasHeaderTitle: true, }, }); }, @@ -67,7 +65,6 @@ export function useSendFormNavigate() { fee, feeRowValue, time, - hasHeaderTitle: true, } as ConfirmationRouteState, }); }, @@ -76,7 +73,6 @@ export function useSendFormNavigate() { replace: true, state: { tx: bytesToHex(tx.serialize()), - hasHeaderTitle: true, showFeeChangeWarning, } as ConfirmationRouteState, }); diff --git a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx index 8ad33c31f81..01fec10cac8 100644 --- a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx +++ b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx @@ -1,23 +1,23 @@ import { Suspense } from 'react'; -import { Route } from 'react-router-dom'; +import { Outlet, Route } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; -import { BroadcastErrorDrawer } from '@app/components/broadcast-error-drawer/broadcast-error-drawer'; +import { BroadcastErrorDialog } from '@app/components/broadcast-error-dialog/broadcast-error-dialog'; import { SendBtcDisabled } from '@app/components/crypto-assets/choose-crypto-asset/send-btc-disabled'; import { FullPageWithHeaderLoadingSpinner } from '@app/components/loading-spinner'; -import { EditNonceDrawer } from '@app/features/edit-nonce-drawer/edit-nonce-drawer'; +import { EditNonceDialog } from '@app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog'; import { ledgerBitcoinTxSigningRoutes } from '@app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container'; import { ledgerStacksTxSigningRoutes } from '@app/features/ledger/flows/stacks-tx-signing/ledger-sign-stacks-tx-container'; import { AccountGate } from '@app/routes/account-gate'; +import { Page } from '@app/ui/layout/page/page.layout'; import { BroadcastError } from '../broadcast-error/broadcast-error'; import { ChooseCryptoAsset } from '../choose-crypto-asset/choose-crypto-asset'; -import { SendContainer } from '../send-container'; import { Brc20SentSummary } from '../sent-summary/brc20-sent-summary'; import { BtcSentSummary } from '../sent-summary/btc-sent-summary'; import { StxSentSummary } from '../sent-summary/stx-sent-summary'; -import { RecipientAccountsDrawer } from './components/recipient-accounts-drawer/recipient-accounts-drawer'; +import { RecipientAccountsDialog } from './components/recipient-accounts-dialog/recipient-accounts-dialog'; import { SendBitcoinAssetContainer } from './family/bitcoin/components/send-bitcoin-asset-container'; import { Brc20SendForm } from './form/brc-20/brc20-send-form'; import { Brc20SendFormConfirmation } from './form/brc-20/brc20-send-form-confirmation'; @@ -29,20 +29,26 @@ import { Sip10TokenSendForm } from './form/stacks-sip10/sip10-token-send-form'; import { StacksSendFormConfirmation } from './form/stacks/stacks-send-form-confirmation'; import { StxSendForm } from './form/stx/stx-send-form'; -const recipientAccountsDrawerRoute = ( +const recipientAccountsDialogRoute = ( } + element={} /> ); -const editNonceDrawerRoute = } />; -const broadcastErrorDrawerRoute = ( - } /> +const editNonceDialogRoute = } />; +const broadcastErrorDialogRoute = ( + } /> ); export const sendCryptoAssetFormRoutes = ( - }> + + + + } + > } /> - }> } > {ledgerBitcoinTxSigningRoutes} - {recipientAccountsDrawerRoute} + {recipientAccountsDialogRoute} - } /> - } /> + } /> + } /> - } /> + } /> }> {ledgerBitcoinTxSigningRoutes} @@ -78,11 +83,10 @@ export const sendCryptoAssetFormRoutes = ( } /> } /> - }> - {broadcastErrorDrawerRoute} - {editNonceDrawerRoute} - {recipientAccountsDrawerRoute} + {broadcastErrorDialogRoute} + {editNonceDialogRoute} + {recipientAccountsDialogRoute} {ledgerStacksTxSigningRoutes} - }> - {broadcastErrorDrawerRoute} - {editNonceDrawerRoute} - {recipientAccountsDrawerRoute} + {broadcastErrorDialogRoute} + {editNonceDialogRoute} + {recipientAccountsDialogRoute} }> {ledgerStacksTxSigningRoutes} - } /> ); diff --git a/src/app/pages/send/sent-summary/brc20-sent-summary.tsx b/src/app/pages/send/sent-summary/brc20-sent-summary.tsx index 6b2748160d4..d610a1318bb 100644 --- a/src/app/pages/send/sent-summary/brc20-sent-summary.tsx +++ b/src/app/pages/send/sent-summary/brc20-sent-summary.tsx @@ -5,7 +5,6 @@ import get from 'lodash.get'; import { createMoney } from '@shared/models/money.model'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { HandleOpenStacksTxLinkArgs } from '@app/common/hooks/use-stacks-explorer-link'; import { formatMoney } from '@app/common/money/format-money'; import { openInNewTab } from '@app/common/utils/open-in-new-tab'; @@ -17,7 +16,6 @@ import { InfoCardRow, InfoCardSeparator, } from '@app/components/info-card/info-card'; -import { ModalHeader } from '@app/components/modal-header'; import { Callout } from '@app/ui/components/callout/callout'; import { Link } from '@app/ui/components/link/link'; import { ExternalLinkIcon } from '@app/ui/icons/external-link-icon'; @@ -47,8 +45,6 @@ export function Brc20SentSummary() { navigate('/'); } - useRouteHeader(); - return ( diff --git a/src/app/pages/send/sent-summary/btc-sent-summary.tsx b/src/app/pages/send/sent-summary/btc-sent-summary.tsx index 8dd0f56b216..0a213463094 100644 --- a/src/app/pages/send/sent-summary/btc-sent-summary.tsx +++ b/src/app/pages/send/sent-summary/btc-sent-summary.tsx @@ -5,7 +5,6 @@ import { HStack, Stack } from 'leather-styles/jsx'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useBitcoinExplorerLink } from '@app/common/hooks/use-bitcoin-explorer-link'; import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { FormAddressDisplayer } from '@app/components/address-displayer/form-address-displayer'; import { InfoCard, @@ -15,7 +14,6 @@ import { InfoCardRow, InfoCardSeparator, } from '@app/components/info-card/info-card'; -import { ModalHeader } from '@app/components/modal-header'; import { useToast } from '@app/features/toasts/use-toast'; import { CopyIcon } from '@app/ui/icons/copy-icon'; import { ExternalLinkIcon } from '@app/ui/icons/external-link-icon'; @@ -54,8 +52,6 @@ export function BtcSentSummary() { toast.success('ID copied!'); } - useRouteHeader(); - return ( diff --git a/src/app/pages/send/sent-summary/stx-sent-summary.tsx b/src/app/pages/send/sent-summary/stx-sent-summary.tsx index 8346b8c9cce..af3fcaa6422 100644 --- a/src/app/pages/send/sent-summary/stx-sent-summary.tsx +++ b/src/app/pages/send/sent-summary/stx-sent-summary.tsx @@ -4,9 +4,7 @@ import { HStack, Stack } from 'leather-styles/jsx'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { useStacksExplorerLink } from '@app/common/hooks/use-stacks-explorer-link'; -import { whenPageMode } from '@app/common/utils'; import { FormAddressDisplayer } from '@app/components/address-displayer/form-address-displayer'; import { InfoCard, @@ -16,7 +14,6 @@ import { InfoCardRow, InfoCardSeparator, } from '@app/components/info-card/info-card'; -import { ModalHeader } from '@app/components/modal-header'; import { useToast } from '@app/features/toasts/use-toast'; import { CopyIcon } from '@app/ui/icons/copy-icon'; import { ExternalLinkIcon } from '@app/ui/icons/external-link-icon'; @@ -55,15 +52,8 @@ export function StxSentSummary() { toast.success('ID copied!'); } - useRouteHeader(); - return ( - + - -
    - - When you sign out, - {whenWallet({ - software: ` you'll need your Secret Key to sign back in. Only sign out if you've backed up your Secret Key.`, - ledger: ` you'll need to reconnect your Ledger to sign back into your wallet.`, - })} - - - {whenWallet({ - software: ( - } spacing="space.02"> - If you haven't backed up your Secret Key, you will lose all your funds. - - ), - ledger: <>, - })} - - - - - - - I've backed up my Secret Key - - - - - - - - I understand my password will no longer work for accessing my wallet upon signing out - - - - - - -
    -
    - - ); -} diff --git a/src/app/pages/sign-out-confirm/sign-out-confirm.tsx b/src/app/pages/sign-out-confirm/sign-out-confirm.tsx deleted file mode 100644 index b335ec8ed8a..00000000000 --- a/src/app/pages/sign-out-confirm/sign-out-confirm.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useNavigate } from 'react-router-dom'; - -import { RouteUrls } from '@shared/route-urls'; - -import { useKeyActions } from '@app/common/hooks/use-key-actions'; -import { useLocationState } from '@app/common/hooks/use-location-state'; -import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; - -import { SignOutConfirmLayout } from './sign-out-confirm.layout'; - -export function SignOutConfirmDrawer() { - useBackgroundLocationRedirect(); - const { signOut } = useKeyActions(); - const navigate = useNavigate(); - const backgroundLocation = useLocationState('backgroundLocation'); - - return ( - { - void signOut().finally(() => { - navigate(RouteUrls.Onboarding); - }); - }} - onUserSafelyReturnToHomepage={() => navigate(backgroundLocation ?? '..')} - /> - ); -} diff --git a/src/app/pages/swap/alex-swap-container.tsx b/src/app/pages/swap/alex-swap-container.tsx index 9a47b3a7d9b..a86d90648f9 100644 --- a/src/app/pages/swap/alex-swap-container.tsx +++ b/src/app/pages/swap/alex-swap-container.tsx @@ -21,8 +21,8 @@ import { NonceSetter } from '@app/components/nonce-setter'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useGenerateStacksContractCallUnsignedTx } from '@app/store/transactions/contract-call.hooks'; import { useSignStacksTransaction } from '@app/store/transactions/transaction.hooks'; +import { Page } from '@app/ui/layout/page/page.layout'; -import { SwapContainerLayout } from './components/swap-container.layout'; import { SwapForm } from './components/swap-form'; import { generateSwapRoutes } from './generate-swap-routes'; import { useAlexBroadcastSwap } from './hooks/use-alex-broadcast-swap'; @@ -204,12 +204,12 @@ function AlexSwapContainer() { return ( - + - + ); } diff --git a/src/app/pages/swap/components/swap-amount-field.tsx b/src/app/pages/swap/components/swap-amount-field.tsx index 6c4336c1c8f..dbd0a5e5425 100644 --- a/src/app/pages/swap/components/swap-amount-field.tsx +++ b/src/app/pages/swap/components/swap-amount-field.tsx @@ -79,7 +79,7 @@ export function SwapAmountField({ amountAsFiat, isDisabled, name }: SwapAmountFi }} /> {amountAsFiat ? ( - + {amountAsFiat} ) : null} diff --git a/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx b/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx index 44d54f375bc..5a9beb435ba 100644 --- a/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx +++ b/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx @@ -16,7 +16,7 @@ export function SwapAssetItemLayout({ caption, icon, symbol, value }: SwapAssetI spacing="space.03" width="100%" > - + {caption} diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx b/src/app/pages/swap/components/swap-choose-asset/components/swap-asset-item.tsx similarity index 92% rename from src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx rename to src/app/pages/swap/components/swap-choose-asset/components/swap-asset-item.tsx index ff3dedfd37d..7fabdbcd131 100644 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx +++ b/src/app/pages/swap/components/swap-choose-asset/components/swap-asset-item.tsx @@ -7,8 +7,8 @@ import { Avatar, defaultFallbackDelay, getAvatarFallback } from '@app/ui/compone import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; import { Pressable } from '@app/ui/pressable/pressable'; -import { useAlexSdkBalanceAsFiat } from '../../hooks/use-alex-sdk-fiat-price'; -import { SwapAsset } from '../../hooks/use-swap-form'; +import { useAlexSdkBalanceAsFiat } from '../../../hooks/use-alex-sdk-fiat-price'; +import { SwapAsset } from '../../../hooks/use-swap-form'; interface SwapAssetItemProps { asset: SwapAsset; diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx b/src/app/pages/swap/components/swap-choose-asset/components/swap-asset-list.tsx similarity index 89% rename from src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx rename to src/app/pages/swap/components/swap-choose-asset/components/swap-asset-list.tsx index 32c2028fd89..c7870c93e52 100644 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx +++ b/src/app/pages/swap/components/swap-choose-asset/components/swap-asset-list.tsx @@ -1,7 +1,9 @@ import { useNavigate } from 'react-router-dom'; +import { SwapSelectors } from '@tests/selectors/swap.selectors'; import BigNumber from 'bignumber.js'; import { useFormikContext } from 'formik'; +import { Stack } from 'leather-styles/jsx'; import { createMoney } from '@shared/models/money.model'; import { isUndefined } from '@shared/utils'; @@ -10,10 +12,9 @@ import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; import { useSwapContext } from '@app/pages/swap/swap.context'; -import { SwapAsset, SwapFormValues } from '../../hooks/use-swap-form'; +import { SwapAsset, SwapFormValues } from '../../../hooks/use-swap-form'; import { useSwapChooseAssetState } from '../swap-choose-asset'; import { SwapAssetItem } from './swap-asset-item'; -import { SwapAssetListLayout } from './swap-asset-list.layout'; interface SwapAssetList { assets: SwapAsset[]; @@ -46,6 +47,7 @@ export function SwapAssetList({ assets }: SwapAssetList) { await setFieldValue('swapAssetTo', asset); setFieldError('swapAssetTo', undefined); } + navigate(-1); if (from && to && values.swapAmountFrom) { const toAmount = await fetchToAmount(from, to, values.swapAmountFrom); @@ -64,7 +66,7 @@ export function SwapAssetList({ assets }: SwapAssetList) { } return ( - + {selectableAssets.map(asset => ( onChooseAsset(asset)} /> ))} - +
    ); } diff --git a/src/app/pages/swap/components/swap-choose-asset/swap-choose-asset.tsx b/src/app/pages/swap/components/swap-choose-asset/swap-choose-asset.tsx new file mode 100644 index 00000000000..20683678027 --- /dev/null +++ b/src/app/pages/swap/components/swap-choose-asset/swap-choose-asset.tsx @@ -0,0 +1,45 @@ +import { useLocation, useNavigate } from 'react-router-dom'; + +import get from 'lodash.get'; + +import { RouteUrls } from '@shared/route-urls'; + +import { Dialog } from '@app/ui/components/containers/dialog/dialog'; +import { Header } from '@app/ui/components/containers/headers/header'; + +import { useSwapContext } from '../../swap.context'; +import { SwapAssetList } from './components/swap-asset-list'; + +export function useSwapChooseAssetState() { + const location = useLocation(); + const swapListType = get(location.state, 'swap') as string; + return { swapListType }; +} + +export function SwapChooseAsset() { + const { swappableAssetsFrom, swappableAssetsTo } = useSwapContext(); + const { swapListType } = useSwapChooseAssetState(); + const navigate = useNavigate(); + + const isFromList = swapListType === 'from'; + + const title = isFromList ? ( + <> + Choose asset
    to swap + + ) : ( + <> + Choose asset
    to receive + + ); + + return ( + navigate(RouteUrls.Swap)} + header={
    navigate(RouteUrls.Swap)} />} + > + +
    + ); +} diff --git a/src/app/pages/swap/components/swap-container.layout.tsx b/src/app/pages/swap/components/swap-container.layout.tsx deleted file mode 100644 index bdd32521f9a..00000000000 --- a/src/app/pages/swap/components/swap-container.layout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Flex } from 'leather-styles/jsx'; - -import { HasChildren } from '@app/common/has-children'; -import { whenPageMode } from '@app/common/utils'; - -export function SwapContainerLayout({ children }: HasChildren) { - return whenPageMode({ - full: ( - - {children} - - ), - popup: ( - - {children} - - ), - }); -} diff --git a/src/app/pages/swap/components/swap-content.layout.tsx b/src/app/pages/swap/components/swap-content.layout.tsx deleted file mode 100644 index 0d3d2147691..00000000000 --- a/src/app/pages/swap/components/swap-content.layout.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { SwapSelectors } from '@tests/selectors/swap.selectors'; -import { Flex } from 'leather-styles/jsx'; - -import { HasChildren } from '@app/common/has-children'; - -export function SwapContentLayout({ children }: HasChildren) { - return ( - - {children} - - ); -} diff --git a/src/app/pages/swap/swap-error/swap-error.tsx b/src/app/pages/swap/components/swap-error.tsx similarity index 100% rename from src/app/pages/swap/swap-error/swap-error.tsx rename to src/app/pages/swap/components/swap-error.tsx diff --git a/src/app/pages/swap/components/swap-footer.layout.tsx b/src/app/pages/swap/components/swap-footer.layout.tsx deleted file mode 100644 index 61339a2c4d8..00000000000 --- a/src/app/pages/swap/components/swap-footer.layout.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Flex } from 'leather-styles/jsx'; - -import { HasChildren } from '@app/common/has-children'; -import { whenPageMode } from '@app/common/utils'; - -export function SwapFooterLayout({ children }: HasChildren) { - return ( - - {children} - - ); -} diff --git a/src/app/pages/swap/components/swap-review.tsx b/src/app/pages/swap/components/swap-review.tsx new file mode 100644 index 00000000000..e4e5e1ab8dd --- /dev/null +++ b/src/app/pages/swap/components/swap-review.tsx @@ -0,0 +1,44 @@ +import { Outlet } from 'react-router-dom'; + +import { SwapSelectors } from '@tests/selectors/swap.selectors'; + +import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; +import { Button } from '@app/ui/components/button/button'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Card } from '@app/ui/layout/card/card'; +import { CardContent } from '@app/ui/layout/card/card-content'; + +import { useSwapContext } from '../swap.context'; +import { SwapAssetsPair } from './swap-assets-pair/swap-assets-pair'; +import { SwapDetails } from './swap-details/swap-details'; + +export function SwapReview() { + const { onSubmitSwap } = useSwapContext(); + const { isLoading } = useLoading(LoadingKeys.SUBMIT_SWAP_TRANSACTION); + + return ( + <> + + + + } + > + + + + + + + + ); +} diff --git a/src/app/pages/swap/components/swap-selected-asset.layout.tsx b/src/app/pages/swap/components/swap-selected-asset.layout.tsx index b8cc6083ced..c5ddb5c9eae 100644 --- a/src/app/pages/swap/components/swap-selected-asset.layout.tsx +++ b/src/app/pages/swap/components/swap-selected-asset.layout.tsx @@ -77,7 +77,7 @@ export function SwapSelectedAssetLayout({ {showError ? error : caption} @@ -88,7 +88,7 @@ export function SwapSelectedAssetLayout({ onClick={onClickHandler ? onClickHandler : noop} variant={onClickHandler ? 'underlined' : 'text'} > - {value} + {value}
    ) : null} diff --git a/src/app/pages/swap/generate-swap-routes.tsx b/src/app/pages/swap/generate-swap-routes.tsx index eba6b0906db..2a3cd476a9d 100644 --- a/src/app/pages/swap/generate-swap-routes.tsx +++ b/src/app/pages/swap/generate-swap-routes.tsx @@ -5,10 +5,10 @@ import { RouteUrls } from '@shared/route-urls'; import { ledgerStacksTxSigningRoutes } from '@app/features/ledger/flows/stacks-tx-signing/ledger-sign-stacks-tx-container'; import { AccountGate } from '@app/routes/account-gate'; +import { SwapChooseAsset } from './components/swap-choose-asset/swap-choose-asset'; +import { SwapError } from './components/swap-error'; +import { SwapReview } from './components/swap-review'; import { Swap } from './swap'; -import { SwapChooseAsset } from './swap-choose-asset/swap-choose-asset'; -import { SwapError } from './swap-error/swap-error'; -import { SwapReview } from './swap-review/swap-review'; export function generateSwapRoutes(container: React.ReactNode) { return ( diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx b/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx deleted file mode 100644 index eb40bfdcbf4..00000000000 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Stack, StackProps } from 'leather-styles/jsx'; - -export function SwapAssetListLayout({ children }: StackProps) { - return ( - - {children} - - ); -} diff --git a/src/app/pages/swap/swap-choose-asset/swap-choose-asset.tsx b/src/app/pages/swap/swap-choose-asset/swap-choose-asset.tsx deleted file mode 100644 index d095c762849..00000000000 --- a/src/app/pages/swap/swap-choose-asset/swap-choose-asset.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useLocation, useNavigate } from 'react-router-dom'; - -import { SwapSelectors } from '@tests/selectors/swap.selectors'; -import { Box, styled } from 'leather-styles/jsx'; -import get from 'lodash.get'; - -import { BaseDrawer } from '@app/components/drawer/base-drawer'; - -import { useSwapContext } from '../swap.context'; -import { SwapAssetList } from './components/swap-asset-list'; - -export function useSwapChooseAssetState() { - const location = useLocation(); - const swapListType = get(location.state, 'swap') as string; - return { swapListType }; -} - -export function SwapChooseAsset() { - const { swappableAssetsFrom, swappableAssetsTo } = useSwapContext(); - const { swapListType } = useSwapChooseAssetState(); - const navigate = useNavigate(); - - const isFromList = swapListType === 'from'; - - const title = isFromList ? ( - <> - Choose asset -
    - to swap - - ) : ( - <> - Choose asset -
    - to receive - - ); - - return ( - navigate(-1)}> - - - {title} - - - - - ); -} diff --git a/src/app/pages/swap/swap-review/swap-review.layout.tsx b/src/app/pages/swap/swap-review/swap-review.layout.tsx deleted file mode 100644 index 64936f0b26c..00000000000 --- a/src/app/pages/swap/swap-review/swap-review.layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Flex } from 'leather-styles/jsx'; - -import { HasChildren } from '@app/common/has-children'; - -export function SwapReviewLayout({ children }: HasChildren) { - return ( - - {children} - - ); -} diff --git a/src/app/pages/swap/swap-review/swap-review.tsx b/src/app/pages/swap/swap-review/swap-review.tsx deleted file mode 100644 index accef3a66a9..00000000000 --- a/src/app/pages/swap/swap-review/swap-review.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Outlet } from 'react-router-dom'; - -import { SwapSelectors } from '@tests/selectors/swap.selectors'; - -import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { ModalHeader } from '@app/components/modal-header'; -import { Button } from '@app/ui/components/button/button'; - -import { SwapAssetsPair } from '../components/swap-assets-pair/swap-assets-pair'; -import { SwapContentLayout } from '../components/swap-content.layout'; -import { SwapDetails } from '../components/swap-details/swap-details'; -import { SwapFooterLayout } from '../components/swap-footer.layout'; -import { useSwapContext } from '../swap.context'; -import { SwapReviewLayout } from './swap-review.layout'; - -export function SwapReview() { - const { onSubmitSwap } = useSwapContext(); - const { isLoading } = useLoading(LoadingKeys.SUBMIT_SWAP_TRANSACTION); - - useRouteHeader(, true); - - return ( - <> - - - - - - - - - - - - ); -} diff --git a/src/app/pages/swap/swap.tsx b/src/app/pages/swap/swap.tsx index 64b8cb5fce4..04331c47075 100644 --- a/src/app/pages/swap/swap.tsx +++ b/src/app/pages/swap/swap.tsx @@ -6,13 +6,12 @@ import { useFormikContext } from 'formik'; import { isUndefined } from '@shared/utils'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { LoadingSpinner } from '@app/components/loading-spinner'; -import { ModalHeader } from '@app/components/modal-header'; import { Button } from '@app/ui/components/button/button'; +import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Card } from '@app/ui/layout/card/card'; +import { CardContent } from '@app/ui/layout/card/card-content'; -import { SwapContentLayout } from './components/swap-content.layout'; -import { SwapFooterLayout } from './components/swap-footer.layout'; import { SwapSelectedAssets } from './components/swap-selected-assets'; import { SwapFormValues } from './hooks/use-swap-form'; import { useSwapContext } from './swap.context'; @@ -21,8 +20,6 @@ export function Swap() { const { isFetchingExchangeRate, swappableAssetsFrom } = useSwapContext(); const { dirty, isValid, setFieldValue, values } = useFormikContext(); - useRouteHeader(, true); - useAsync(async () => { if (isUndefined(values.swapAssetFrom)) return await setFieldValue('swapAssetFrom', swappableAssetsFrom[0]); @@ -32,21 +29,24 @@ export function Swap() { if (isUndefined(values.swapAssetFrom)) return ; return ( - <> - + + + + } + > + - - - - + - + ); } diff --git a/src/app/pages/transaction-request/transaction-request.tsx b/src/app/pages/transaction-request/transaction-request.tsx index 55aea73d0dd..049cdd81d6f 100644 --- a/src/app/pages/transaction-request/transaction-request.tsx +++ b/src/app/pages/transaction-request/transaction-request.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react'; +import { memo, useState } from 'react'; import { Outlet, useNavigate } from 'react-router-dom'; import { Formik, FormikHelpers } from 'formik'; @@ -13,13 +13,11 @@ import { RouteUrls } from '@shared/route-urls'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useOnMount } from '@app/common/hooks/use-on-mount'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { stxFeeValidator } from '@app/common/validation/forms/fee-validators'; import { nonceValidator } from '@app/common/validation/nonce-validators'; import { NonceSetter } from '@app/components/nonce-setter'; -import { PopupHeader } from '@app/features/current-account/popup-header'; +import { HighFeeDialog } from '@app/features/dialogs/high-fee-dialog/high-fee-dialog'; import { RequestingTabClosedWarningMessage } from '@app/features/errors/requesting-tab-closed-error-msg'; -import { HighFeeDrawer } from '@app/features/high-fee-drawer/high-fee-drawer'; import { ContractCallDetails } from '@app/features/stacks-transaction-request/contract-call-details/contract-call-details'; import { ContractDeployDetails } from '@app/features/stacks-transaction-request/contract-deploy-details/contract-deploy-details'; import { FeeForm } from '@app/features/stacks-transaction-request/fee-form'; @@ -42,6 +40,8 @@ import { import { Link } from '@app/ui/components/link/link'; function TransactionRequestBase() { + const [isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation] = useState(false); + const transactionRequest = useTransactionRequestState(); const unsignedTx = useUnsignedStacksTransactionBaseState(); const { data: stxFees } = useCalculateStacksTxFees(unsignedTx.transaction); @@ -52,8 +52,6 @@ function TransactionRequestBase() { const navigate = useNavigate(); const { stacksBroadcastTransaction } = useStacksBroadcastTransaction('STX'); - useRouteHeader(); - useOnMount(() => void analytics.track('view_transaction_signing')); async function onSubmit( @@ -93,7 +91,13 @@ function TransactionRequestBase() { }; return ( - + - - + setIsShowingHighFeeConfirmation(true)} + /> + + )} diff --git a/src/app/pages/unlock.tsx b/src/app/pages/unlock.tsx index 71135790040..8906f5f5b98 100644 --- a/src/app/pages/unlock.tsx +++ b/src/app/pages/unlock.tsx @@ -1,56 +1,17 @@ -import { useEffect } from 'react'; import { Outlet, useNavigate } from 'react-router-dom'; -import { Box } from 'leather-styles/jsx'; - -import { WALLET_ENVIRONMENT } from '@shared/environment'; import { RouteUrls } from '@shared/route-urls'; -import { closeWindow } from '@shared/utils'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { isFullPageMode, isPopupMode } from '@app/common/utils'; -import { openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab'; -import { Header } from '@app/components/header'; import { RequestPassword } from '@app/components/request-password'; -import { useNewBrandApprover } from '@app/store/settings/settings.selectors'; export function Unlock() { const navigate = useNavigate(); - useRouteHeader(
    ); - - const { hasApprovedNewBrand } = useNewBrandApprover(); - - useEffect(() => { - if (!hasApprovedNewBrand && isPopupMode() && WALLET_ENVIRONMENT !== 'testing') { - openIndexPageInNewTab('/unlock/we-have-a-new-name'); - closeWindow(); - } - if (!hasApprovedNewBrand && isFullPageMode()) { - navigate('./we-have-a-new-name'); - } - }, [hasApprovedNewBrand, navigate]); - const handleSuccess = () => navigate(RouteUrls.Home); return ( <> - {/* Hide the logo when user hasn't consented yet */} - {!hasApprovedNewBrand && ( - - )} - - - Your -
    - session is locked - - } - caption="Enter the password you set on this device" - onSuccess={handleSuccess} - /> + ); diff --git a/src/app/pages/update-profile-request/components/update-profile-request.layout.tsx b/src/app/pages/update-profile-request/components/update-profile-request.layout.tsx index 13415723fa8..a8955e1f6d8 100644 --- a/src/app/pages/update-profile-request/components/update-profile-request.layout.tsx +++ b/src/app/pages/update-profile-request/components/update-profile-request.layout.tsx @@ -7,7 +7,12 @@ interface ProfileUpdateRequestLayoutProps { } export function ProfileUpdateRequestLayout({ children }: ProfileUpdateRequestLayoutProps) { return ( - + {children} diff --git a/src/app/pages/update-profile-request/update-profile-request.tsx b/src/app/pages/update-profile-request/update-profile-request.tsx index 44602f339bf..3369a3f4440 100644 --- a/src/app/pages/update-profile-request/update-profile-request.tsx +++ b/src/app/pages/update-profile-request/update-profile-request.tsx @@ -2,8 +2,6 @@ import { memo } from 'react'; import { closeWindow, isUndefined } from '@shared/utils'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { PopupHeader } from '@app/features/current-account/popup-header'; import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; import { useIsProfileUpdateRequestValid, @@ -18,8 +16,6 @@ function ProfileUpdateRequestBase() { const validProfileUpdateRequest = useIsProfileUpdateRequestValid(); const { requestToken } = useProfileUpdateRequestSearchParams(); - useRouteHeader(); - useOnOriginTabClose(() => closeWindow()); if (isUndefined(validProfileUpdateRequest) || !validProfileUpdateRequest || !requestToken) return ( diff --git a/src/app/pages/view-secret-key/components/view-secret-key.content.tsx b/src/app/pages/view-secret-key/components/view-secret-key.content.tsx deleted file mode 100644 index ea124ff4a10..00000000000 --- a/src/app/pages/view-secret-key/components/view-secret-key.content.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { styled } from 'leather-styles/jsx'; - -export function ViewSecretKeyContent(): React.JSX.Element { - return ( - <> - - Your
    Secret Key -
    - - These 24 words are your Secret Key. They create your account, and you sign in on different - devices with them. Make sure to save these somewhere safe. - - -
    - - If you lose these words, you lose your account. - - - ); -} diff --git a/src/app/pages/view-secret-key/view-secret-key.tsx b/src/app/pages/view-secret-key/view-secret-key.tsx index 5921326d62b..f2e0684848d 100644 --- a/src/app/pages/view-secret-key/view-secret-key.tsx +++ b/src/app/pages/view-secret-key/view-secret-key.tsx @@ -1,26 +1,17 @@ import { useEffect, useState } from 'react'; -import { Outlet, useNavigate } from 'react-router-dom'; - -import { RouteUrls } from '@shared/route-urls'; +import { Outlet } from 'react-router-dom'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { Header } from '@app/components/header'; import { RequestPassword } from '@app/components/request-password'; -import { TwoColumnLayout } from '@app/components/secret-key/two-column.layout'; -import { SecretKeyDisplayer } from '@app/features/secret-key-displayer/secret-key-displayer'; +import { SecretKey } from '@app/features/secret-key-displayer/secret-key-displayer'; import { useDefaultWalletSecretKey } from '@app/store/in-memory-key/in-memory-key.selectors'; - -import { ViewSecretKeyContent } from './components/view-secret-key.content'; +import { TwoColumnLayout } from '@app/ui/pages/two-column.layout'; export function ViewSecretKey() { const analytics = useAnalytics(); - const navigate = useNavigate(); const defaultWalletSecretKey = useDefaultWalletSecretKey(); const [showSecretKey, setShowSecretKey] = useState(false); - useRouteHeader(
    navigate(RouteUrls.Home)} />); - useEffect(() => { void analytics.page('view', '/save-secret-key'); }, [analytics]); @@ -28,25 +19,28 @@ export function ViewSecretKey() { if (showSecretKey) { return ( } - rightColumn={} - /> - ); - } - - return ( - <> - - View + Your
    Secret Key + + } + content={ + <> + These 24 words are your Secret Key. They create your account, and you sign in on + different devices with them. Make sure to save these somewhere safe.
    - Secret Key + If you lose these words, you lose your account. } - caption="Enter the password you set on this device" - onSuccess={() => setShowSecretKey(true)} - /> + > + +
    + ); + } + + return ( + <> + setShowSecretKey(true)} /> ); diff --git a/src/app/routes/app-routes.tsx b/src/app/routes/app-routes.tsx index 2c397351a92..efb89d692c2 100644 --- a/src/app/routes/app-routes.tsx +++ b/src/app/routes/app-routes.tsx @@ -14,11 +14,11 @@ import { RouteUrls } from '@shared/route-urls'; import { LoadingSpinner } from '@app/components/loading-spinner'; import { AddNetwork } from '@app/features/add-network/add-network'; import { Container } from '@app/features/container/container'; -import { EditNonceDrawer } from '@app/features/edit-nonce-drawer/edit-nonce-drawer'; -import { IncreaseBtcFeeDrawer } from '@app/features/increase-fee-drawer/increase-btc-fee-drawer'; -import { IncreaseFeeSentDrawer } from '@app/features/increase-fee-drawer/increase-fee-sent-drawer'; -import { IncreaseStxFeeDrawer } from '@app/features/increase-fee-drawer/increase-stx-fee-drawer'; -import { leatherIntroDialogRoutes } from '@app/features/leather-intro-dialog/leather-intro-dialog'; +import { EditNonceDialog } from '@app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog'; +import { IncreaseBtcFeeDialog } from '@app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog'; +import { IncreaseFeeSentDialog } from '@app/features/dialogs/increase-fee-dialog/increase-fee-sent-dialog'; +import { IncreaseStxFeeDialog } from '@app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog'; +import { leatherIntroDialogRoutes } from '@app/features/dialogs/leather-intro-dialog/leather-intro-dialog'; import { ledgerBitcoinTxSigningRoutes } from '@app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container'; import { ledgerJwtSigningRoutes } from '@app/features/ledger/flows/jwt-signing/ledger-sign-jwt.routes'; import { requestBitcoinKeysRoutes } from '@app/features/ledger/flows/request-bitcoin-keys/ledger-request-bitcoin-keys'; @@ -53,7 +53,6 @@ import { AccountGate } from '@app/routes/account-gate'; import { receiveRoutes } from '@app/routes/receive-routes'; import { legacyRequestRoutes } from '@app/routes/request-routes'; import { rpcRequestRoutes } from '@app/routes/rpc-routes'; -import { settingsRoutes } from '@app/routes/settings-routes'; import { OnboardingGate } from './onboarding-gate'; @@ -70,7 +69,6 @@ const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouter(createHashRoute export const homePageModalRoutes = ( <> - {settingsRoutes} {receiveRoutes} {ledgerStacksTxSigningRoutes} {ledgerBitcoinTxSigningRoutes} @@ -96,13 +94,17 @@ function useAppRoutes() { } /> - }> + }> {ledgerStacksTxSigningRoutes} - }> + } + /> + }> {ledgerBitcoinTxSigningRoutes} - } /> + } /> {ledgerStacksTxSigningRoutes} @@ -187,8 +189,6 @@ function useAppRoutes() { } > - {settingsRoutes} - } /> } /> @@ -200,7 +200,6 @@ function useAppRoutes() { } > - {settingsRoutes} } /> @@ -213,11 +212,8 @@ function useAppRoutes() { } - > - {settingsRoutes} - + /> }> - {settingsRoutes} {leatherIntroDialogRoutes} @@ -241,7 +237,7 @@ function useAppRoutes() { } > - } /> + } /> - } /> + } /> } /> } /> } /> - } /> + } /> } /> ); diff --git a/src/app/routes/request-routes.tsx b/src/app/routes/request-routes.tsx index 1b0e931009b..1cd1886af83 100644 --- a/src/app/routes/request-routes.tsx +++ b/src/app/routes/request-routes.tsx @@ -3,8 +3,8 @@ import { Route } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; -import { BroadcastErrorDrawer } from '@app/components/broadcast-error-drawer/broadcast-error-drawer'; -import { EditNonceDrawer } from '@app/features/edit-nonce-drawer/edit-nonce-drawer'; +import { BroadcastErrorDialog } from '@app/components/broadcast-error-dialog/broadcast-error-dialog'; +import { EditNonceDialog } from '@app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog'; import { ledgerStacksMessageSigningRoutes } from '@app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg.routes'; import { ledgerStacksTxSigningRoutes } from '@app/features/ledger/flows/stacks-tx-signing/ledger-sign-stacks-tx-container'; import { PsbtRequest } from '@app/pages/psbt-request/psbt-request'; @@ -27,8 +27,8 @@ export const legacyRequestRoutes = ( } > {ledgerStacksTxSigningRoutes} - } /> - } /> + } /> + } /> - } /> - } /> - } /> - -); diff --git a/src/app/store/settings/settings.selectors.ts b/src/app/store/settings/settings.selectors.ts index 34862abfb69..4103be10dda 100644 --- a/src/app/store/settings/settings.selectors.ts +++ b/src/app/store/settings/settings.selectors.ts @@ -2,9 +2,7 @@ import { useSelector } from 'react-redux'; import { createSelector } from '@reduxjs/toolkit'; -import { RootState, useAppDispatch } from '@app/store'; - -import { settingsSlice } from './settings.slice'; +import { RootState } from '@app/store'; const selectSettings = (state: RootState) => state.settings; @@ -40,19 +38,3 @@ const selectDismissedMessageIds = createSelector( export function useDismissedMessageIds() { return useSelector(selectDismissedMessageIds); } - -const selectHasApprovedNewBrand = createSelector( - selectSettings, - state => !!state.hasApprovedNewBrand -); - -export function useNewBrandApprover() { - const hasApprovedNewBrand = useSelector(selectHasApprovedNewBrand); - const dispatch = useAppDispatch(); - return { - hasApprovedNewBrand, - userApprovedNewBrand() { - dispatch(settingsSlice.actions.setHasApprovedNewBrand()); - }, - }; -} diff --git a/src/app/store/settings/settings.slice.ts b/src/app/store/settings/settings.slice.ts index be0960dca6a..36105a6b890 100644 --- a/src/app/store/settings/settings.slice.ts +++ b/src/app/store/settings/settings.slice.ts @@ -7,15 +7,12 @@ interface InitialState { userSelectedTheme: UserSelectedTheme; hasAllowedAnalytics: HasAcceptedAnalytics; dismissedMessages: string[]; - hasApprovedNewBrand: boolean; } const initialState: InitialState = { userSelectedTheme: 'system', hasAllowedAnalytics: null, dismissedMessages: [], - // Defaults to true, as this is undefined for existing users - hasApprovedNewBrand: true, }; export const settingsSlice = createSlice({ @@ -35,11 +32,5 @@ export const settingsSlice = createSlice({ resetMessages(state) { state.dismissedMessages = []; }, - setHasApprovedNewBrand(state) { - state.hasApprovedNewBrand = true; - }, - resetHasApprovedNewBrand(state) { - state.hasApprovedNewBrand = false; - }, }, }); diff --git a/src/app/store/ui/ui.hooks.ts b/src/app/store/ui/ui.hooks.ts index e8344c96800..bde03bc5c23 100644 --- a/src/app/store/ui/ui.hooks.ts +++ b/src/app/store/ui/ui.hooks.ts @@ -1,30 +1,6 @@ import { useAtom } from 'jotai'; -import { - errorStackTraceState, - loadingState, - routeHeaderState, - showHighFeeConfirmationState, - showSettingsStore, - showSwitchAccountsState, - showTxSettingsCallback, -} from './ui'; - -export function useShowHighFeeConfirmationState() { - return useAtom(showHighFeeConfirmationState); -} - -export function useShowSwitchAccountsState() { - return useAtom(showSwitchAccountsState); -} - -export function useShowSettingsStore() { - return useAtom(showSettingsStore); -} - -export function useShowTxSettingsCallback() { - return useAtom(showTxSettingsCallback); -} +import { errorStackTraceState, loadingState } from './ui'; export function useLoadingState(key: string) { return useAtom(loadingState(key)); @@ -33,7 +9,3 @@ export function useLoadingState(key: string) { export function useErrorStackTraceState() { return useAtom(errorStackTraceState); } - -export function useRouteHeaderState() { - return useAtom(routeHeaderState); -} diff --git a/src/app/store/ui/ui.ts b/src/app/store/ui/ui.ts index bbcd2eaa5f6..ea1ad46c9c9 100644 --- a/src/app/store/ui/ui.ts +++ b/src/app/store/ui/ui.ts @@ -9,15 +9,4 @@ export const loadingState = atomFamily(_param => { return anAtom; }); -// TODO: refactor into atom family -export const showSwitchAccountsState = atom(false); - -export const showHighFeeConfirmationState = atom(false); - -export const showSettingsStore = atom(false); - -export const showTxSettingsCallback = atom<(() => Promise) | undefined>(undefined); - export const errorStackTraceState = atom(null); - -export const routeHeaderState = atom(null); diff --git a/src/app/components/account/account-avatar-item.tsx b/src/app/ui/components/account/account-avatar/account-avatar-item.tsx similarity index 76% rename from src/app/components/account/account-avatar-item.tsx rename to src/app/ui/components/account/account-avatar/account-avatar-item.tsx index a6470aca3de..f0b4ecdfa59 100644 --- a/src/app/components/account/account-avatar-item.tsx +++ b/src/app/ui/components/account/account-avatar/account-avatar-item.tsx @@ -1,6 +1,6 @@ import { memo } from 'react'; -import { AccountAvatar } from '@app/components/account/account-avatar'; +import { AccountAvatar } from '@app/ui/components/account/account-avatar/account-avatar'; interface AccountAvatarItemProps { publicKey: string; diff --git a/src/app/components/account/account-avatar.tsx b/src/app/ui/components/account/account-avatar/account-avatar.tsx similarity index 100% rename from src/app/components/account/account-avatar.tsx rename to src/app/ui/components/account/account-avatar/account-avatar.tsx diff --git a/src/app/ui/components/account/account.card.stories.tsx b/src/app/ui/components/account/account.card.stories.tsx new file mode 100644 index 00000000000..8a47ba3c4e8 --- /dev/null +++ b/src/app/ui/components/account/account.card.stories.tsx @@ -0,0 +1,33 @@ +import type { Meta } from '@storybook/react'; +import { Flex } from 'leather-styles/jsx'; + +import { IconButton } from '@app/ui/components/icon-button/icon-button'; +import { ArrowDownIcon, ArrowUpIcon, PlusIcon, SwapIcon } from '@app/ui/icons'; + +import { AccountCard as Component } from './account.card'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Layout/AccountCard', +}; + +export default meta; + +export function AccountCard() { + return ( + } + toggleSwitchAccount={() => null} + > + + } label="Send" /> + } label="Receive" /> + } label="Buy" /> + } label="Swap" /> + + + ); +} diff --git a/src/app/ui/components/account/account.card.tsx b/src/app/ui/components/account/account.card.tsx new file mode 100644 index 00000000000..8508f21cfb9 --- /dev/null +++ b/src/app/ui/components/account/account.card.tsx @@ -0,0 +1,57 @@ +import { ReactNode } from 'react'; + +import { SettingsSelectors } from '@tests/selectors/settings.selectors'; +import { Box, Flex, styled } from 'leather-styles/jsx'; + +import { Link } from '@app/ui/components/link/link'; +import { ChevronDownIcon } from '@app/ui/icons'; + +interface AccountCardProps { + name: string; + balance: string; + children: ReactNode; + switchAccount: ReactNode; + toggleSwitchAccount(): void; +} + +export function AccountCard({ + name, + balance, + switchAccount, + toggleSwitchAccount, + children, +}: AccountCardProps) { + return ( + + + + + {name} + + + + + + + + + {balance} + + {switchAccount} + {children} + + + ); +} diff --git a/src/app/ui/components/bullet-separator/bullet-separator.tsx b/src/app/ui/components/bullet-separator/bullet-separator.tsx index 9d6c7f1213e..a403ed1e79e 100644 --- a/src/app/ui/components/bullet-separator/bullet-separator.tsx +++ b/src/app/ui/components/bullet-separator/bullet-separator.tsx @@ -7,7 +7,7 @@ export function BulletOperator(props: CircleProps) { , keyof ButtonVariantProps> & +export type ButtonProps = Omit< + React.ComponentProps, + keyof ButtonVariantProps +> & ButtonVariantProps; export function Button(props: ButtonProps) { - const { children, fullWidth, invert, size, trigger, type = 'button', variant, ...rest } = props; + const { children, fullWidth, size, trigger, invert, type = 'button', variant, ...rest } = props; return ( diff --git a/src/app/ui/components/callout/callout.tsx b/src/app/ui/components/callout/callout.tsx index 7ba98707b97..80d5311c75a 100644 --- a/src/app/ui/components/callout/callout.tsx +++ b/src/app/ui/components/callout/callout.tsx @@ -60,7 +60,7 @@ export function Callout(props: CalloutProps & CalloutVariants) { {title} )} - {children && {children}} + {children && {children}} diff --git a/src/app/ui/components/containers/container.layout.tsx b/src/app/ui/components/containers/container.layout.tsx new file mode 100644 index 00000000000..147233e5b8f --- /dev/null +++ b/src/app/ui/components/containers/container.layout.tsx @@ -0,0 +1,25 @@ +import { radixBaseCSS } from '@radix-ui/themes/styles.css'; +import { css } from 'leather-styles/css'; +import { Flex } from 'leather-styles/jsx'; + +interface ContainerLayoutProps { + children: React.JSX.Element | React.JSX.Element[]; + header: React.JSX.Element | null; +} +export function ContainerLayout({ children, header }: ContainerLayoutProps) { + return ( + + {header} + + {children} + + + ); +} diff --git a/src/app/ui/components/containers/dialog/dialog.stories.tsx b/src/app/ui/components/containers/dialog/dialog.stories.tsx new file mode 100644 index 00000000000..dbbb86e3770 --- /dev/null +++ b/src/app/ui/components/containers/dialog/dialog.stories.tsx @@ -0,0 +1,26 @@ +import { useState } from 'react'; + +import type { Meta } from '@storybook/react'; + +import { Button } from '../../button/button'; +import { Dialog as Component } from './dialog'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Containers/Dialog', +}; + +export default meta; + +export function Dialog() { + const [isShowing, setIsShowing] = useState(false); + return ( + <> + + setIsShowing(false)}> +

    Some Dialog

    +
    + + ); +} diff --git a/src/app/ui/components/containers/dialog/dialog.tsx b/src/app/ui/components/containers/dialog/dialog.tsx new file mode 100644 index 00000000000..980b5baf8ba --- /dev/null +++ b/src/app/ui/components/containers/dialog/dialog.tsx @@ -0,0 +1,87 @@ +import { JSXElementConstructor, ReactElement, ReactNode, cloneElement } from 'react'; + +import * as RadixDialog from '@radix-ui/react-dialog'; +import { css } from 'leather-styles/css'; +import { Box } from 'leather-styles/jsx'; +import { token } from 'leather-styles/tokens'; + +import { pxStringToNumber } from '@shared/utils/px-string-to-number'; + +export interface DialogProps { + isShowing: boolean; + onClose(): void; +} +interface RadixDialogProps extends DialogProps { + children: ReactNode; + footer?: ReactNode; + header?: ReactElement>; + onGoBack?(): void; +} + +export function getHeightOffset(header: ReactNode, footer: ReactNode) { + const headerHeight = header ? pxStringToNumber(token('sizes.headerHeight')) : 0; + const footerHeight = footer ? pxStringToNumber(token('sizes.footerHeight')) : 0; + return headerHeight + footerHeight; +} + +function getContentMaxHeight(maxHeightOffset: number) { + const virtualHeight = window.innerWidth <= pxStringToNumber(token('sizes.popupWidth')) ? 100 : 70; + + return `calc(${virtualHeight}vh - ${maxHeightOffset}px)`; +} + +export function Dialog({ children, footer, header, onClose, isShowing }: RadixDialogProps) { + if (!isShowing) return null; + + const maxHeightOffset = getHeightOffset(header, footer); + const contentMaxHeight = getContentMaxHeight(maxHeightOffset); + + return ( + + + + + {header && cloneElement(header, { onClose })} + + + {children} + + {footer} + + + + + ); +} diff --git a/src/app/components/available-balance.tsx b/src/app/ui/components/containers/footers/available-balance.tsx similarity index 62% rename from src/app/components/available-balance.tsx rename to src/app/ui/components/containers/footers/available-balance.tsx index 864b7ef15a8..37d9041d638 100644 --- a/src/app/components/available-balance.tsx +++ b/src/app/ui/components/containers/footers/available-balance.tsx @@ -1,17 +1,17 @@ import { Box, Flex, HStack, styled } from 'leather-styles/jsx'; -import { Money } from '@shared/models/money.model'; - -import { formatMoney } from '@app/common/money/format-money'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; import { InfoCircleIcon } from '@app/ui/icons/info-circle-icon'; -export function AvailableBalance(props: { balance: Money; balanceTooltipLabel?: string }) { - const { - balance, - balanceTooltipLabel = 'Amount that is immediately available for use after taking into account any pending transactions or holds placed on your account by the protocol.', - } = props; +interface AvailableBalanceProps { + balance: string; + balanceTooltipLabel?: string; +} +export function AvailableBalance({ + balance, + balanceTooltipLabel = 'Amount that is immediately available for use after taking into account any pending transactions or holds placed on your account by the protocol.', +}: AvailableBalanceProps) { return ( @@ -25,7 +25,7 @@ export function AvailableBalance(props: { balance: Money; balanceTooltipLabel?: - {formatMoney(balance)} + {balance} ); diff --git a/src/app/ui/components/containers/footers/footer.stories.tsx b/src/app/ui/components/containers/footers/footer.stories.tsx new file mode 100644 index 00000000000..61561d0eec0 --- /dev/null +++ b/src/app/ui/components/containers/footers/footer.stories.tsx @@ -0,0 +1,134 @@ +import type { Meta } from '@storybook/react'; +import { Flex, styled } from 'leather-styles/jsx'; + +import { Button } from '@app/ui/components/button/button'; + +import { Link } from '../../link/link'; +import { Footer as Component } from './footer'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Containers/Footer', + parameters: { + controls: { + disable: true, + // TODO try get rid of these empty controls + // https://github.com/storybookjs/storybook/issues/24422 + hideNoControlsWarning: true, + }, + }, +}; + +export default meta; + +export function Footer() { + return ( + + + + ); +} + +export function SignOutConfirmFooter() { + return ( + + + + + + + ); +} + +export function ReceiveTokensFooter() { + return ( + + + + ); +} + +export function RequestPasswordFooter() { + return ( + + + + ); +} + +export function FooterWithText() { + return ( + + + + + + Leather Wallet will now be provided by Leather Wallet LLC [a subsidiary of Nassau Machines + Inc]. Please review and accept Leather Wallet{' '} + + Terms of Service + {' '} + and{' '} + + Privacy Policy + + . + + + + ); +} + +export function FooterWithLink() { + return ( + + + + + + View all addresses + + + + ); +} + +export function FooterWithBalancesAbove() { + return ( + + + 0.00048208 BTC + $ 1,100.00 + + + + ); +} diff --git a/src/app/ui/components/containers/footers/footer.tsx b/src/app/ui/components/containers/footers/footer.tsx new file mode 100644 index 00000000000..8d8f315997e --- /dev/null +++ b/src/app/ui/components/containers/footers/footer.tsx @@ -0,0 +1,31 @@ +import type { ReactNode } from 'react'; + +import { Flex, styled } from 'leather-styles/jsx'; + +interface FooterProps { + children: ReactNode; + variant?: 'page' | 'card'; + flexDirection?: 'column' | 'row'; +} + +export function Footer({ children, variant = 'page', flexDirection = 'column' }: FooterProps) { + return ( + + + {children} + + + ); +} diff --git a/src/app/ui/components/containers/headers/components/big-title-header.tsx b/src/app/ui/components/containers/headers/components/big-title-header.tsx new file mode 100644 index 00000000000..8f8e759aa4e --- /dev/null +++ b/src/app/ui/components/containers/headers/components/big-title-header.tsx @@ -0,0 +1,28 @@ +import { ReactNode } from 'react'; + +import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; +import { Flex, styled } from 'leather-styles/jsx'; + +import { CloseIcon } from '@app/ui/icons'; + +import { HeaderActionButton } from './header-action-button'; + +interface BigTitleHeaderProps { + onClose?(): void; + title?: ReactNode; +} + +export function BigTitleHeader({ onClose, title }: BigTitleHeaderProps) { + return ( + + {title} + {onClose && ( + } + dataTestId={SharedComponentsSelectors.HeaderCloseBtn} + onAction={onClose} + /> + )} + + ); +} diff --git a/src/app/ui/components/containers/headers/components/header-action-button.tsx b/src/app/ui/components/containers/headers/components/header-action-button.tsx new file mode 100644 index 00000000000..cb966581f54 --- /dev/null +++ b/src/app/ui/components/containers/headers/components/header-action-button.tsx @@ -0,0 +1,34 @@ +import { FlexProps } from 'leather-styles/jsx'; + +import { IconButton } from '../../../icon-button/icon-button'; + +interface HeaderActionButtonProps extends FlexProps { + icon: React.JSX.Element; + isWaitingOnPerformedAction?: boolean; + onAction?(): void; + dataTestId: string; +} +export function HeaderActionButton({ + icon, + isWaitingOnPerformedAction, + onAction, + dataTestId, +}: HeaderActionButtonProps) { + return ( + + ); +} diff --git a/src/app/ui/components/containers/headers/components/network-mode-badge.tsx b/src/app/ui/components/containers/headers/components/network-mode-badge.tsx new file mode 100644 index 00000000000..539e02b928e --- /dev/null +++ b/src/app/ui/components/containers/headers/components/network-mode-badge.tsx @@ -0,0 +1,18 @@ +import { Flex } from 'leather-styles/jsx'; + +import { Tag } from '@app/ui/components/tag/tag'; + +interface NetworkModeBadge { + isTestnetChain: boolean; + name: string; +} + +export function NetworkModeBadge({ isTestnetChain, name }: NetworkModeBadge) { + if (!isTestnetChain) return null; + + return ( + + + + ); +} diff --git a/src/app/ui/components/containers/headers/header.stories.tsx b/src/app/ui/components/containers/headers/header.stories.tsx new file mode 100644 index 00000000000..d6b48b0b883 --- /dev/null +++ b/src/app/ui/components/containers/headers/header.stories.tsx @@ -0,0 +1,22 @@ +import type { Meta } from '@storybook/react'; + +import { Header as Component, HeaderProps } from './header'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Containers/Header', + args: { + variant: 'home', + }, +}; + +export default meta; + +export function Header(args: HeaderProps) { + return null} onGoBack={() => null} />; +} + +export function PageHeader(args: HeaderProps) { + return null} />; +} diff --git a/src/app/ui/components/containers/headers/header.tsx b/src/app/ui/components/containers/headers/header.tsx new file mode 100644 index 00000000000..fc8e88e7860 --- /dev/null +++ b/src/app/ui/components/containers/headers/header.tsx @@ -0,0 +1,93 @@ +import { ReactNode } from 'react'; + +import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; +import { Flex, Grid, GridItem, HStack, styled } from 'leather-styles/jsx'; + +import { ArrowLeftIcon, CloseIcon } from '@app/ui/icons'; + +import { BigTitleHeader } from './components/big-title-header'; +import { HeaderActionButton } from './components/header-action-button'; + +type HeaderVariants = 'page' | 'home' | 'onboarding' | 'dialog' | 'bigTitle' | 'fund'; + +export interface HeaderProps { + variant: HeaderVariants; + isWaitingOnPerformedAction?: boolean; + onClose?(): void; + onGoBack?(): void; + title?: ReactNode; + account?: ReactNode; + totalBalance?: ReactNode; + settingsMenu?: ReactNode; + networkBadge?: ReactNode; + logo?: ReactNode; +} + +export function Header({ + variant, + isWaitingOnPerformedAction, + onClose, + onGoBack, + account, + totalBalance, + settingsMenu, + networkBadge, + title, + logo, +}: HeaderProps) { + const logoItem = onGoBack || logo || account; + + return ( + + + + {logoItem && ( + + {variant !== 'home' && onGoBack ? ( + } + isWaitingOnPerformedAction={isWaitingOnPerformedAction} + onAction={onGoBack} + dataTestId={SharedComponentsSelectors.HeaderBackBtn} + /> + ) : undefined} + {account ? account : logo} + + )} + + + {title && {title}} + + + + {networkBadge} + {totalBalance && totalBalance} + {settingsMenu} + {variant !== 'bigTitle' && onClose && ( + } + dataTestId={SharedComponentsSelectors.HeaderCloseBtn} + isWaitingOnPerformedAction={isWaitingOnPerformedAction} + onAction={onClose} + /> + )} + + + + {variant === 'bigTitle' && } + + ); +} diff --git a/src/app/ui/components/dowpdown-menu/dropdown-menu-item.layout.tsx b/src/app/ui/components/dropdown-menu/dropdown-menu-item.layout.tsx similarity index 100% rename from src/app/ui/components/dowpdown-menu/dropdown-menu-item.layout.tsx rename to src/app/ui/components/dropdown-menu/dropdown-menu-item.layout.tsx diff --git a/src/app/ui/components/dowpdown-menu/dropdown-menu.stories.tsx b/src/app/ui/components/dropdown-menu/dropdown-menu.stories.tsx similarity index 100% rename from src/app/ui/components/dowpdown-menu/dropdown-menu.stories.tsx rename to src/app/ui/components/dropdown-menu/dropdown-menu.stories.tsx diff --git a/src/app/ui/components/dowpdown-menu/dropdown-menu.tsx b/src/app/ui/components/dropdown-menu/dropdown-menu.tsx similarity index 74% rename from src/app/ui/components/dowpdown-menu/dropdown-menu.tsx rename to src/app/ui/components/dropdown-menu/dropdown-menu.tsx index 1ba3e52013f..edf39d6a5bf 100644 --- a/src/app/ui/components/dowpdown-menu/dropdown-menu.tsx +++ b/src/app/ui/components/dropdown-menu/dropdown-menu.tsx @@ -26,6 +26,7 @@ const dropdownButtonStyles = css({ userSelect: 'none', '[data-state=open] &': { bg: 'ink.component-background-pressed' }, }); + function Button({ children, ...props }: HTMLStyledProps<'div'>) { return ( @@ -36,9 +37,21 @@ function Button({ children, ...props }: HTMLStyledProps<'div'>) { ); } +const dropdownIconButtonStyles = css({ + _hover: { bg: 'ink.component-background-hover' }, + _focus: { outline: 'none' }, + p: 'space.02', + + '&[data-state=open]': { bg: 'ink.component-background-pressed' }, +}); +const IconButton: typeof RadixDropdownMenu.Trigger = forwardRef((props, ref) => ( + +)); + const dropdownTriggerStyles = css({ _focus: { outline: 'none' }, }); + const Trigger: typeof RadixDropdownMenu.Trigger = forwardRef((props, ref) => ( )); @@ -50,14 +63,18 @@ const dropdownContentStyles = css({ borderRadius: 'xs', boxShadow: '0px 12px 24px 0px rgba(18, 16, 15, 0.08), 0px 4px 8px 0px rgba(18, 16, 15, 0.08), 0px 0px 2px 0px rgba(18, 16, 15, 0.08)', - p: 'space.02', + p: '0', willChange: 'transform, opacity', zIndex: 999, - _closed: { animation: 'slideDownAndOut 140ms ease-in-out' }, - _open: { animation: 'slideUpAndFade 140ms ease-in-out' }, + // _closed: { animation: 'slideDownAndOut 140ms ease-in-out' }, + // _open: { animation: 'slideUpAndFade 140ms ease-in-out' }, }); -const Content: typeof RadixDropdownMenu.Content = forwardRef((props, ref) => ( - +const Content: typeof RadixDropdownMenu.Content = forwardRef(({ className, ...props }, ref) => ( + )); const dropdownMenuLabelStyles = css({ @@ -92,13 +109,20 @@ const dropdownMenuSeparatorStyles = css({ const Separator: typeof RadixDropdownMenu.Separator = forwardRef((props, ref) => ( )); +const dropdownMenuGroupStyles = css({ + p: 'space.02', +}); +const Group: typeof RadixDropdownMenu.Group = forwardRef((props, ref) => ( + +)); export const DropdownMenu = { Root: RadixDropdownMenu.Root, - Group: RadixDropdownMenu.Group, + Group, Portal: RadixDropdownMenu.Portal, Trigger, Button, + IconButton, Content, Label, Item, diff --git a/src/app/pages/home/components/accessible-icon.tsx b/src/app/ui/components/icon-button/accessible-icon.tsx similarity index 65% rename from src/app/pages/home/components/accessible-icon.tsx rename to src/app/ui/components/icon-button/accessible-icon.tsx index b7e9aaee2db..1c1a6cc5694 100644 --- a/src/app/pages/home/components/accessible-icon.tsx +++ b/src/app/ui/components/icon-button/accessible-icon.tsx @@ -1,9 +1,7 @@ -import React from 'react'; - import * as AccessibleIconPrimitive from '@radix-ui/react-accessible-icon'; type Props = AccessibleIconPrimitive.AccessibleIconProps; -export default function AccessibleIcon(props: Props): React.ReactElement { +export default function AccessibleIcon(props: Props) { return ; } diff --git a/src/app/ui/components/icon-button/icon-button.stories.tsx b/src/app/ui/components/icon-button/icon-button.stories.tsx new file mode 100644 index 00000000000..e5e1d392420 --- /dev/null +++ b/src/app/ui/components/icon-button/icon-button.stories.tsx @@ -0,0 +1,30 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { PlaceholderIcon, SendIcon } from '@app/ui/icons'; + +import { IconButton as Component } from './icon-button'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Icon Button', + parameters: { + controls: { include: [] }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const IconButton: Story = { + args: { + icon: , + }, +}; + +export const WithLabel: Story = { + args: { + icon: , + label: 'Send', + }, +}; diff --git a/src/app/ui/components/icon-button/icon-button.tsx b/src/app/ui/components/icon-button/icon-button.tsx new file mode 100644 index 00000000000..318554254ff --- /dev/null +++ b/src/app/ui/components/icon-button/icon-button.tsx @@ -0,0 +1,31 @@ +import { Stack, styled } from 'leather-styles/jsx'; + +import { Button, type ButtonProps } from '@app/ui/components/button/button'; +import AccessibleIcon from '@app/ui/components/icon-button/accessible-icon'; + +interface IconButtonProps extends ButtonProps { + icon: React.JSX.Element; + label?: string; +} +export function IconButton({ icon, label, ...rest }: IconButtonProps) { + return ( + + ); +} diff --git a/src/app/ui/components/item-layout/item-layout.tsx b/src/app/ui/components/item-layout/item-layout.tsx index 9080292d5e3..7adca289ea7 100644 --- a/src/app/ui/components/item-layout/item-layout.tsx +++ b/src/app/ui/components/item-layout/item-layout.tsx @@ -1,6 +1,6 @@ import { ReactNode, isValidElement } from 'react'; -import { Flex, HStack, Stack, styled } from 'leather-styles/jsx'; +import { Box, Flex, HStack, Stack, styled } from 'leather-styles/jsx'; import { pressableCaptionStyles, pressableChevronStyles } from '@app/ui/pressable/pressable'; @@ -42,11 +42,13 @@ export function ItemLayout({ {isValidElement(titleLeft) ? ( titleLeft ) : ( - - {titleLeft} - + {titleLeft} + )} + {isSelected && ( + + + )} - {isSelected && } {isValidElement(captionLeft) ? ( captionLeft @@ -68,7 +70,7 @@ export function ItemLayout({ {isValidElement(captionRight) ? ( captionRight ) : ( - + {captionRight} )} diff --git a/src/app/ui/components/link/link.stories.tsx b/src/app/ui/components/link/link.stories.tsx index 8048b8a5091..1baf4cfdf65 100644 --- a/src/app/ui/components/link/link.stories.tsx +++ b/src/app/ui/components/link/link.stories.tsx @@ -33,17 +33,3 @@ export const Disabled: Story = { variant: 'underlined', }, }; - -// TODO: Remove invert code -export const InvertLink: Story = { - parameters: { - backgrounds: { default: 'leather-dark-mode' }, - controls: { include: [] }, - }, - args: { - children: 'Link', - invert: true, - size: 'md', - variant: 'underlined', - }, -}; diff --git a/src/app/ui/components/link/link.tsx b/src/app/ui/components/link/link.tsx index 6890f139d90..bcbbd902449 100644 --- a/src/app/ui/components/link/link.tsx +++ b/src/app/ui/components/link/link.tsx @@ -9,7 +9,7 @@ type LinkProps = Omit, keyof LinkVariant LinkVariantProps; export const Link = forwardRef((props: LinkProps, ref: ForwardedRef) => { - const { children, disabled, fullWidth, invert, size, variant, ...rest } = props; + const { children, disabled, fullWidth, size, invert, variant, ...rest } = props; return ( + + + ); +} diff --git a/src/app/components/secret-key/mnemonic-key/mnemonic-word-input.tsx b/src/app/ui/components/secret-key/mnemonic-key/mnemonic-word-input.tsx similarity index 100% rename from src/app/components/secret-key/mnemonic-key/mnemonic-word-input.tsx rename to src/app/ui/components/secret-key/mnemonic-key/mnemonic-word-input.tsx diff --git a/src/app/components/secret-key/mnemonic-key/utils/error-handling.ts b/src/app/ui/components/secret-key/mnemonic-key/utils/error-handling.ts similarity index 100% rename from src/app/components/secret-key/mnemonic-key/utils/error-handling.ts rename to src/app/ui/components/secret-key/mnemonic-key/utils/error-handling.ts diff --git a/src/app/components/secret-key/mnemonic-key/utils/validation.ts b/src/app/ui/components/secret-key/mnemonic-key/utils/validation.ts similarity index 100% rename from src/app/components/secret-key/mnemonic-key/utils/validation.ts rename to src/app/ui/components/secret-key/mnemonic-key/utils/validation.ts diff --git a/src/app/ui/components/secret-key/secret-key-grid.tsx b/src/app/ui/components/secret-key/secret-key-grid.tsx new file mode 100644 index 00000000000..1082a2b62a6 --- /dev/null +++ b/src/app/ui/components/secret-key/secret-key-grid.tsx @@ -0,0 +1,20 @@ +import { Grid } from 'leather-styles/jsx'; + +interface SecretKeyGridProps { + children: React.ReactNode; +} +export function SecretKeyGrid({ children }: SecretKeyGridProps) { + return ( + + {children} + + ); +} diff --git a/src/app/features/secret-key-displayer/components/secret-key-word.tsx b/src/app/ui/components/secret-key/secret-key-word.tsx similarity index 93% rename from src/app/features/secret-key-displayer/components/secret-key-word.tsx rename to src/app/ui/components/secret-key/secret-key-word.tsx index 1fad76031f1..dca0d2b8f27 100644 --- a/src/app/features/secret-key-displayer/components/secret-key-word.tsx +++ b/src/app/ui/components/secret-key/secret-key-word.tsx @@ -12,7 +12,7 @@ export function SecretKeyWord({ word, num }: SecretKeyWordProps) { width="100%" gap="space.01" px="space.03" - backgroundColor="ink.component-background-default" + bg="ink.component-background-default" borderRadius="xs" > diff --git a/src/app/features/secret-key-displayer/secret-key-displayer.layout.tsx b/src/app/ui/components/secret-key/secret-key.layout.tsx similarity index 63% rename from src/app/features/secret-key-displayer/secret-key-displayer.layout.tsx rename to src/app/ui/components/secret-key/secret-key.layout.tsx index c7b30138e2a..651aa81a822 100644 --- a/src/app/features/secret-key-displayer/secret-key-displayer.layout.tsx +++ b/src/app/ui/components/secret-key/secret-key.layout.tsx @@ -2,29 +2,33 @@ import { useState } from 'react'; import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; import { SettingsSelectors } from '@tests/selectors/settings.selectors'; -import { Flex, styled } from 'leather-styles/jsx'; +import { Flex, HStack, Stack, styled } from 'leather-styles/jsx'; import { Button } from '@app/ui/components/button/button'; import { CopyIcon } from '@app/ui/icons/copy-icon'; import { EyeIcon } from '@app/ui/icons/eye-icon'; import { EyeSlashIcon } from '@app/ui/icons/eye-slash-icon'; -import { SecretKeyGrid } from '../../components/secret-key/secret-key-grid'; -import { SecretKeyWord } from './components/secret-key-word'; +import { SecretKeyGrid } from './secret-key-grid'; +import { SecretKeyWord } from './secret-key-word'; -interface SecretKeyDisplayerLayoutProps { +interface SecretKeyLayoutProps { hasCopied: boolean; onCopyToClipboard(): void; secretKeyWords: string[] | undefined; showTitleAndIllustration: boolean; onBackedUpSecretKey(): void; } -export function SecretKeyDisplayerLayout(props: SecretKeyDisplayerLayoutProps) { - const { hasCopied, onCopyToClipboard, onBackedUpSecretKey, secretKeyWords } = props; +export function SecretKeyLayout({ + hasCopied, + onCopyToClipboard, + onBackedUpSecretKey, + secretKeyWords, +}: SecretKeyLayoutProps) { const [showSecretKey, setShowSecretKey] = useState(false); return ( - <> + {secretKeyWords?.map((word, index) => ( ))} - + - + ); } diff --git a/src/app/ui/components/typography/caption.tsx b/src/app/ui/components/typography/caption.tsx index f51c0a3501f..86953e043c0 100644 --- a/src/app/ui/components/typography/caption.tsx +++ b/src/app/ui/components/typography/caption.tsx @@ -8,7 +8,7 @@ export const Caption = forwardRef>( _disabled={{ color: 'ink.non-interactive' }} color="ink.text-subdued" ref={ref} - textStyle="caption.02" + textStyle="caption.01" {...props} > {children} diff --git a/src/app/ui/layout/card/card-content.tsx b/src/app/ui/layout/card/card-content.tsx new file mode 100644 index 00000000000..bf5c4d89243 --- /dev/null +++ b/src/app/ui/layout/card/card-content.tsx @@ -0,0 +1,27 @@ +import type { ReactNode } from 'react'; + +import { Flex, FlexProps } from 'leather-styles/jsx'; +import { token } from 'leather-styles/tokens'; + +interface CardContentProps extends FlexProps { + children: ReactNode; + dataTestId: string; +} + +export function CardContent({ children, dataTestId, ...props }: CardContentProps) { + return ( + + {children} + + ); +} diff --git a/src/app/ui/layout/card/card.stories.tsx b/src/app/ui/layout/card/card.stories.tsx new file mode 100644 index 00000000000..026a1da1f56 --- /dev/null +++ b/src/app/ui/layout/card/card.stories.tsx @@ -0,0 +1,28 @@ +import type { Meta } from '@storybook/react'; +import { Box } from 'leather-styles/jsx'; + +import { Button } from '@app/ui/components/button/button'; + +import { Card as Component } from './card'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Layout/Card', +}; + +export default meta; + +export function Card() { + return ( + null}> + Create new account + + } + > + + + ); +} diff --git a/src/app/ui/layout/card/card.tsx b/src/app/ui/layout/card/card.tsx new file mode 100644 index 00000000000..ae4359003db --- /dev/null +++ b/src/app/ui/layout/card/card.tsx @@ -0,0 +1,19 @@ +import type { ReactNode } from 'react'; + +import { Flex } from 'leather-styles/jsx'; + +interface CardProps { + children: ReactNode; + header?: ReactNode; + footer?: ReactNode; +} + +export function Card({ children, header, footer }: CardProps) { + return ( + + {header} + {children} + {footer} + + ); +} diff --git a/src/app/ui/layout/page/page.layout.stories.tsx b/src/app/ui/layout/page/page.layout.stories.tsx new file mode 100644 index 00000000000..8b972aa73d1 --- /dev/null +++ b/src/app/ui/layout/page/page.layout.stories.tsx @@ -0,0 +1,21 @@ +import type { Meta } from '@storybook/react'; + +import { Card } from '@app/ui/layout/card/card.stories'; + +import { Page as Component } from './page.layout'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Layout/Page', +}; + +export default meta; + +export function Page() { + return ( + + + + ); +} diff --git a/src/app/ui/layout/page/page.layout.tsx b/src/app/ui/layout/page/page.layout.tsx new file mode 100644 index 00000000000..19f4cb8cbd1 --- /dev/null +++ b/src/app/ui/layout/page/page.layout.tsx @@ -0,0 +1,16 @@ +import { type ReactNode } from 'react'; + +import { Box } from 'leather-styles/jsx'; + +interface PageProps { + children: ReactNode; + showLogo?: boolean; +} + +export function Page({ children }: PageProps) { + return ( + + {children} + + ); +} diff --git a/src/app/ui/pages/home.layout.stories.tsx b/src/app/ui/pages/home.layout.stories.tsx new file mode 100644 index 00000000000..54922c8b769 --- /dev/null +++ b/src/app/ui/pages/home.layout.stories.tsx @@ -0,0 +1,51 @@ +import type { Meta } from '@storybook/react'; +import { Box, Flex, Stack } from 'leather-styles/jsx'; + +import { RouteUrls } from '@shared/route-urls'; + +import { IconButton } from '@app/ui/components/icon-button/icon-button'; +import { Tabs } from '@app/ui/components/tabs/tabs'; +import { ArrowDownIcon, ArrowUpIcon, PlusIcon, SwapIcon } from '@app/ui/icons'; + +import { HomeLayout as Component } from './home.layout'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Pages/Home', +}; + +export default meta; + +export function HomeLayout() { + return ( + + } label="Send" /> + } label="Receive" /> + } label="Buy" /> + } label="Swap" /> + + } + > + + + + + Assets + + + Activity + + + + + + + ); +} diff --git a/src/app/ui/pages/home.layout.tsx b/src/app/ui/pages/home.layout.tsx new file mode 100644 index 00000000000..ee06606f5e8 --- /dev/null +++ b/src/app/ui/pages/home.layout.tsx @@ -0,0 +1,31 @@ +import type { ReactNode } from 'react'; + +import { HomePageSelectors } from '@tests/selectors/home.selectors'; +import { Box, Stack } from 'leather-styles/jsx'; + +interface HomeLayoutProps { + children: ReactNode; + accountCard: ReactNode; +} + +export function HomeLayout({ children, accountCard }: HomeLayoutProps) { + return ( + + + {accountCard} + + {children} + + ); +} diff --git a/src/app/ui/pages/two-column.layout.stories.tsx b/src/app/ui/pages/two-column.layout.stories.tsx new file mode 100644 index 00000000000..47b594f98a9 --- /dev/null +++ b/src/app/ui/pages/two-column.layout.stories.tsx @@ -0,0 +1,20 @@ +import type { Meta } from '@storybook/react'; +import { Box } from 'leather-styles/jsx'; + +import { TwoColumnLayout as Component } from './two-column.layout'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Layout/TwoColumnLayout', +}; + +export default meta; + +export function TwoColumnLayout() { + return ( + Hello world} content={

    lorem ipsum

    } action={<>some action}> + +
    + ); +} diff --git a/src/app/ui/pages/two-column.layout.tsx b/src/app/ui/pages/two-column.layout.tsx new file mode 100644 index 00000000000..78b8c6df1a3 --- /dev/null +++ b/src/app/ui/pages/two-column.layout.tsx @@ -0,0 +1,55 @@ +import { Box, Flex, Stack, styled } from 'leather-styles/jsx'; + +interface TwoColumnLayoutProps { + title: React.JSX.Element; + content: React.JSX.Element; + action?: React.JSX.Element; + children: React.JSX.Element; + wideChild?: boolean; +} + +export function TwoColumnLayout({ + title, + content, + action, + children, + wideChild, +}: TwoColumnLayoutProps): React.JSX.Element { + return ( + + + + {title} + {content} + {action} + + + + + + {children} + + + + ); +} diff --git a/src/app/ui/pages/welcome.layout.tsx b/src/app/ui/pages/welcome.layout.tsx new file mode 100644 index 00000000000..6a2679569f0 --- /dev/null +++ b/src/app/ui/pages/welcome.layout.tsx @@ -0,0 +1,143 @@ +import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; +import { Flex, styled } from 'leather-styles/jsx'; + +import { useThemeSwitcher } from '@app/common/theme-provider'; +import { Button } from '@app/ui/components/button/button'; +import { Link } from '@app/ui/components/link/link'; +import { LettermarkIcon } from '@app/ui/icons/lettermark-icon'; +import { LogomarkIcon } from '@app/ui/icons/logomark-icon'; + +interface WelcomeLayoutProps { + isGeneratingWallet: boolean; + onSelectConnectLedger(): void; + onStartOnboarding(): void; + onRestoreWallet(): void; +} +export function WelcomeLayout({ + isGeneratingWallet, + onStartOnboarding, + onSelectConnectLedger, + onRestoreWallet, +}: WelcomeLayoutProps): React.JSX.Element { + // On this page 'theme' is used to set specific colours and bypass automatic theming + const { theme } = useThemeSwitcher(); + // hardcoded specific instances of colour variables needed to bypass theme + const inkBgSecondary = '#F5F1ED'; + const inkTextPrimary = '#12100F'; + + const primaryActionButton = { + p: 'space.03', + minWidth: '148px', + bg: { + base: inkBgSecondary, + md: theme === 'light' ? inkBgSecondary : inkTextPrimary, + }, + color: { + base: inkTextPrimary, + md: theme === 'light' ? inkTextPrimary : inkBgSecondary, + }, + + _hover: { + bg: 'ink.action-primary-hover', + color: theme === 'light' ? inkBgSecondary : inkTextPrimary, + }, + }; + const secondaryActionButton = { + p: 'space.03', + minWidth: '148px', + color: { base: inkBgSecondary, md: theme === 'light' ? inkBgSecondary : inkTextPrimary }, + border: `1px solid ${inkBgSecondary}`, + borderColor: { base: inkBgSecondary, md: theme === 'light' ? inkBgSecondary : inkTextPrimary }, + _hover: { + bg: 'ink.action-primary-hover', + color: 'ink.background-secondary', + }, + }; + + const tagline = 'Bitcoin for the rest of us'; + const taglineExtended = 'The bitcoin wallet for the rest of us'; + const subheader = + 'Leather is the only Bitcoin wallet you need to tap into the emerging Bitcoin economy'; + + return ( + + + + + {tagline} + + + {taglineExtended} + + + + {subheader} + + + + + + + + + + + + + + + + leather.io + + + + + + + ); +} diff --git a/src/app/ui/shared/virtuoso.ts b/src/app/ui/shared/virtuoso.ts new file mode 100644 index 00000000000..ce1dc191264 --- /dev/null +++ b/src/app/ui/shared/virtuoso.ts @@ -0,0 +1,8 @@ +import { token } from 'leather-styles/tokens'; + +// virtuosoHeight = calc(InteractiveItem height - negative margin) 72px - 24px = 58 +export const virtuosoHeight = 58; + +export const virtuosoStyles = { + paddingBottom: token('spacing.space.05'), +}; diff --git a/src/app/ui/utils/prism.tsx b/src/app/ui/utils/prism.tsx index e4ae2112869..3a69ba152da 100644 --- a/src/app/ui/utils/prism.tsx +++ b/src/app/ui/utils/prism.tsx @@ -90,7 +90,7 @@ export type Language = export const theme: PrismTheme = { plain: { color: '#fff', - backgroundColor: 'transparent', + background: 'transparent', }, styles: [ { diff --git a/src/background/messaging/messaging-utils.ts b/src/background/messaging/messaging-utils.ts index c4dfac7bad3..e9422d73f56 100644 --- a/src/background/messaging/messaging-utils.ts +++ b/src/background/messaging/messaging-utils.ts @@ -2,7 +2,7 @@ import { InternalMethods } from '@shared/message-types'; import { sendMessage } from '@shared/messages'; import { RouteUrls } from '@shared/route-urls'; -import { popupCenter } from '@background/popup-center'; +import { popup } from '@background/popup'; export function getTabIdFromPort(port: chrome.runtime.Port) { return port.sender?.tab?.id ?? 0; @@ -67,5 +67,5 @@ const IS_TEST_ENV = process.env.TEST_ENV === 'true'; export async function triggerRequestWindowOpen(path: RouteUrls, urlParams: URLSearchParams) { if (IS_TEST_ENV) return openRequestInFullPage(path, urlParams); - return popupCenter({ url: `/popup-center.html#${path}?${urlParams.toString()}` }); + return popup({ url: `/popup.html#${path}?${urlParams.toString()}` }); } diff --git a/src/background/popup-center.ts b/src/background/popup.ts similarity index 51% rename from src/background/popup-center.ts rename to src/background/popup.ts index 5720a460c6f..65b0dcf2b5f 100644 --- a/src/background/popup-center.ts +++ b/src/background/popup.ts @@ -1,14 +1,22 @@ -import { POPUP_CENTER_HEIGHT, POPUP_CENTER_WIDTH } from '@shared/constants'; +import { pxStringToNumber } from '@shared/utils/px-string-to-number'; interface PopupOptions { url?: string; title?: string; - w?: number; - h?: number; skipPopupFallback?: boolean; } -export function popupCenter(options: PopupOptions): Promise { - const { url, w = POPUP_CENTER_WIDTH, h = POPUP_CENTER_HEIGHT } = options; +// FIXME import popupTokens from '@leather-wallet/tokens' when bundling working +// https://github.com/leather-wallet/mono/pull/76 +const popupTokens = { + popupWidth: { value: '390px' }, + popupHeight: { value: '756px' }, +}; + +export function popup(options: PopupOptions): Promise { + const { url } = options; + + const popupWidth = pxStringToNumber(popupTokens.popupWidth.value); + const popupHeight = pxStringToNumber(popupTokens.popupHeight.value); return new Promise(resolve => { // @see https://developer.chrome.com/docs/extensions/reference/windows/#method-getCurrent @@ -22,13 +30,13 @@ export function popupCenter(options: PopupOptions): Promise { const width = win.width ?? 0; const height = win.height ?? 0; - const left = Math.floor(width / 2 - w / 2 + dualScreenLeft); - const top = Math.floor(height / 2 - h / 2 + dualScreenTop); + const left = Math.floor(width / 2 - popupWidth / 2 + dualScreenLeft); + const top = Math.floor(height / 2 - popupHeight / 2 + dualScreenTop); const popup = await chrome.windows.create({ url, - width: w, - height: h, + width: popupWidth, + height: popupHeight, top, left, focused: true, diff --git a/src/shared/constants.ts b/src/shared/constants.ts index ebf5537ab58..20124ed5030 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -4,9 +4,6 @@ import { Blockchains } from './models/blockchain.model'; export const gaiaUrl = 'https://hub.blockstack.org'; -export const POPUP_CENTER_WIDTH = 442; -export const POPUP_CENTER_HEIGHT = 646; - export const HIGH_FEE_AMOUNT_STX = 5; export const HIGH_FEE_WARNING_LEARN_MORE_URL_BTC = 'https://bitcoinfees.earn.com/'; export const HIGH_FEE_WARNING_LEARN_MORE_URL_STX = 'https://hiro.so/questions/fee-estimates'; diff --git a/src/shared/route-urls.ts b/src/shared/route-urls.ts index 2ca8451ca83..7be2f0ba592 100644 --- a/src/shared/route-urls.ts +++ b/src/shared/route-urls.ts @@ -25,7 +25,6 @@ export enum RouteUrls { // Active wallet routes Home = '/', AddNetwork = '/add-network', - ChooseAccount = '/choose-account', Fund = '/fund/:currency', FundChooseCurrency = '/fund-choose-currency', IncreaseStxFee = '/increase-fee/stx', @@ -52,9 +51,7 @@ export enum RouteUrls { BitcoinContractList = '/bitcoin-contract-list', // Modal routes - ChangeTheme = 'change-theme', EditNonce = 'edit-nonce', - SelectNetwork = 'choose-network', SignOutConfirm = 'sign-out', RetrieveTaprootFunds = 'retrieve-taproot-funds', @@ -65,6 +62,7 @@ export enum RouteUrls { SendCryptoAssetFormRecipientAccounts = 'recipient-accounts', SendCryptoAssetFormRecipientBns = 'recipient-bns', SendBtcChooseFee = '/send/btc/choose-fee', + SendBtcError = '/send/btc/error', SendBtcConfirmation = '/send/btc/confirm', SendBtcDisabled = '/send/btc/disabled', SendStxConfirmation = '/send/stx/confirm', @@ -108,6 +106,9 @@ export enum RouteUrls { RpcSignBip322Message = '/sign-bip322-message', RpcStacksSignature = '/sign-stacks-message', + // Popup routes + ChooseAccount = '/choose-account', + // Shared legacy and rpc request routes RequestError = '/request-error', UnauthorizedRequest = '/unauthorized-request', diff --git a/src/shared/utils/px-string-to-number.spec.ts b/src/shared/utils/px-string-to-number.spec.ts new file mode 100644 index 00000000000..738f80f0276 --- /dev/null +++ b/src/shared/utils/px-string-to-number.spec.ts @@ -0,0 +1,12 @@ +import { pxStringToNumber } from './px-string-to-number'; + +describe('convert px string to number for calculation', () => { + it('converts standard px string to number', () => { + const result = pxStringToNumber('10px'); + expect(result).toEqual(10); + }); + it('converts token px string to number', () => { + const result = pxStringToNumber('600px'); + expect(result).toEqual(600); + }); +}); diff --git a/src/shared/utils/px-string-to-number.ts b/src/shared/utils/px-string-to-number.ts new file mode 100644 index 00000000000..f942018d314 --- /dev/null +++ b/src/shared/utils/px-string-to-number.ts @@ -0,0 +1,3 @@ +export function pxStringToNumber(pxString: string): number { + return +pxString.replace('px', ''); +} diff --git a/test-app/src/components/app.tsx b/test-app/src/components/app.tsx index 6fc725e1f05..fa913eb5d87 100755 --- a/test-app/src/components/app.tsx +++ b/test-app/src/components/app.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { AppContext } from '@common/context'; import { useAuth } from '@common/use-auth'; -import { Header } from '@components/header'; import { Home } from '@components/home'; import { Connect } from '@stacks/connect-react'; +import { Box, styled } from 'leather-styles/jsx'; import { Flex } from 'leather-styles/jsx'; export const App: React.FC = () => { @@ -17,7 +17,22 @@ export const App: React.FC = () => { {/*These are for tests*/} {authResponse && } {appPrivateKey && } -
    + + {state.userData ? ( + + { + handleSignOut(); + }} + > + Sign out + + + ) : null} + diff --git a/test-app/src/components/bns.tsx b/test-app/src/components/bns.tsx index 50e30a3065d..21c0c754974 100644 --- a/test-app/src/components/bns.tsx +++ b/test-app/src/components/bns.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { Box, styled } from 'leather-styles/jsx'; +/** TODO 4370 - Delete this as the link is broken ???? */ +/** @deprecated */ export const Bns = () => { return ( diff --git a/test-app/src/components/counter-actions.tsx b/test-app/src/components/counter-actions.tsx index 50590867e84..94a34fba30e 100644 --- a/test-app/src/components/counter-actions.tsx +++ b/test-app/src/components/counter-actions.tsx @@ -5,7 +5,7 @@ import { AppContext } from '@common/context'; import { getRPCClient, stacksTestnetNetwork as network } from '@common/utils'; import { ExplorerLink } from '@components/explorer-link'; import { useConnect } from '@stacks/connect-react'; -import { Box, styled } from 'leather-styles/jsx'; +import { Box, Flex, styled } from 'leather-styles/jsx'; export const CounterActions: React.FC = () => { const { userData } = React.useContext(AppContext); @@ -54,26 +54,30 @@ export const CounterActions: React.FC = () => { return ( - {!userData && Log in to change the state of this smart contract.} - - - - - + + {error && ( - + {error} - + )} {txId && !loading && } {counter !== null && !loading && ( - Current counter value: {counter} + Current counter value: {counter} )} ); diff --git a/test-app/src/components/header.tsx b/test-app/src/components/header.tsx deleted file mode 100644 index d98839c4e4f..00000000000 --- a/test-app/src/components/header.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { useContext } from 'react'; - -import { AppContext } from '@common/context'; -import { Link } from '@components/link'; -import { Box, styled } from 'leather-styles/jsx'; - -interface HeaderProps { - signOut: () => void; -} - -export const Header: React.FC = ({ signOut }) => { - const state = useContext(AppContext); - return ( - - {state.userData ? ( - - { - signOut(); - }} - > - Sign out - - - ) : null} - - ); -}; diff --git a/tests/page-object-models/home.page.ts b/tests/page-object-models/home.page.ts index afd587bb7bd..477bee70a79 100644 --- a/tests/page-object-models/home.page.ts +++ b/tests/page-object-models/home.page.ts @@ -1,5 +1,6 @@ import { Locator, Page } from '@playwright/test'; import { HomePageSelectors } from '@tests/selectors/home.selectors'; +import { NetworkSelectors } from '@tests/selectors/network.selectors'; import { SettingsSelectors } from '@tests/selectors/settings.selectors'; import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; import { createTestSelector } from '@tests/utils'; @@ -8,7 +9,7 @@ import { WalletDefaultNetworkConfigurationIds } from '@shared/constants'; export class HomePage { readonly page: Page; - readonly drawerActionButton: Locator; + readonly headerActionButton: Locator; readonly receiveButton: Locator; readonly sendButton: Locator; readonly swapButton: Locator; @@ -21,10 +22,6 @@ export class HomePage { readonly lockSettingsListItem: Locator; readonly fundAccountBtn: Locator; - readonly testNetworkSelector: string = createTestSelector( - WalletDefaultNetworkConfigurationIds.testnet - ); - $signOutConfirmHasBackupCheckbox = createTestSelector( SettingsSelectors.SignOutConfirmHasBackupCheckbox ); @@ -35,7 +32,7 @@ export class HomePage { constructor(page: Page) { this.page = page; - this.drawerActionButton = page.getByTestId(HomePageSelectors.DrawerHeaderActionBtn); + this.headerActionButton = page.getByTestId(SharedComponentsSelectors.HeaderBackBtn); this.receiveButton = page.getByTestId(HomePageSelectors.ReceiveCryptoAssetBtn); this.sendButton = page.getByTestId(HomePageSelectors.SendCryptoAssetBtn); this.swapButton = page.getByTestId(HomePageSelectors.SwapBtn); @@ -55,7 +52,7 @@ export class HomePage { this.fundAccountBtn = page.getByTestId(HomePageSelectors.FundAccountBtn); } - async goToReceiveModal() { + async goToReceiveDialog() { await this.page.getByTestId(HomePageSelectors.ReceiveCryptoAssetBtn).click(); } @@ -69,7 +66,7 @@ export class HomePage { // https://github.com/microsoft/playwright/issues/12168 // Using the `Receive` route to get the account address for now. async getReceiveNativeSegwitAddress() { - await this.goToReceiveModal(); + await this.goToReceiveDialog(); await this.page.getByTestId(HomePageSelectors.ReceiveBtcNativeSegwitQrCodeBtn).click(); const displayerAddress = await this.page .getByTestId(SharedComponentsSelectors.AddressDisplayer) @@ -79,14 +76,21 @@ export class HomePage { // Currently under Ordinals receive flow async getReceiveTaprootAddress() { - await this.goToReceiveModal(); + await this.goToReceiveDialog(); + await this.page.getByTestId(HomePageSelectors.ReceiveCollectiblesTab).click(); await this.page.getByTestId(HomePageSelectors.ReceiveBtcTaprootQrCodeBtn).click(); - await this.page.getByRole('button', { name: 'Copy address' }).click(); - return this.page.evaluate('navigator.clipboard.readText()'); + // FIXME - add better test for Copy action + // await this.page.getByRole('button', { name: 'Copy address' }).click(); + // const address = await this.page.evaluate('navigator.clipboard.readText()'); + // return address; + const displayerAddress = await this.page + .getByTestId(SharedComponentsSelectors.AddressDisplayer) + .innerText(); + return displayerAddress.replaceAll('\n', ''); } async getReceiveStxAddress() { - await this.goToReceiveModal(); + await this.goToReceiveDialog(); // In Ledger mode, this element isn't visible, so clicking is conditional const qrCodeBtn = this.page.getByTestId(HomePageSelectors.ReceiveStxQrCodeBtn); if (await qrCodeBtn.isVisible()) await qrCodeBtn.click(); @@ -96,10 +100,15 @@ export class HomePage { return displayerAddress.replaceAll('\n', ''); } - async enableTestMode() { + async selectTestNet() { await this.page.getByTestId(SettingsSelectors.SettingsMenuBtn).click(); await this.page.getByTestId(SettingsSelectors.ChangeNetworkAction).click(); + await this.page + .getByTestId(WalletDefaultNetworkConfigurationIds.testnet) + .click({ force: true }); + await this.page.getByTestId(NetworkSelectors.NetworkListActiveNetwork).isVisible(); + await this.page.waitForTimeout(1000); await this.page.getByTestId(WalletDefaultNetworkConfigurationIds.testnet).click(); } diff --git a/tests/page-object-models/network.page.ts b/tests/page-object-models/network.page.ts index 386d48b537f..0441a593bcc 100644 --- a/tests/page-object-models/network.page.ts +++ b/tests/page-object-models/network.page.ts @@ -9,29 +9,31 @@ export class NetworkPage { NetworkSelectors.NetworkBitcoinAddress ); readonly networkKeySelector = createTestSelector(NetworkSelectors.NetworkKey); - readonly btnAddNetworkSelector = createTestSelector(NetworkSelectors.BtnAddNetwork); + readonly btnAddNetworkSelector = createTestSelector(NetworkSelectors.AddNetworkBtn); readonly errorTextSelector = createTestSelector(NetworkSelectors.ErrorText); constructor(readonly page: Page) {} + async waitForNetworkPageReady() { + await this.page.waitForSelector(createTestSelector(NetworkSelectors.NetworkPageReady), { + state: 'attached', + }); + } + async inputNetworkNameField(input: string) { - const field = this.page.locator(this.networkNameSelector); - await field?.fill(input); + await this.page.locator(this.networkNameSelector).fill(input); } async inputNetworkStacksAddressField(input: string) { - const field = this.page.locator(this.networkStacksAddressSelector); - await field?.fill(input); + await this.page.locator(this.networkStacksAddressSelector).fill(input); } async inputNetworkBitcoinAddressField(input: string) { - const field = this.page.locator(this.networkBitcoinAddressSelector); - await field?.fill(input); + await this.page.locator(this.networkBitcoinAddressSelector).fill(input); } async inputNetworkKeyField(input: string) { - const field = this.page.locator(this.networkKeySelector); - await field?.fill(input); + await this.page.locator(this.networkKeySelector).fill(input); } async waitForErrorMessage() { @@ -43,6 +45,6 @@ export class NetworkPage { } async clickAddNetwork() { - await this.page.locator(this.btnAddNetworkSelector).click(); + await this.page.locator(this.btnAddNetworkSelector).click({ force: true }); } } diff --git a/tests/page-object-models/onboarding.page.ts b/tests/page-object-models/onboarding.page.ts index a7260550a61..1c54efe4791 100644 --- a/tests/page-object-models/onboarding.page.ts +++ b/tests/page-object-models/onboarding.page.ts @@ -41,7 +41,6 @@ export const testSoftwareAccountDefaultWalletState = { userSelectedTheme: 'system', hasAllowedAnalytics: false, dismissedMessages: [], - hasApprovedNewBrand: true, }, _persist: { version: 2, rehydrated: true }, }; diff --git a/tests/page-object-models/send.page.ts b/tests/page-object-models/send.page.ts index edb0b419c57..d89e993482e 100644 --- a/tests/page-object-models/send.page.ts +++ b/tests/page-object-models/send.page.ts @@ -94,7 +94,7 @@ export class SendPage { } async goBack() { - await this.page.getByTestId(SharedComponentsSelectors.ModalHeaderBackBtn).click(); + await this.page.getByTestId(SharedComponentsSelectors.HeaderBackBtn).click({ force: true }); } async goBackSelectStx() { diff --git a/tests/selectors/home.selectors.ts b/tests/selectors/home.selectors.ts index 0b64be33b76..ab42923c2b9 100644 --- a/tests/selectors/home.selectors.ts +++ b/tests/selectors/home.selectors.ts @@ -1,8 +1,9 @@ export enum HomePageSelectors { - DrawerHeaderActionBtn = 'drawer-header-action-btn', HomePageContainer = 'home-page-container', ReceiveCryptoAssetBtn = 'receive-crypto-asset-btn', ReceiveBtcNativeSegwitQrCodeBtn = 'receive-native-segwit-qr-code-btn', + ReceiveAssetsTab = 'receive-assets-tab', + ReceiveCollectiblesTab = 'receive-collectibles-tab', ReceiveBtcTaprootQrCodeBtn = 'receive-taproot-qr-code-btn', ReceiveStxQrCodeBtn = 'receive-stx-qr-code-btn', SendCryptoAssetBtn = 'send-crypto-asset-btn', diff --git a/tests/selectors/network.selectors.ts b/tests/selectors/network.selectors.ts index fd6dffa1d8a..ac5ff3652a9 100644 --- a/tests/selectors/network.selectors.ts +++ b/tests/selectors/network.selectors.ts @@ -1,9 +1,11 @@ export enum NetworkSelectors { + NetworkListActiveNetwork = 'network-active-network', + NetworkPageReady = 'network-page-ready', NetworkName = 'network-name', NetworkStacksAddress = 'network-stacks-address', NetworkBitcoinAddress = 'network-bitcoin-address', NetworkKey = 'network-key', - BtnAddNetwork = 'btn-add-network', + AddNetworkBtn = 'add-network-btn', ErrorText = 'error-text', EmptyNameError = 'Enter a name', EmptyStacksAddressError = 'Enter a valid Stacks API URL', diff --git a/tests/selectors/onboarding.selectors.ts b/tests/selectors/onboarding.selectors.ts index 9371afc424a..fa4e8cb66f7 100644 --- a/tests/selectors/onboarding.selectors.ts +++ b/tests/selectors/onboarding.selectors.ts @@ -2,7 +2,7 @@ export enum OnboardingSelectors { AllowAnalyticsBtn = 'allow-analytics-btn', BackUpSecretKeyBtn = 'back-up-secret-key-btn', DenyAnalyticsBtn = 'deny-analytics-btn', - LeatherLogoRouteToHome = 'leather-logo-route-to-home', + LogoRouteToHome = 'logo-route-to-home', NewPasswordInput = 'set-or-enter-password-input', NoAssetsFundAccountLink = 'no-assets-fund-account-link', SecretKey = 'secret-key', diff --git a/tests/selectors/requests.selectors.ts b/tests/selectors/requests.selectors.ts index ef0c2c5c1b5..ba45aed9f98 100644 --- a/tests/selectors/requests.selectors.ts +++ b/tests/selectors/requests.selectors.ts @@ -8,3 +8,11 @@ export enum UpdateProfileRequestSelectors { BtnUpdateProfile = 'btn-update-profile', ErrorMessage = 'update-profile-request-error-message', } + +export enum PsbtSelectors { + PsbtSignerCard = 'psbt-signer-card', +} + +export enum DlcSelectors { + DlcSelectorsCard = 'dlc-signer-card', +} diff --git a/tests/selectors/settings.selectors.ts b/tests/selectors/settings.selectors.ts index 25d88c0d572..2af823d329b 100644 --- a/tests/selectors/settings.selectors.ts +++ b/tests/selectors/settings.selectors.ts @@ -17,10 +17,11 @@ export enum SettingsSelectors { LockListItem = 'settings-lock', EnterPasswordInput = 'password-input', UnlockWalletBtn = 'unlock-wallet-btn', - BtnAddNetwork = 'btn-add-network', + AddNewNetworkBtn = 'add-new-network-btn', ShowSecretKeyBtn = 'show-secret-key-btn', GetSupportMenuItem = 'get-support-menu-item', - SettingsMenuBtn = 'settings-menu-btn', + SettingsMenuBtn = 'settings-menu-trigger', + SwitchAccountTrigger = 'switch-account-trigger', SwitchAccountMenuItem = 'switch-account-menu-item', SwitchAccountItemIndex = 'switch-account-item-[index]', OpenWalletInNewTab = 'open-wallet-in-new-tab', diff --git a/tests/selectors/shared-component.selectors.ts b/tests/selectors/shared-component.selectors.ts index 1090a4c75f1..1f98f53e98f 100644 --- a/tests/selectors/shared-component.selectors.ts +++ b/tests/selectors/shared-component.selectors.ts @@ -19,9 +19,8 @@ export enum SharedComponentsSelectors { FeesListItem = 'fee-list-item', FeesListItemFeeValue = 'fee-list-item-fee-value', - // Modal Header - ModalHeaderBackBtn = 'modal-header-back-button', - // Error BroadcastErrorTitle = 'broadcast-error-title', + HeaderBackBtn = 'header-back-button', + HeaderCloseBtn = 'header-close-button', } diff --git a/tests/specs/network/add-network.spec.ts b/tests/specs/network/add-network.spec.ts index 4883d0b8508..837955e68d2 100644 --- a/tests/specs/network/add-network.spec.ts +++ b/tests/specs/network/add-network.spec.ts @@ -4,13 +4,16 @@ import { SettingsSelectors } from '@tests/selectors/settings.selectors'; import { test } from '../../fixtures/fixtures'; test.describe('Networks tests', () => { - test.beforeEach(async ({ extensionId, globalPage, onboardingPage, homePage, page }) => { - await globalPage.setupAndUseApiCalls(extensionId); - await onboardingPage.signInWithTestAccount(extensionId); - await homePage.clickSettingsButton(); - await page.getByTestId(SettingsSelectors.ChangeNetworkAction).click(); - await page.getByTestId(SettingsSelectors.BtnAddNetwork).click(); - }); + test.beforeEach( + async ({ extensionId, globalPage, onboardingPage, homePage, networkPage, page }) => { + await globalPage.setupAndUseApiCalls(extensionId); + await onboardingPage.signInWithTestAccount(extensionId); + await homePage.clickSettingsButton(); + await page.getByTestId(SettingsSelectors.ChangeNetworkAction).click(); + await page.getByTestId(SettingsSelectors.AddNewNetworkBtn).click(); + await networkPage.waitForNetworkPageReady(); + } + ); test('validation error when stacks api url is empty', async ({ networkPage }) => { await networkPage.inputNetworkNameField('Test network'); @@ -33,6 +36,7 @@ test.describe('Networks tests', () => { const errorMessage = await errorMsgElement.innerText(); test.expect(errorMessage).toEqual(NetworkSelectors.EmptyNameError); }); + test('validation error when key is empty', async ({ networkPage }) => { await networkPage.inputNetworkNameField('Test network'); await networkPage.clickAddNetwork(); diff --git a/tests/specs/send/send-btc.spec.ts b/tests/specs/send/send-btc.spec.ts index 15bba8f9a5e..4ef10d57b09 100644 --- a/tests/specs/send/send-btc.spec.ts +++ b/tests/specs/send/send-btc.spec.ts @@ -12,7 +12,7 @@ test.describe('send btc', () => { test.beforeEach(async ({ extensionId, globalPage, homePage, onboardingPage, sendPage }) => { await globalPage.setupAndUseApiCalls(extensionId); await onboardingPage.signInWithTestAccount(extensionId); - await homePage.enableTestMode(); + await homePage.selectTestNet(); await homePage.sendButton.click(); await sendPage.selectBtcAndGoToSendForm(); await sendPage.waitForSendPageReady(); diff --git a/tests/specs/send/send-stx.spec.ts b/tests/specs/send/send-stx.spec.ts index c0e3a3982b7..8b2fd1673ec 100644 --- a/tests/specs/send/send-stx.spec.ts +++ b/tests/specs/send/send-stx.spec.ts @@ -4,7 +4,6 @@ import { TEST_BNS_RESOLVED_ADDRESS, TEST_TESTNET_ACCOUNT_2_STX_ADDRESS, } from '@tests/mocks/constants'; -import { SendPage } from '@tests/page-object-models/send.page'; import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; import { getDisplayerAddress } from '@tests/utils'; @@ -14,219 +13,206 @@ import { FormErrorMessages } from '@app/common/error-messages'; import { test } from '../../fixtures/fixtures'; -test.describe('send stx', () => { - test.describe.serial('tests on testnet', () => { - const amount = '0.000001'; - let sPage: SendPage; - test.beforeAll(async ({ extensionId, globalPage, onboardingPage, homePage, sendPage }) => { - await globalPage.setupAndUseApiCalls(extensionId); - await onboardingPage.signInWithTestAccount(extensionId); - await homePage.enableTestMode(); - await homePage.sendButton.click(); - await sendPage.selectStxAndGoToSendForm(); - - sPage = sendPage; - }); +const amount = '0.000001'; - test('that send max button sets available balance minus fee', async () => { - await sPage.amountInput.fill('.0001'); - await sPage.amountInput.clear(); - await sPage.amountInput.blur(); - await sPage.sendMaxButton.click(); - await sPage.amountInput.blur(); - test.expect(await sPage.amountInput.inputValue()).toBeTruthy(); - await sPage.goBackSelectStx(); - }); +test.describe('send stx: tests on testnet', () => { + test.beforeEach(async ({ extensionId, globalPage, homePage, onboardingPage, sendPage }) => { + await globalPage.setupAndUseApiCalls(extensionId); + await onboardingPage.signInWithTestAccount(extensionId); + await homePage.selectTestNet(); + await homePage.sendButton.click(); + await sendPage.selectStxAndGoToSendForm(); + }); - test('that empty memo on preview matches default empty value', async () => { - const emptyMemoPreviewValue = 'No memo'; + test('that send max button sets available balance minus fee', async ({ sendPage }) => { + await sendPage.amountInput.fill('.0001'); + await sendPage.amountInput.clear(); + await sendPage.amountInput.blur(); + await sendPage.sendMaxButton.click(); + await sendPage.amountInput.blur(); + }); - await sPage.amountInput.fill(amount); - await sPage.recipientInput.fill(TEST_TESTNET_ACCOUNT_2_STX_ADDRESS); + test('that empty memo on preview matches default empty value', async ({ sendPage }) => { + const emptyMemoPreviewValue = 'No memo'; - await sPage.previewSendTxButton.click(); + await sendPage.amountInput.fill('1'); + await sendPage.amountInput.blur(); + await sendPage.page.waitForTimeout(2000); + await sendPage.recipientInput.fill(TEST_TESTNET_ACCOUNT_2_STX_ADDRESS); + await sendPage.recipientInput.blur(); + await sendPage.page.waitForTimeout(2000); + await sendPage.previewSendTxButton.focus(); + await sendPage.previewSendTxButton.click(); - const confirmationMemo = await sPage.memoRow - .getByTestId(SharedComponentsSelectors.InfoCardRowValue) - .innerText(); - test.expect(confirmationMemo).toEqual(emptyMemoPreviewValue); - await sPage.goBack(); - }); + const confirmationMemo = await sendPage.memoRow + .getByTestId(SharedComponentsSelectors.InfoCardRowValue) + .innerText(); - test('that asset value, recipient, memo and fees on preview match input', async () => { - const amountSymbol = 'STX'; - const memo = '123'; - await sPage.amountInput.fill(amount); - await sPage.recipientInput.fill(TEST_TESTNET_ACCOUNT_2_STX_ADDRESS); - await sPage.memoInput.fill(memo); + test.expect(confirmationMemo).toEqual(emptyMemoPreviewValue); + }); - const fees = await sPage.page - .getByTestId(SharedComponentsSelectors.FeeToBePaidLabel) - .innerText(); - await sPage.previewSendTxButton.click(); + test('that asset value, recipient, memo and fees on preview match input', async ({ + sendPage, + }) => { + const amountSymbol = 'STX'; + const memo = '123'; + await sendPage.amountInput.fill(amount); + await sendPage.recipientInput.fill(TEST_TESTNET_ACCOUNT_2_STX_ADDRESS); + await sendPage.memoInput.fill(memo); + + const fees = await sendPage.page + .getByTestId(SharedComponentsSelectors.FeeToBePaidLabel) + .innerText(); + await sendPage.previewSendTxButton.click(); + + const displayerAddress = await getDisplayerAddress(sendPage.confirmationDetailsRecipient); + + test.expect(displayerAddress).toEqual(TEST_TESTNET_ACCOUNT_2_STX_ADDRESS); + + const confirmationAssetValue = await sendPage.confirmationDetails + .getByTestId(SharedComponentsSelectors.InfoCardAssetValue) + .innerText(); + test.expect(confirmationAssetValue).toEqual(`${amount} ${amountSymbol}`); + + const confirmationFees = await sendPage.feesRow + .getByTestId(SharedComponentsSelectors.InfoCardRowValue) + .innerText(); + test.expect(confirmationFees).toEqual(fees); + + const confirmationMemo2 = await sendPage.memoRow + .getByTestId(SharedComponentsSelectors.InfoCardRowValue) + .innerText(); + test.expect(confirmationMemo2).toEqual(memo); + }); - const displayerAddress = await getDisplayerAddress(sPage.confirmationDetailsRecipient); + test.describe('send form validation', () => { + test('that the amount must be a number', async ({ sendPage }) => { + await sendPage.amountInput.fill('aaaaaa'); + await sendPage.amountInput.blur(); + const errorMsg = await sendPage.amountInputErrorLabel.innerText(); + test.expect(errorMsg).toBeTruthy(); + }); - test.expect(displayerAddress).toEqual(TEST_TESTNET_ACCOUNT_2_STX_ADDRESS); + test('that the amount must be positive', async ({ sendPage }) => { + await sendPage.amountInput.fill('-9999'); + await sendPage.amountInput.blur(); + const errorMsg = await sendPage.amountInputErrorLabel.innerText(); + test.expect(errorMsg).toEqual(FormErrorMessages.MustBePositive); + }); - const confirmationAssetValue = await sPage.confirmationDetails - .getByTestId(SharedComponentsSelectors.InfoCardAssetValue) - .innerText(); - test.expect(confirmationAssetValue).toEqual(`${amount} ${amountSymbol}`); + test('that the amount field enforces max length based on decimals', async ({ sendPage }) => { + await sendPage.amountInput.fill('0.0000001'); + await sendPage.amountInput.blur(); + const errorMsg = await sendPage.amountInputErrorLabel.innerText(); + const error = FormErrorMessages.TooMuchPrecision; + test.expect(errorMsg).toEqual(error.replace('{decimals}', String(STX_DECIMALS))); + }); - const confirmationFees = await sPage.feesRow - .getByTestId(SharedComponentsSelectors.InfoCardRowValue) - .innerText(); - test.expect(confirmationFees).toEqual(fees); + test('that the amount is greater than the available balance', async ({ sendPage }) => { + await sendPage.amountInput.fill('999999999'); + await sendPage.amountInput.blur(); + const errorMsg = await sendPage.amountInputErrorLabel.innerText(); + test.expect(errorMsg).toContain('Insufficient balance'); + }); - const confirmationMemo2 = await sPage.memoRow - .getByTestId(SharedComponentsSelectors.InfoCardRowValue) - .innerText(); - test.expect(confirmationMemo2).toEqual(memo); - await sPage.goBack(); + test('that the address must be valid', async ({ sendPage }) => { + await sendPage.recipientInput.fill('ST3TZVWsss4VTZA1WZN2TB6RQ5J8RACHZYMWMM2N1HT2'); + await sendPage.recipientInput.blur(); + const errorMsg = await sendPage.formInputErrorLabel.innerText(); + test.expect(errorMsg).toContain(FormErrorMessages.InvalidAddress); }); - test.describe('send form validation', () => { - test.afterEach(async () => { - await sPage.goBackSelectStx(); - }); - test('that the amount must be a number', async () => { - await sPage.amountInput.fill('aaaaaa'); - await sPage.amountInput.blur(); - const errorMsg = await sPage.amountInputErrorLabel.innerText(); - test.expect(errorMsg).toBeTruthy(); - }); - - test('that the amount must be positive', async () => { - await sPage.amountInput.fill('-9999'); - await sPage.amountInput.blur(); - const errorMsg = await sPage.amountInputErrorLabel.innerText(); - test.expect(errorMsg).toEqual(FormErrorMessages.MustBePositive); - }); - - test('that the amount field enforces max length based on decimals', async () => { - await sPage.amountInput.fill('0.0000001'); - await sPage.amountInput.blur(); - const errorMsg = await sPage.amountInputErrorLabel.innerText(); - const error = FormErrorMessages.TooMuchPrecision; - test.expect(errorMsg).toEqual(error.replace('{decimals}', String(STX_DECIMALS))); - }); - - test('that the amount is greater than the available balance', async () => { - await sPage.amountInput.fill('999999999'); - await sPage.amountInput.blur(); - const errorMsg = await sPage.amountInputErrorLabel.innerText(); - test.expect(errorMsg).toContain('Insufficient balance'); - }); - - test('that the address must be valid', async () => { - await sPage.recipientInput.fill('ST3TZVWsss4VTZA1WZN2TB6RQ5J8RACHZYMWMM2N1HT2'); - await sPage.recipientInput.blur(); - const errorMsg = await sPage.formInputErrorLabel.innerText(); - test.expect(errorMsg).toContain(FormErrorMessages.InvalidAddress); - }); - - test('that the address cannot be same as sender', async () => { - await sPage.recipientChooseAccountButton.click(); - await sPage.page.getByTestId('switch-account-item-0').click(); - await sPage.previewSendTxButton.click(); - const errorMsg = await sPage.formInputErrorLabel.innerText(); - test.expect(errorMsg).toContain(FormErrorMessages.SameAddress); - }); - - test('that valid addresses are accepted', async () => { - await sPage.amountInput.fill('0.000001'); - await sPage.recipientInput.fill(TEST_ACCOUNT_2_STX_ADDRESS); - await sPage.previewSendTxButton.click(); - const details = await sPage.confirmationDetails.allInnerTexts(); - test.expect(details).toBeTruthy(); - }); + test('that the address cannot be same as sender', async ({ sendPage }) => { + await sendPage.recipientChooseAccountButton.click(); + await sendPage.page.getByTestId('switch-account-item-0').click(); + await sendPage.previewSendTxButton.click(); + const errorMsg = await sendPage.formInputErrorLabel.innerText(); + test.expect(errorMsg).toContain(FormErrorMessages.SameAddress); }); - test.describe('send form preview', () => { - test.afterEach(async () => { - await sPage.goBack(); - await sPage.goBackSelectStx(); - }); - test('that it shows preview of tx details to be confirmed', async () => { - await sPage.amountInput.fill('0.000001'); - await sPage.recipientInput.fill(TEST_TESTNET_ACCOUNT_2_STX_ADDRESS); - - await sPage.previewSendTxButton.click(); - const details = await sPage.confirmationDetails.allInnerTexts(); - test.expect(details).toBeTruthy(); - }); - - test('that it shows preview of tx details after validation error is resolved', async () => { - await sPage.amountInput.fill('0.0000001'); - await sPage.recipientInput.fill(TEST_TESTNET_ACCOUNT_2_STX_ADDRESS); - - await sPage.previewSendTxButton.click(); - const errorMsg = await sPage.amountInputErrorLabel.innerText(); - const error = FormErrorMessages.TooMuchPrecision; - test.expect(errorMsg).toEqual(error.replace('{decimals}', String(STX_DECIMALS))); - await sPage.amountInput.fill('0.000001'); - await sPage.previewSendTxButton.click(); - const details = await sPage.confirmationDetails.allInnerTexts(); - test.expect(details).toBeTruthy(); - }); + test('that valid addresses are accepted', async ({ sendPage }) => { + await sendPage.amountInput.fill('0.000001'); + await sendPage.recipientInput.fill(TEST_ACCOUNT_2_STX_ADDRESS); + await sendPage.previewSendTxButton.click(); + const details = await sendPage.confirmationDetails.allInnerTexts(); + test.expect(details).toBeTruthy(); }); }); - // Those that can should be migrated to testnet tests - test.describe.serial('tests on mainnet', () => { - let sPage: SendPage; - test.beforeAll(async ({ extensionId, globalPage, onboardingPage, homePage, sendPage }) => { - await globalPage.setupAndUseApiCalls(extensionId); - await onboardingPage.signInWithTestAccount(extensionId); - await homePage.sendButton.click(); - await sendPage.selectStxAndGoToSendForm(); + test.describe('send form preview', () => { + test('send form preview: that it shows preview of tx details to be confirmed', async ({ + sendPage, + }) => { + await sendPage.amountInput.fill('0.000001'); + await sendPage.recipientInput.fill(TEST_TESTNET_ACCOUNT_2_STX_ADDRESS); + await sendPage.previewSendTxButton.click(); + const details = await sendPage.confirmationDetails.allInnerTexts(); + test.expect(details).toBeTruthy(); + }); - sPage = sendPage; + test('send form preview: that it shows preview of tx details after validation error is resolved', async ({ + sendPage, + }) => { + await sendPage.amountInput.fill('0.0000001'); + await sendPage.recipientInput.fill(TEST_TESTNET_ACCOUNT_2_STX_ADDRESS); + + await sendPage.previewSendTxButton.click(); + const errorMsg = await sendPage.amountInputErrorLabel.innerText(); + const error = FormErrorMessages.TooMuchPrecision; + test.expect(errorMsg).toEqual(error.replace('{decimals}', String(STX_DECIMALS))); + await sendPage.amountInput.fill('0.000001'); + await sendPage.previewSendTxButton.click(); + const details = await sendPage.confirmationDetails.allInnerTexts(); + test.expect(details).toBeTruthy(); }); + }); +}); + +// Those that can should be migrated to testnet tests +test.describe('send stx: tests on mainnet', () => { + test.beforeEach(async ({ extensionId, globalPage, onboardingPage, homePage, sendPage }) => { + await globalPage.setupAndUseApiCalls(extensionId); + await onboardingPage.signInWithTestAccount(extensionId); + await homePage.sendButton.click(); + await sendPage.selectStxAndGoToSendForm(); + }); - test.afterEach(async () => { - await sPage.goBack(); - await sPage.selectStxAndGoToSendForm(); + test.describe('send form input fields', () => { + test('that recipient address matches bns name', async ({ sendPage }) => { + await sendPage.amountInput.fill('.0001'); + await sendPage.amountInput.blur(); + await sendPage.recipientSelectRecipientTypeDropdown.click(); + await sendPage.recipientSelectFieldBnsName.click(); + await sendPage.recipientInput.fill(TEST_BNS_NAME); + await sendPage.recipientInput.blur(); + await sendPage.recipientBnsAddressLabel.waitFor(); + const bnsResolvedAddress = await sendPage.page + .getByText(TEST_BNS_RESOLVED_ADDRESS) + .innerText(); + + test.expect(bnsResolvedAddress).toBeTruthy(); }); - test.describe('send form input fields', () => { - test('that recipient address matches bns name', async () => { - await sPage.amountInput.fill('.0001'); - await sPage.amountInput.blur(); - await sPage.recipientSelectRecipientTypeDropdown.click(); - await sPage.recipientSelectFieldBnsName.click(); - await sPage.recipientInput.fill(TEST_BNS_NAME); - await sPage.recipientInput.blur(); - await sPage.recipientBnsAddressLabel.waitFor(); - const bnsResolvedAddress = await sPage.page - .getByText(TEST_BNS_RESOLVED_ADDRESS) - .innerText(); - - test.expect(bnsResolvedAddress).toBeTruthy(); - }); - - test('that fee row defaults to middle fee estimation', async () => { - const feeToBePaid = await sPage.page - .getByTestId(SharedComponentsSelectors.FeeToBePaidLabel) - .innerText(); - const fee = Number(feeToBePaid.split(' ')[0]); - // Using min/max fee caps - const isMiddleFee = fee >= 0.003 && fee <= 0.75; - test.expect(isMiddleFee).toBeTruthy(); - }); - - test('that low fee estimate can be selected', async () => { - await sPage.page.getByTestId(SharedComponentsSelectors.MiddleFeeEstimateItem).click(); - await sPage.page.getByTestId(SharedComponentsSelectors.LowFeeEstimateItem).click(); - const feeToBePaid = await sPage.page - .getByTestId(SharedComponentsSelectors.FeeToBePaidLabel) - .innerText(); - const fee = Number(feeToBePaid.split(' ')[0]); - // Using min/max fee caps - const isLowFee = fee >= 0.0025 && fee <= 0.5; - test.expect(isLowFee).toBeTruthy(); - }); + test('that fee row defaults to middle fee estimation', async ({ sendPage }) => { + const feeToBePaid = await sendPage.page + .getByTestId(SharedComponentsSelectors.FeeToBePaidLabel) + .innerText(); + const fee = Number(feeToBePaid.split(' ')[0]); + // Using min/max fee caps + const isMiddleFee = fee >= 0.003 && fee <= 0.75; + test.expect(isMiddleFee).toBeTruthy(); + }); + + test('that low fee estimate can be selected', async ({ sendPage }) => { + await sendPage.page.getByTestId(SharedComponentsSelectors.MiddleFeeEstimateItem).click(); + await sendPage.page.getByTestId(SharedComponentsSelectors.LowFeeEstimateItem).click(); + const feeToBePaid = await sendPage.page + .getByTestId(SharedComponentsSelectors.FeeToBePaidLabel) + .innerText(); + const fee = Number(feeToBePaid.split(' ')[0]); + // Using min/max fee caps + const isLowFee = fee >= 0.0025 && fee <= 0.5; + test.expect(isLowFee).toBeTruthy(); }); }); }); diff --git a/tests/specs/settings/settings-menu.spec.ts b/tests/specs/settings/settings.spec.ts similarity index 95% rename from tests/specs/settings/settings-menu.spec.ts rename to tests/specs/settings/settings.spec.ts index 48c7bb71d37..1ed4296a0c0 100644 --- a/tests/specs/settings/settings-menu.spec.ts +++ b/tests/specs/settings/settings.spec.ts @@ -42,6 +42,7 @@ test.describe('Settings menu', () => { test.expect(displayName).toEqual('Account 1'); }); + // TODO 3919 improve tests = This doesn't actually test copy of the value test('that menu item allows viewing and saving secret key to clipboard', async ({ page, homePage, @@ -58,6 +59,7 @@ test.describe('Settings menu', () => { test.expect(copySuccessMessage).toContain('Copied!'); }); + // TODO 3919 improve tests - this doesn't actually change networks test('that menu item allows changing networks', async ({ homePage, page }) => { await homePage.clickSettingsButton(); const currentNetwork = await page.getByTestId(SettingsSelectors.CurrentNetwork).innerText(); diff --git a/theme/global/full-page-styles.ts b/theme/global/full-page-styles.ts deleted file mode 100644 index e4a41367579..00000000000 --- a/theme/global/full-page-styles.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const fullPageStyles = { - '.mode__full-page': { - '&, body, main, .radix-themes': { - height: '100%', - maxHeight: 'unset', - width: '100%', - }, - '.main-content': { - flexGrow: 1, - justifyContent: 'center', - margin: '0 auto', - }, - }, -}; diff --git a/theme/global/global.ts b/theme/global/global.ts index fdec95e9a18..ef3d871c7be 100644 --- a/theme/global/global.ts +++ b/theme/global/global.ts @@ -1,20 +1,19 @@ +import { tokens } from '@leather-wallet/tokens'; import { defineGlobalStyles } from '@pandacss/dev'; -import { fullPageStyles } from './full-page-styles'; -import { popupCenterStyles } from './popup-center-styles'; -import { popupStyles } from './popup-styles'; - // ts-unused-exports:disable-next-line export const globalCss = defineGlobalStyles({ - 'html, body': { - backgroundColor: 'ink.background-primary', - }, button: { cursor: 'pointer', }, - '@media (min-width: 600px)': { - 'html, body': { - backgroundColor: 'ink.background-secondary', + 'html, body': { + backgroundColor: 'ink.background-primary', + }, + html: { + // adding to always not show scroll bar for windows Chrome + '::-webkit-scrollbar': { + display: 'none', + width: 0, }, }, body: { @@ -22,7 +21,29 @@ export const globalCss = defineGlobalStyles({ overflow: 'hidden', }, }, - ...fullPageStyles, - ...popupStyles, - ...popupCenterStyles, + '.mode__full-page': { + '&, body, main, .radix-themes': { + height: '100%', + maxHeight: 'unset', + width: '100%', + }, + '.main-content': { + flexGrow: 1, + justifyContent: 'center', + margin: '0 auto', + }, + }, + '.mode__popup': { + 'html,body, #app, .radix-themes': { + height: tokens.sizes.popupHeight.value, + maxHeight: '100vh', + minHeight: tokens.sizes.dialogHeight.value, + width: tokens.sizes.popupWidth.value, + + '::-webkit-scrollbar': { + display: 'none', + width: 0, + }, + }, + }, }); diff --git a/theme/global/popup-center-styles.ts b/theme/global/popup-center-styles.ts deleted file mode 100644 index d16d386c739..00000000000 --- a/theme/global/popup-center-styles.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const popupCenterStyles = { - '.mode__popup-center': { - '&, body': { - height: '100%', - width: '100%', - }, - }, -}; diff --git a/theme/global/popup-styles.ts b/theme/global/popup-styles.ts deleted file mode 100644 index 26897cd7dd3..00000000000 --- a/theme/global/popup-styles.ts +++ /dev/null @@ -1,24 +0,0 @@ -const maxWidth = '392px'; -const maxHeight = '600px'; - -export const popupStyles = { - '.mode__popup': { - 'html,body': { - minWidth: maxWidth, - maxWidth: maxWidth, - minHeight: maxHeight, - maxHeight: maxHeight, - scrollbarWidth: 'none', - - // Only add overflow scroll on non-firefox browsers - '@supports not (-moz-appearance: none)': { - overflowY: 'scroll', - }, - - '::-webkit-scrollbar': { - display: 'none', - width: 0, - }, - }, - }, -}; diff --git a/theme/recipes/button-recipe.ts b/theme/recipes/button-recipe.ts index ad3679f0ec1..8a881afd9bb 100644 --- a/theme/recipes/button-recipe.ts +++ b/theme/recipes/button-recipe.ts @@ -8,7 +8,7 @@ function loadingStyles(color: ColorToken) { animation: 'spin', border: '2px solid', borderColor: color, - borderTop: '2px solid', + borderBottomColor: 'transparent', boxSizing: 'border-box', content: '""', display: 'inline-block', @@ -112,7 +112,6 @@ export const buttonRecipe = defineRecipe({ }, }, - // TODO: Remove invert code invert: { true: {} }, fullWidth: { true: { width: '100%' } }, @@ -124,7 +123,6 @@ export const buttonRecipe = defineRecipe({ variant: 'solid', }, - // TODO: Remove invert code compoundVariants: [ { css: { diff --git a/theme/recipes/link-recipe.ts b/theme/recipes/link-recipe.ts index a9f919d7b28..ff259791e2b 100644 --- a/theme/recipes/link-recipe.ts +++ b/theme/recipes/link-recipe.ts @@ -93,7 +93,6 @@ export const linkRecipe = defineRecipe({ }, }, - // TODO: Remove invert code invert: { true: {} }, disabled: { true: {} }, fullWidth: { true: { width: '100%' } }, diff --git a/theme/tokens.ts b/theme/tokens.ts index c1e63d2f05a..69e2e82b6e9 100644 --- a/theme/tokens.ts +++ b/theme/tokens.ts @@ -6,18 +6,6 @@ import { colors } from './colors'; // ts-unused-exports:disable-next-line export const tokens = defineTokens({ ...leatherTokens, - // TODO: Update in mono repo - borders: { - action: { value: '1px solid {colors.ink.action-primary-default}' }, - active: { value: '2px solid {colors.ink.border-default}' }, - background: { value: '2px solid {colors.ink.background-primary}' }, - dashed: { value: '2px dashed {colors.ink.component-background-default}' }, - default: { value: '1px solid {colors.ink.border-default}' }, - error: { value: '1px solid {colors.red.border}' }, - focus: { value: '2px solid {colors.ink.action-primary-default}' }, - invert: { value: '1px solid {colors.invert}' }, - subdued: { value: '1px solid {colors.ink.text-subdued}' }, - warning: { value: '1px solid {colors.yellow.border}' }, - }, + colors, }); diff --git a/theme/typography.ts b/theme/typography.ts index 6f378cd773f..48dc8bd6db5 100644 --- a/theme/typography.ts +++ b/theme/typography.ts @@ -1,5 +1,5 @@ -import { getWebTextVariants } from '@leather-wallet/tokens'; +import { getExtensionTextVariants } from '@leather-wallet/tokens'; import { defineTextStyles } from '@pandacss/dev'; // ts-unused-exports:disable-next-line -export const textStyles = defineTextStyles({ ...getWebTextVariants() }); +export const textStyles = defineTextStyles({ ...getExtensionTextVariants() }); diff --git a/webpack/webpack.config.base.js b/webpack/webpack.config.base.js index 2a89b3ed952..5414cf7cb58 100755 --- a/webpack/webpack.config.base.js +++ b/webpack/webpack.config.base.js @@ -218,11 +218,6 @@ export const config = { filename: 'popup.html', ...HTML_PROD_OPTIONS, }), - new HtmlWebpackPlugin({ - template: path.join(SRC_ROOT_PATH, '../', 'public', 'html', 'popup-center.html'), - filename: 'popup-center.html', - ...HTML_PROD_OPTIONS, - }), new HtmlWebpackPlugin({ template: path.join(SRC_ROOT_PATH, '../', 'public', 'html', 'debug.html'), filename: 'debug.html',