diff --git a/.changeset/unlucky-geckos-doubt.md b/.changeset/unlucky-geckos-doubt.md new file mode 100644 index 00000000..7d82fb5b --- /dev/null +++ b/.changeset/unlucky-geckos-doubt.md @@ -0,0 +1,7 @@ +--- +'@stacks/connect-ui': minor +'@stacks/connect': minor +'@stacks/connect-react': minor +--- + +Add new UI to allow selecting which wallet to use diff --git a/.gitignore b/.gitignore index f33d1ab8..25b99a1a 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ packages/connect-ui/connect web-ext-artifacts docs/ + +storybook-static/ diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000..b5ab0454 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,4 @@ +module.exports = { + ...require('@stacks/prettier-config'), + plugins: ['prettier-plugin-tailwindcss'], +}; diff --git a/.vscode/settings.json b/.vscode/settings.json index 3662b370..25fa6215 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { "typescript.tsdk": "node_modules/typescript/lib" -} \ No newline at end of file +} diff --git a/package.json b/package.json index cfdd707c..5719884f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "workspaces": [ "packages/**" ], - "prettier": "@stacks/prettier-config", "scripts": { "build": "lerna run build", "build:connect": "lerna run build --scope @stacks/connect", @@ -30,7 +29,7 @@ "@stacks/eslint-config": "^2.0.0", "@stacks/prettier-config": "^0.0.10", "@stencil-community/eslint-plugin": "^0.5.0", - "concurrently": "^8.2.1", + "concurrently": "^8.2.2", "eslint": "^8.49.0", "eslint-import-resolver-typescript": "^3.6.0", "eslint-plugin-import": "^2.28.1", @@ -40,11 +39,12 @@ "eslint-plugin-unused-imports": "^3.0.0", "husky": "^8.0.3", "lerna": "^7.3.0", - "prettier": "^3.0.3", + "prettier": "^3.1.0", + "prettier-plugin-tailwindcss": "^0.5.7", "react": "^18.2.0", "react-dom": "^18.2.0", "tsup": "^7.2.0", - "typedoc": "^0.25.1", - "typescript": "^5.2.2" + "typedoc": "^0.25.3", + "typescript": "^5.3.2" } } diff --git a/packages/connect-react/package.json b/packages/connect-react/package.json index 3f88481a..55a6dfb9 100644 --- a/packages/connect-react/package.json +++ b/packages/connect-react/package.json @@ -2,7 +2,6 @@ "name": "@stacks/connect-react", "version": "22.2.2", "license": "MIT", - "prettier": "@stacks/prettier-config", "scripts": { "build": "concurrently 'tsup src/index.ts' 'yarn types'", "prepublishOnly": "yarn build", diff --git a/packages/connect-react/src/react/components/connect/context.tsx b/packages/connect-react/src/react/components/connect/context.tsx index 7c843866..90a8e813 100644 --- a/packages/connect-react/src/react/components/connect/context.tsx +++ b/packages/connect-react/src/react/components/connect/context.tsx @@ -5,7 +5,7 @@ enum States { UPDATE_AUTH_OPTIONS = 'data/update-auth-options', } -type Action = { type: string; payload?: any }; +type Action = { type: States; payload?: any }; type Dispatch = (action: Action) => void; @@ -35,7 +35,7 @@ const initialState: State = { }, }; -const connectReducer = (state: State, { type, payload }: { type: string; payload?: any }) => { +const connectReducer = (state: State, { type, payload }: { type: States; payload?: any }) => { switch (type) { case States.UPDATE_AUTH_OPTIONS: { return { diff --git a/packages/connect-ui/package.json b/packages/connect-ui/package.json index f8a73881..faa30547 100644 --- a/packages/connect-ui/package.json +++ b/packages/connect-ui/package.json @@ -2,7 +2,6 @@ "name": "@stacks/connect-ui", "version": "6.1.3", "license": "MIT", - "prettier": "@stacks/prettier-config", "scripts": { "build": "stencil build --docs", "dev": "stencil build --dev --watch", @@ -19,7 +18,10 @@ "devDependencies": { "@stencil/sass": "^1.5.2", "@types/node": "^14.6.0", - "puppeteer": "^9.0.0" + "puppeteer": "^9.0.0", + "stencil-tailwind-plugin": "^1.8.0", + "tailwindcss": "^3.3.5", + "tailwindcss-animate": "^1.0.7" }, "publishConfig": { "access": "public" diff --git a/packages/connect-ui/src/components.d.ts b/packages/connect-ui/src/components.d.ts index 43b8964c..e8177053 100644 --- a/packages/connect-ui/src/components.d.ts +++ b/packages/connect-ui/src/components.d.ts @@ -5,9 +5,12 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; +import { WebBTCProvider } from "./providers"; export namespace Components { interface ConnectModal { - "authOptions": any; + "callback": Function; + "defaultProviders": WebBTCProvider[]; + "installedProviders": WebBTCProvider[]; } } declare global { @@ -23,7 +26,9 @@ declare global { } declare namespace LocalJSX { interface ConnectModal { - "authOptions"?: any; + "callback"?: Function; + "defaultProviders"?: WebBTCProvider[]; + "installedProviders"?: WebBTCProvider[]; } interface IntrinsicElements { "connect-modal": ConnectModal; diff --git a/packages/connect-ui/src/components/modal/modal.scss b/packages/connect-ui/src/components/modal/modal.scss index f0cd14f4..fbb1fd2e 100644 --- a/packages/connect-ui/src/components/modal/modal.scss +++ b/packages/connect-ui/src/components/modal/modal.scss @@ -1,196 +1,17 @@ -@import 'browser-not-supported'; -@import 'hiro-wallet-download'; - -@mixin mobile { - @media (max-width: 640px) { - @content; - } -} - :host { all: initial; } .modal-container { - display: flex; - flex-direction: column; - background-color: rgba(0, 0, 0, 0.48); - width: 100%; - height: 100%; - position: fixed; - top: 0px; - left: 0px; - justify-content: center; + color: #74777D; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; - z-index: 8999; - overflow-y: scroll; } .modal-body { - width: 486px; - max-width: 100%; - max-height: calc(100% - 48px); - background: white; - margin-left: auto; - margin-right: auto; - border-radius: 12px; - padding: 20px 24px 20px; - box-sizing: border-box; - overflow-y: scroll; -ms-overflow-style: none; scrollbar-width: none; &::-webkit-scrollbar { display: none; } - - @include mobile { - max-height: 100%; - } -} - -.modal-header { - display: flex; - flex-direction: column; - - @include mobile { - flex-direction: row-reverse; - align-items: flex-start; - } -} - -.close-modal { - display: flex; - align-items: flex-end; - justify-content: flex-end; -} - -.close-icon { - cursor: pointer; -} - -.modal-content { - display: flex; - flex-direction: column; -} - -.modal-title { - color: #242629; - font-family: 'Open Sauce One', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, - sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; - font-weight: 500; - font-size: 32px; - line-height: 32px; - margin-bottom: 18px; - padding-right: 48px; - margin-top: 18px; - - @include mobile { - font-size: 24px; - line-height: 24px; - margin-bottom: 8px; - margin-top: 0px; - padding-right: 0px; - } -} - -.modal-subtitle { - font-size: 16px; - font-weight: 400; - line-height: 24px; - color: #74777d; - - @include mobile { - font-size: 13px; - } -} - -.download-app-container { - display: flex; - flex-direction: column; - margin-top: 20px; -} - -.button { - width: 100%; - max-width: 139px; - box-sizing: border-box; - border-radius: 8px; - font-size: 14px; - font-weight: 500; - line-height: 20px; - background-color: #5546ff; - color: #ffffff; - min-height: 48px; - min-width: 126px; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - -webkit-transition: all 250ms; - transition: all 250ms; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - white-space: nowrap; - outline: none; - border: none; - cursor: pointer; -} - -.link { - color: #5546ff; - font-size: 14px; - line-height: 20px; - cursor: pointer; - margin-top: 4px; -} - -.modal-subheading { - font-size: 16px; - font-weight: 600; - line-height: 19px; - color: #000000; -} - -.modal-wallet-card { - display: flex; - align-items: flex-start; - background: white; - border: 2px solid #f4f4f6; - border-radius: 16px; - margin-top: 12px; - padding: 24px 10px 24px 20px; -} - -.wallet-container { - display: flex; - flex-direction: column; - - @include mobile { - flex-direction: column-reverse; - } -} - -.modal-wallet-text { - font-size: 14px; - font-weight: 400; - line-height: 20px; - margin-top: 4px; - color: #74777d; - - a { - color: #5546ff; - text-decoration: none; - } - - @include mobile { - font-size: 13px; - } -} - -.modal-wallet-card-content { - display: flex; - flex-direction: column; - padding-left: 14px; } diff --git a/packages/connect-ui/src/components/modal/modal.tsx b/packages/connect-ui/src/components/modal/modal.tsx index 63355c05..bbffba11 100644 --- a/packages/connect-ui/src/components/modal/modal.tsx +++ b/packages/connect-ui/src/components/modal/modal.tsx @@ -1,21 +1,9 @@ -import { Component, h, Prop, State, Element } from '@stencil/core'; +import { Component, Element, Prop, h } from '@stencil/core'; +import { WebBTCProvider } from '../../providers'; +import { setSelectedProviderId } from '../../session'; import CloseIcon from './assets/close-icon.svg'; -import LeatherLogo from './assets/leather-logo.svg'; -import XverseWalletLogo from './assets/xverse-wallet-logo.svg'; import { getBrowser, getPlatform } from './utils'; -const CHROME_BROWSER_URL = 'https://www.google.com/chrome/'; -const BRAVE_BROWSER_URL = 'https://brave.com/'; -const FIREFOX_BROWSER_URL = 'https://www.mozilla.org/en-US/'; -const CHROME_STORE_URL = - 'https://chrome.google.com/webstore/detail/hiro-wallet/ldinpeekobnhjjdofggfgjlcehhmanlj/'; -const FIREFOX_STORE_URL = 'https://addons.mozilla.org/en-US/firefox/addon/hiro-wallet/'; -const XVERSE_APP_STORE_URL = 'https://apps.apple.com/app/id1552272513'; -const XVERSE_PLAY_STORE_URL = - 'https://play.google.com/store/apps/details?id=com.secretkeylabs.xverse'; -const XVERSE_CHROME_STORE_URL = - 'https://chrome.google.com/webstore/detail/xverse-wallet/idnnbdplmphpflfnlkomgpfbpcgelopg'; - @Component({ tag: 'connect-modal', styleUrl: 'modal.scss', @@ -23,177 +11,205 @@ const XVERSE_CHROME_STORE_URL = shadow: true, }) export class Modal { - @Prop() authOptions: any; // AuthOptions (would lead to circular dependency) - - @State() hasOpenedInstall: boolean; + @Prop() defaultProviders: WebBTCProvider[]; + @Prop() installedProviders: WebBTCProvider[]; - @State() hasOpenedInstallXverse: boolean; + @Prop() callback: Function; @Element() modalEl: HTMLConnectModalElement; + handleSelectProvider(providerId: string) { + setSelectedProviderId(providerId); + this.modalEl.remove(); + this.callback(); + } + handleCloseModal() { this.modalEl.remove(); - this.authOptions.onCancel?.(); + // todo: throw Error that website can catch and handle (e.g. ConnectCancelError) } - handleDownloadPath(browser: string) { + // todo: nice to have: + // getComment(provider: WebBTCProvider, browser: string, isMobile?: string) { + // if (!provider) return null; + + // const hasExtension = this.getBrowserUrl(provider); + // const hasMobile = this.getMobileUrl(provider); + + // if (isMobile && hasExtension && !hasMobile) return 'Extension Only'; + // if (!isMobile && !hasExtension && hasMobile) return 'Mobile Only'; + + // if (!isMobile && !browser) return 'Current browser not supported'; + + // return null; + // } + + getBrowserUrl(provider: WebBTCProvider) { + return provider.chromeWebStoreUrl ?? provider.mozillaAddOnsUrl; + } + + getMobileUrl(provider: WebBTCProvider) { + return provider.iOSAppStoreUrl ?? provider.googlePlayStoreUrl; + } + + getInstallUrl(provider: WebBTCProvider, browser: string) { if (browser === 'Chrome') { - window.open(CHROME_STORE_URL, '_blank'); + return provider.chromeWebStoreUrl ?? this.getMobileUrl(provider) ?? provider.webUrl; } else if (browser === 'Firefox') { - window.open(FIREFOX_STORE_URL, '_blank'); + return provider.mozillaAddOnsUrl ?? this.getMobileUrl(provider) ?? provider.webUrl; } else if (browser === 'IOS') { - window.open(XVERSE_APP_STORE_URL, '_blank'); - this.hasOpenedInstallXverse = true; - return; + return provider.iOSAppStoreUrl ?? this.getBrowserUrl(provider) ?? provider.webUrl; } else if (browser === 'Android') { - window.open(XVERSE_PLAY_STORE_URL, '_blank'); - this.hasOpenedInstallXverse = true; - return; - } else if (browser === 'Xverse-Chrome') { - window.open(XVERSE_CHROME_STORE_URL, '_blank'); - this.hasOpenedInstallXverse = true; - return; + return provider.googlePlayStoreUrl ?? this.getBrowserUrl(provider) ?? provider.webUrl; } else { - window.open('https://www.hiro.so/wallet/install-web', '_blank'); + return this.getBrowserUrl(provider) ?? provider.webUrl ?? this.getMobileUrl(provider); } - this.hasOpenedInstall = true; } render() { const browser = getBrowser(); - const isMobile = getPlatform(); + const mobile = getPlatform(); + + const notInstalledProviders = this.defaultProviders.filter( + p => this.installedProviders.findIndex(i => i.id === p.id) === -1 // keep providers NOT already in installed list + ); + + const hasInstalled = this.installedProviders.length > 0; + const hasMore = notInstalledProviders.length > 0; return ( -
Select the wallet you want to connect to.
) : ( - Your browser isn't supported ++ You don't have any wallets in your browser that support this app. You need to + install a wallet to proceed. +
)}Installed wallets
+Other wallets
+ ) : ( +Recommended wallets
+ + {/* QUESTION MARK ICON */} + ++ What is a wallet? ↗ +
+