diff --git a/package.json b/package.json index 48c0eee9f..2981bfc4c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "workspaces": [ "packages/sdk-socket-server-next", "packages/sdk-install-modal-web", + "packages/sdk-multichain-ui", "packages/sdk-communication-layer", "packages/sdk", "packages/sdk-react", diff --git a/packages/sdk-multichain-ui/.change b/packages/sdk-multichain-ui/.change new file mode 100644 index 000000000..0f7217737 --- /dev/null +++ b/packages/sdk-multichain-ui/.change @@ -0,0 +1 @@ +0.30.2 diff --git a/packages/sdk-multichain-ui/.eslintrc.js b/packages/sdk-multichain-ui/.eslintrc.js new file mode 100644 index 000000000..2c588227f --- /dev/null +++ b/packages/sdk-multichain-ui/.eslintrc.js @@ -0,0 +1,62 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + + // extends: ['@metamask/eslint-config'], + // TODO check if we can integrate to @metamask/eslint-config + extends: ['eslint:recommended'], + + parser: '@typescript-eslint/parser', + + ignorePatterns: [ + '.eslintrc.js', + 'dist', + 'rollup.config.js', + '**/coverage/**', + 'postcss.config.js', + 'jest-preload.js', + 'loader', + '.stencil', + 'stencil.config.ts', + ], + + parserOptions: { + project: true, + // sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + // ecmaVersion: 2018, + tsconfigRootDir: __dirname, + }, + + plugins: ['@typescript-eslint', 'react'], + + env: { + browser: true, + node: true, + 'shared-node-browser': true, + }, + + overrides: [ + { + files: ['**/*.ts', '**/*.tsx'], + rules: { + 'no-restricted-syntax': 'off', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['error'], + '@typescript-eslint/consistent-type-exports': [ + 'error', + { + fixMixedExportsWithInlineTypeSpecifier: true, + }, + ], + '@typescript-eslint/consistent-type-definitions': [ + 'error', + 'interface', + ], + 'import/no-named-as-default': 0, + }, + }, + ], +}; diff --git a/packages/sdk-multichain-ui/.gitignore b/packages/sdk-multichain-ui/.gitignore new file mode 100644 index 000000000..c79078fdb --- /dev/null +++ b/packages/sdk-multichain-ui/.gitignore @@ -0,0 +1,85 @@ +packages/*/yarn.lock +packages/*/package-lock.json + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +junit.xml + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules +examples/biz-website/public/ +examples/blog/public/ +*.un~ +dist +bin/published.js + + +test/**/public +.gatsby-context.js +.DS_Store +node_modules/ +.cache/ +.netlify + +# IDE specific +.idea/ +.vscode/ +*.sw* + +# misc +.serverless/ + +# lock files +yarn.lock +package-lock.json + +# == Stencil == +dist/ +www/ +loader/ + +*~ +*.sw[mnpcod] +*.log +*.lock +*.tmp +*.tmp.* +log.txt +*.sublime-project +*.sublime-workspace + +.stencil/ +.idea/ +.vscode/ +.sass-cache/ +.versions/ +node_modules/ +$RECYCLE.BIN/ + +.DS_Store +Thumbs.db +UserInterfaceState.xcuserstate +.env +# ==== \ No newline at end of file diff --git a/packages/sdk-multichain-ui/.prettierrc.js b/packages/sdk-multichain-ui/.prettierrc.js new file mode 100644 index 000000000..b2d98d2ee --- /dev/null +++ b/packages/sdk-multichain-ui/.prettierrc.js @@ -0,0 +1,8 @@ +// All of these are defaults except singleQuote, but we specify them +// for explicitness +module.exports = { + quoteProps: 'as-needed', + singleQuote: true, + tabWidth: 2, + trailingComma: 'all', +}; diff --git a/packages/sdk-multichain-ui/.size-limit.json b/packages/sdk-multichain-ui/.size-limit.json new file mode 100644 index 000000000..87b504d9e --- /dev/null +++ b/packages/sdk-multichain-ui/.size-limit.json @@ -0,0 +1,6 @@ +[ + { + "path": "dist/components/*.js", + "limit": "30 KB" + } +] diff --git a/packages/sdk-multichain-ui/CHANGELOG.md b/packages/sdk-multichain-ui/CHANGELOG.md new file mode 100644 index 000000000..3cab0227d --- /dev/null +++ b/packages/sdk-multichain-ui/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[Unreleased]: https://github.com/MetaMask/metamask-sdk/ diff --git a/packages/sdk-multichain-ui/LICENSE b/packages/sdk-multichain-ui/LICENSE new file mode 100644 index 000000000..f245dcd0e --- /dev/null +++ b/packages/sdk-multichain-ui/LICENSE @@ -0,0 +1,18 @@ +Copyright ConsenSys Software Inc. 2022. All rights reserved. + +You acknowledge and agree that ConsenSys Software Inc. (“ConsenSys”) (or ConsenSys’s licensors) own all legal right, title and interest in and to the work, software, application, source code, documentation and any other documents in this repository (collectively, the “Program”), including any intellectual property rights which subsist in the Program (whether those rights happen to be registered or not, and wherever in the world those rights may exist), whether in source code or any other form. + +Subject to the limited license below, you may not (and you may not permit anyone else to) distribute, publish, copy, modify, merge, combine with another program, create derivative works of, reverse engineer, decompile or otherwise attempt to extract the source code of, the Program or any part thereof, except that you may contribute to this repository. + +You are granted a non-exclusive, non-transferable, non-sublicensable license to distribute, publish, copy, modify, merge, combine with another program or create derivative works of the Program (such resulting program, collectively, the “Resulting Program”) solely for Non-Commercial Use as long as you: + 1. give prominent notice (“Notice”) with each copy of the Resulting Program that the Program is used in the Resulting Program and that the Program is the copyright of ConsenSys; and + 2. subject the Resulting Program and any distribution, publication, copy, modification, merger therewith, combination with another program or derivative works thereof to the same Notice requirement and Non-Commercial Use restriction set forth herein. + +“Non-Commercial Use” means each use as described in clauses (1)-(3) below, as reasonably determined by ConsenSys in its sole discretion: + 1. personal use for research, personal study, private entertainment, hobby projects or amateur pursuits, in each case without any anticipated commercial application; + 2. use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization or government institution; or + 3. the number of monthly active users of the Resulting Program across all versions thereof and platforms globally do not exceed 10,000 at any time. + +You will not use any trade mark, service mark, trade name, logo of ConsenSys or any other company or organization in a way that is likely or intended to cause confusion about the owner or authorized user of such marks, names or logos. + +If you have any questions, comments or interest in pursuing any other use cases, please reach out to us at metamask.license@consensys.net. diff --git a/packages/sdk-multichain-ui/README.md b/packages/sdk-multichain-ui/README.md new file mode 100644 index 000000000..6ff73c155 --- /dev/null +++ b/packages/sdk-multichain-ui/README.md @@ -0,0 +1 @@ +# sdk-install-modal-web diff --git a/packages/sdk-multichain-ui/jest-preload.js b/packages/sdk-multichain-ui/jest-preload.js new file mode 100644 index 000000000..ac2409d0e --- /dev/null +++ b/packages/sdk-multichain-ui/jest-preload.js @@ -0,0 +1,8 @@ +global.console = { + ...console, + log: jest.fn(), + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), +}; diff --git a/packages/sdk-multichain-ui/package.json b/packages/sdk-multichain-ui/package.json new file mode 100644 index 000000000..973467ed1 --- /dev/null +++ b/packages/sdk-multichain-ui/package.json @@ -0,0 +1,93 @@ +{ + "name": "@metamask/sdk-multichain-ui", + "version": "0.32.1", + "description": "MetaMask SDK Install Modal for Web", + "homepage": "https://github.com/MetaMask/metamask-sdk#readme", + "bugs": { + "url": "https://github.com/MetaMask/metamask-sdk/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/metamask-sdk.git", + "directory": "packages/sdk-multichain-ui" + }, + "author": "MetaMask", + "packageManager": "yarn@3.5.0", + "main": "./dist/index.cjs.js", + "module": "./dist/index.js", + "collection": "dist/collection/collection-manifest.json", + "types": "dist/types/index.d.ts", + "files": [ + "./dist" + ], + "scripts": { + "allow-scripts": "", + "generate": "stencil generate", + "build:types": "tsc --project tsconfig.json --emitDeclarationOnly --outDir dist/types", + "build:clean": "yarn clean && yarn build", + "dev": "stencil build --dev --watch --serve", + "build": "stencil build --prod", + "build:dev": "NODE_ENV=development stencil build --debug --dev", + "build:tsc": "tsc --build tsconfig.json --verbose", + "build:watch": "stencil build --watchAll", + "build:post-tsc": "echo 'N/A'", + "build:pre-tsc": "echo 'N/A'", + "build:release": "yarn version && stencil build", + "clean": "rimraf ./dist && rimraf ./loader", + "lint": "yarn lint:eslint && yarn lint:misc --check", + "lint:changelog": "../../scripts/validate-changelog.sh @metamask/sdk-multichain-ui", + "lint:eslint": "eslint . --cache --ext js,ts", + "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", + "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' --ignore-path ../../.gitignore", + "prepack": "../../scripts/prepack.sh", + "publish:preview": "yarn npm publish --tag preview", + "reset": "yarn clean && rimraf ./node_modules/", + "start": "stencil build --dev --watch --serve", + "test": "stencil test --spec --e2e", + "test:watch": "stencil test --spec --e2e --watchAll", + "test:ci": "jest --coverage --passWithNoTests --setupFilesAfterEnv ./jest-preload.js", + "preversion": "yarn test", + "postversion": "git push --tags && yarn publish . --tag $npm_package_version && git push && echo \"Successfully released version $npm_package_version!\"" + }, + "devDependencies": { + "@lavamoat/allow-scripts": "^2.3.1", + "@metamask/auto-changelog": "3.1.0", + "@size-limit/preset-big-lib": "^11.0.2", + "@stencil/core": "^4.22.2", + "@types/i18n": "^0.13.12", + "@types/jest": "^29.5.14", + "@types/node": "^22.9.0", + "@types/prettier": "^2", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "@typescript-eslint/parser": "^5.56.0", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jest": "^24.4.0", + "eslint-plugin-jsdoc": "^44.2.4", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-react": "^7.32.2", + "jest": "^29.6.4", + "prettier": "^2.8.8", + "puppeteer": "^23.8.0", + "rimraf": "^5.0.0", + "rollup-plugin-visualizer": "^5.12.0", + "size-limit": "^11.1.6", + "typescript": "^5.6.3" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "lavamoat": { + "allowScripts": { + "webpack-dev-server>ws>bufferutil": false, + "webpack-dev-server>ws>utf-8-validate": false + } + }, + "dependencies": { + "@paulmillr/qr": "^0.2.1", + "qr-code-styling": "^1.9.2" + } +} diff --git a/packages/sdk-multichain-ui/rollup.config.js b/packages/sdk-multichain-ui/rollup.config.js new file mode 100644 index 000000000..6605bb9ed --- /dev/null +++ b/packages/sdk-multichain-ui/rollup.config.js @@ -0,0 +1,72 @@ +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import typescript from 'rollup-plugin-typescript2'; +import terser from '@rollup/plugin-terser'; +import { visualizer } from 'rollup-plugin-visualizer'; +import external from 'rollup-plugin-peer-deps-external'; +import postcss from 'rollup-plugin-postcss'; + +// Check if environment variable is set to 'dev' +const isDev = process.env.NODE_ENV === 'dev'; + +const packageJson = require('./package.json'); + +/** + * @type {import('rollup').RollupOptions} + */ +const config = [ + { + input: 'src/index.ts', + output: [ + { + file: packageJson.module, + format: 'es', + sourcemap: true, + }, + { + file: packageJson.unpkg, + format: 'umd', + exports: 'named', + name: 'MetaMaskSDKInstallModal', + sourcemap: true + }, + { + file: packageJson.main, + format: 'cjs', + sourcemap: true, + }, + ], + external: [], + plugins: [ + external(), + resolve({ + browser: true, + extensions: ['.ts', '.tsx'] + }), + commonjs(), + typescript({ + tsconfigOverride: { + compilerOptions: { + importHelpers: true, + noEmitHelpers: true, + }, + }, + useTsconfigDeclarationDir: true, + }), + postcss({ + extract: true, + plugins: [], + sourceMap: true, + modules: true, + }), + terser(), + isDev && visualizer({ + filename: `bundle_stats/${packageJson.version}/stats.html`, + gzipSize: true, + brotliSize: true, + }), + ].filter(Boolean), + }, +]; + +export default config; diff --git a/packages/sdk-multichain-ui/src/assets/fonts/Euclid Circular B Regular.ttf b/packages/sdk-multichain-ui/src/assets/fonts/Euclid Circular B Regular.ttf new file mode 100644 index 000000000..380c7ea81 Binary files /dev/null and b/packages/sdk-multichain-ui/src/assets/fonts/Euclid Circular B Regular.ttf differ diff --git a/packages/sdk-multichain-ui/src/assets/fox.svg b/packages/sdk-multichain-ui/src/assets/fox.svg new file mode 100644 index 000000000..c9b619a9d --- /dev/null +++ b/packages/sdk-multichain-ui/src/assets/fox.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/sdk-multichain-ui/src/components.d.ts b/packages/sdk-multichain-ui/src/components.d.ts new file mode 100644 index 000000000..22b628c7a --- /dev/null +++ b/packages/sdk-multichain-ui/src/components.d.ts @@ -0,0 +1,67 @@ +/* eslint-disable */ +/* tslint:disable */ +/** + * This is an autogenerated file created by the Stencil compiler. + * It contains typing information for all components that exist in this project. + */ +import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; +export namespace Components { + interface MmInstallModal { + /** + * The QR code link + */ + "link": string; + "preferDesktop": boolean; + "sdkVersion"?: string; + } +} +export interface MmInstallModalCustomEvent extends CustomEvent { + detail: T; + target: HTMLMmInstallModalElement; +} +declare global { + interface HTMLMmInstallModalElementEventMap { + "close": { shouldTerminate?: boolean }; + "startDesktopOnboarding": any; + } + interface HTMLMmInstallModalElement extends Components.MmInstallModal, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLMmInstallModalElement, ev: MmInstallModalCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLMmInstallModalElement, ev: MmInstallModalCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLMmInstallModalElement: { + prototype: HTMLMmInstallModalElement; + new (): HTMLMmInstallModalElement; + }; + interface HTMLElementTagNameMap { + "mm-install-modal": HTMLMmInstallModalElement; + } +} +declare namespace LocalJSX { + interface MmInstallModal { + /** + * The QR code link + */ + "link"?: string; + "onClose"?: (event: MmInstallModalCustomEvent<{ shouldTerminate?: boolean }>) => void; + "onStartDesktopOnboarding"?: (event: MmInstallModalCustomEvent) => void; + "preferDesktop"?: boolean; + "sdkVersion"?: string; + } + interface IntrinsicElements { + "mm-install-modal": MmInstallModal; + } +} +export { LocalJSX as JSX }; +declare module "@stencil/core" { + export namespace JSX { + interface IntrinsicElements { + "mm-install-modal": LocalJSX.MmInstallModal & JSXBase.HTMLAttributes; + } + } +} diff --git a/packages/sdk-multichain-ui/src/components/misc/CloseButton.tsx b/packages/sdk-multichain-ui/src/components/misc/CloseButton.tsx new file mode 100644 index 000000000..da5920a93 --- /dev/null +++ b/packages/sdk-multichain-ui/src/components/misc/CloseButton.tsx @@ -0,0 +1,10 @@ +import { h } from "@stencil/core"; + +const CloseButton = () => ( + + + + +); + +export default CloseButton; diff --git a/packages/sdk-multichain-ui/src/components/misc/InstallIcon.tsx b/packages/sdk-multichain-ui/src/components/misc/InstallIcon.tsx new file mode 100644 index 000000000..85d438289 --- /dev/null +++ b/packages/sdk-multichain-ui/src/components/misc/InstallIcon.tsx @@ -0,0 +1,10 @@ +import { h } from "@stencil/core"; + +const InstallIcon = () => ( + + + + +); + +export default InstallIcon; diff --git a/packages/sdk-multichain-ui/src/components/misc/SDKVersion.tsx b/packages/sdk-multichain-ui/src/components/misc/SDKVersion.tsx new file mode 100644 index 000000000..35547b8ba --- /dev/null +++ b/packages/sdk-multichain-ui/src/components/misc/SDKVersion.tsx @@ -0,0 +1,7 @@ +import { h } from "@stencil/core"; + +export default function SDKVersion({version}: {version?: string}) { + return ( +
SDK Version {version ? `v${version}`:`unknown`}
+ ) +} diff --git a/packages/sdk-multichain-ui/src/components/misc/simple-i18n.ts b/packages/sdk-multichain-ui/src/components/misc/simple-i18n.ts new file mode 100644 index 000000000..a794b2b37 --- /dev/null +++ b/packages/sdk-multichain-ui/src/components/misc/simple-i18n.ts @@ -0,0 +1,125 @@ +interface I18nInstance { + t: (key: string) => string; + init: (config: { fallbackLng: string }) => Promise; +} + +interface TranslationDict { + [key: string]: string | TranslationDict; +} + +const defaultTranslations: TranslationDict = { + + /** + * In use + */ + "CONNECT_WITH_METAMASK": "Connect with Metamask", + "USE_EXTENSION": "Use extension", + "USE_MOBILE":"Use mobile", + "ONE_CLICK_CONNECT": "Connect in one click to the MetaMask extension.", + "CONNECT_WITH_EXTENSION": "Connect With Extension", + "SCAN_TO_CONNECT": "Scan to connect with the MetaMask mobile app.", + /** + * In use + */ + + "DESKTOP": "Desktop", + "MOBILE": "Mobile", + "META_MASK_MOBILE_APP": "MetaMask mobile app", + "INSTALL_MODAL": { + "TRUSTED_BY_USERS": "Trusted by over 30 million users to buy, store, send and swap crypto securely", + "LEADING_CRYPTO_WALLET": "The leading crypto wallet & gateway to blockchain apps built on Ethereum Mainnet, Polygon, Optimism, and many other networks", + "CONTROL_DIGITAL_INTERACTIONS": "Puts you in control of your digital interactions by making power of cryptography more accessible", + "INSTALL_META_MASK_EXTENSION_BUTTON": "Install MetaMask Extension", + "INSTALL_META_MASK_EXTENSION_TEXT": "Install and try the MetaMask browser extension." + }, + "PENDING_MODAL": { + "OPEN_META_MASK_SELECT_CODE": "Please open the MetaMask wallet app and select the code on the screen OR disconnect", + "OPEN_META_MASK_CONTINUE": "Open the MetaMask app to continue with your session.", + "NUMBER_AFTER_OPEN_NOTICE": "If a number doesn't appear after opening MetaMask, please click disconnect and re-scan the QRCode.", + "DISCONNECT": "Disconnect" + }, + "SELECT_MODAL": { + "CRYPTO_TAKE_CONTROL_TEXT": "Take control of your crypto and explore the blockchain with the wallet trusted by over 30 million people worldwide" + }, + "META_MASK_MODAL": { + "ADDRESS_COPIED": "Address copied to clipboard!", + "DISCONNECT": "Disconnect", + "ACTIVE_NETWORK": "Active Network" + } +} as const; + +export class SimpleI18n implements I18nInstance { + private translations: TranslationDict = defaultTranslations; + private supportedLocales: string[] = ['es', 'fr', 'he', 'it', 'pt', 'tr']; + private baseUrl: string; + + constructor(config?: { baseUrl?: string }) { + this.baseUrl = config?.baseUrl ?? 'https://raw.githubusercontent.com/MetaMask/metamask-sdk/refs/heads/gh-pages/locales'; + } + + private getBrowserLanguage(): string { + // Get all browser languages in order of preference + const browserLanguages = navigator.languages || [navigator.language]; + + // Check if English is one of the preferred languages + const hasEnglish = browserLanguages.some(lang => + lang.toLowerCase().startsWith('en') + ); + + // If user understands English, use it + if (hasEnglish) { + return 'en'; + } + + // Otherwise, check for other supported languages + const primaryLang = navigator.language; + const shortLang = primaryLang.toLowerCase().split('-')[0]; + if (this.supportedLocales.includes(shortLang)) { + return shortLang; + } + + return 'en'; + } + + async init(config: { fallbackLng: string }): Promise { + const browserLang = this.getBrowserLanguage(); + const locale = browserLang || config.fallbackLng; + await this.loadTranslations(locale); + } + + async loadTranslations(locale: string): Promise { + const shortLocale = locale.split('-')[0]; + + if (shortLocale === 'en' || !this.supportedLocales.includes(shortLocale)) { + this.translations = defaultTranslations; + return; + } + + try { + const url = `${this.baseUrl}/${shortLocale}.json`; + const response = await fetch(url); + + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + this.translations = await response.json(); + } catch (error) { + console.warn(`❌ Failed to load ${shortLocale} translations, falling back to English:`, error); + this.translations = defaultTranslations; + } + } + + t(key: string): string { + return this.getNestedTranslation(key, this.translations) || key; + } + + private getNestedTranslation(key: string, dict: TranslationDict): string { + const parts = key.split('.'); + let current: TranslationDict | string = dict; + + for (const part of parts) { + if (typeof current !== 'object') return ''; + current = current[part]; + } + + return typeof current === 'string' ? current : ''; + } +} diff --git a/packages/sdk-multichain-ui/src/components/mm-install-modal/mm-install-modal.tsx b/packages/sdk-multichain-ui/src/components/mm-install-modal/mm-install-modal.tsx new file mode 100644 index 000000000..1bb1ebe9f --- /dev/null +++ b/packages/sdk-multichain-ui/src/components/mm-install-modal/mm-install-modal.tsx @@ -0,0 +1,219 @@ +import { Component, Prop, h, Event, EventEmitter, State, Element } from '@stencil/core'; +import { WidgetWrapper } from '../widget-wrapper/widget-wrapper'; +import InstallIcon from '../misc/InstallIcon'; +import SDKVersion from '../misc/SDKVersion'; +import CloseButton from '../misc/CloseButton'; + +import QRCodeStyling, { + DrawType, + DotType, + CornerSquareType, + Options, + ErrorCorrectionLevel, + Mode, + TypeNumber, +} from "qr-code-styling"; + +import { SimpleI18n } from '../misc/simple-i18n'; +import SVG from '../../assets/fox.svg' + +@Component({ + tag: 'mm-install-modal', + styleUrl: '../style.css', + shadow: true, +}) +export class InstallModal { + /** + * The QR code link + */ + @Prop() link: string; + + @Prop() sdkVersion?: string; + + @Prop() preferDesktop: boolean; + + private i18nInstance: SimpleI18n; + private qrCodeContainer: HTMLDivElement | undefined; + + @Event() close: EventEmitter<{ shouldTerminate?: boolean }>; + + @Event() startDesktopOnboarding: EventEmitter; + + @Element() el: HTMLElement; + + @State() private translationsLoaded: boolean = false; + + constructor() { + this.onClose = this.onClose.bind(this); + this.onStartDesktopOnboardingHandler = this.onStartDesktopOnboardingHandler.bind(this); + this.render = this.render.bind(this); + + this.i18nInstance = new SimpleI18n(); + } + + componentDidLoad() { + this.generateQRCode(); + } + + async connectedCallback() { + await this.i18nInstance.init({ + fallbackLng: 'en' + }); + this.translationsLoaded = true; + } + + private generateQRCode() { + if (!this.qrCodeContainer || !this.link) { + return; + } + + const options: Options = { + width: 165, + height: 165, + type: 'svg' as DrawType, + data: this.link, + image: SVG, + imageOptions: { + hideBackgroundDots: true, + imageSize:0.7, + crossOrigin: 'anonymous', + margin:5 + }, + dotsOptions: { + color: '#222222', + type: 'square' as DotType, + gradient: undefined, + roundSize: false, + }, + cornersSquareOptions: { + color: '#222222', + type: 'square' as CornerSquareType, + gradient: undefined + }, + cornersDotOptions: { + color: '#ff5c16', + }, + backgroundOptions: { + color: 'transparent', + }, + qrOptions: { + typeNumber: 0 as TypeNumber, + mode: 'Byte' as Mode, + errorCorrectionLevel: 'Q' as ErrorCorrectionLevel + }, + }; + + const qrCode = new QRCodeStyling(options); + + this.qrCodeContainer.innerHTML = ''; + + // Append the QR code to the container + qrCode.append(this.qrCodeContainer); + } + + onClose(shouldTerminate = false) { + this.close.emit({ shouldTerminate }); + } + + onStartDesktopOnboardingHandler() { + this.startDesktopOnboarding.emit(); + } + + render() { + if (!this.translationsLoaded) { + return null; // or a loading state + } + + const t = (key: string) => this.i18nInstance.t(key); + + // Determine which section should be shown first based on preferDesktop + const showExtensionFirst = this.preferDesktop; + + return ( + +
this.onClose(true)}>
+