diff --git a/cspell.json b/cspell.json index cb8afe24d192..5091651edb21 100644 --- a/cspell.json +++ b/cspell.json @@ -6,8 +6,8 @@ "./packages/app/postcss.config.cjs", "./packages/app/src/styles/index.css", "./packages/app/tailwind.config.cjs", - "./packages/web3-contracts/types/**", "./packages/icons/countries/**", + "./packages/web3-contracts/types/**", ".github", ".gitignore", ".prettierignore", @@ -108,6 +108,7 @@ "fbid", "filelist", "fileservice", + "finalised", "flashloan", "flowns", "forcaster", @@ -259,12 +260,14 @@ "sbch", "scamsniffer", "scenechange", + "scrl", "scroller", "secp", "serializers", "shiden", "siddomains", "sidjs", + "signless", "sinonjs", "sniffings", "solana", @@ -302,6 +305,7 @@ "typeson", "unencrypted", "unfollow", + "unfollower", "unreviewed", "unstake", "unstoppabledomains", diff --git a/lint-staged.config.js b/lint-staged.config.js index 893e2297697f..2569fd4bea8d 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,7 +1,6 @@ export default { '*': 'prettier --write --ignore-unknown', 'packages/**/*.{ts,tsx,js,jsx}': 'eslint --fix', - 'packages/web3-constants/**/*': () => 'pnpm --filter ./packages/web3-constants start', 'cspell.json': () => 'gulp reorder-spellcheck', 'packages/**/*.svg': 'npx svgo', } diff --git a/package.json b/package.json index 7854dce6f431..0bc4f2577a4d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "yarn": ">=999.0.0", "npm": ">=999.0.0" }, - "version": "2.22.0", + "version": "2.23.12", "private": true, "license": "AGPL-3.0-or-later", "scripts": { @@ -24,18 +24,16 @@ "lint:ci-report": "pnpm run lint:ci --format=junit --output-file=reports/junit/eslint-results.xml --max-warnings=0", "prepare": "husky install", "svgo": "svgo packages/icons/*/*.svg", - "test": "vitest", + "test": "vitest --no-threads", "spellcheck": "cspell lint --no-must-find-files", "clean": "npx gulp clean" }, "dependencies": { "@dimensiondev/holoflows-kit": "0.9.0-20230307045856-46252fb", - "@emotion/cache": "11.10.3", - "@emotion/react": "11.10.4", - "@emotion/serialize": "1.1.0", - "@emotion/server": "11.10.0", - "@emotion/styled": "11.10.4", - "@emotion/utils": "1.2.0", + "@emotion/cache": "11.11.0", + "@emotion/react": "11.11.1", + "@emotion/serialize": "1.1.2", + "@emotion/styled": "11.11.0", "@masknet/kit": "0.1.2", "@mui/base": "5.0.0-alpha.100", "@mui/icons-material": "5.10.6", @@ -131,9 +129,6 @@ "@uniswap/v3-sdk@3.9.0": "patches/@uniswap__v3-sdk@3.9.0.patch", "@ceramicnetwork/rpc-transport@0.3.1": "patches/@ceramicnetwork__rpc-transport@0.3.1.patch", "gulp@4.0.2": "patches/gulp@4.0.2.patch", - "@emotion/use-insertion-effect-with-fallbacks@1.0.0": "patches/@emotion__use-insertion-effect-with-fallbacks@1.0.0.patch", - "@emotion/cache@11.10.3": "patches/@emotion__cache@11.10.3.patch", - "@emotion/react@11.10.4": "patches/@emotion__react@11.10.4.patch", "@mui/base@5.0.0-alpha.100": "patches/@mui__base@5.0.0-alpha.100.patch", "micromark@3.1.0": "patches/micromark@3.1.0.patch", "micromark-util-symbol@1.0.1": "patches/micromark-util-symbol@1.0.1.patch", diff --git a/packages/app/src/pages/ApplicationPage.tsx b/packages/app/src/pages/ApplicationPage.tsx index 7f12c635c856..df373cb39ffa 100644 --- a/packages/app/src/pages/ApplicationPage.tsx +++ b/packages/app/src/pages/ApplicationPage.tsx @@ -6,7 +6,11 @@ export interface ApplicationsPageProps {} export default function ApplicationsPage(props: ApplicationsPageProps) { return ( - + []} + allPersonas={[]} + personaPerSiteConnectStatusLoading={false} + /> ) } diff --git a/packages/icons/brands/DiscordRoundBlack.dark.svg b/packages/icons/brands/DiscordRoundBlack.dark.svg new file mode 100644 index 000000000000..c7fe1db57c16 --- /dev/null +++ b/packages/icons/brands/DiscordRoundBlack.dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/icons/brands/DiscordRoundBlack.light.svg b/packages/icons/brands/DiscordRoundBlack.light.svg new file mode 100644 index 000000000000..b4b6c04f8ce5 --- /dev/null +++ b/packages/icons/brands/DiscordRoundBlack.light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/icons/general/LinearCalendar.dark.svg b/packages/icons/general/LinearCalendar.dark.svg new file mode 100644 index 000000000000..b4fec0e7ebcd --- /dev/null +++ b/packages/icons/general/LinearCalendar.dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/icons/general/LinearCalendar.svg b/packages/icons/general/LinearCalendar.light.svg similarity index 100% rename from packages/icons/general/LinearCalendar.svg rename to packages/icons/general/LinearCalendar.light.svg diff --git a/packages/icons/general/Lock.svg b/packages/icons/general/Lock.svg index 79a1f7e5de78..95c3b16efb9c 100644 --- a/packages/icons/general/Lock.svg +++ b/packages/icons/general/Lock.svg @@ -1 +1,6 @@ - \ No newline at end of file + + + + + + diff --git a/packages/icons/general/RadioButtonChecked.svg b/packages/icons/general/RadioButtonChecked.svg index 7e0292772f2c..c9ad5db75c3b 100644 --- a/packages/icons/general/RadioButtonChecked.svg +++ b/packages/icons/general/RadioButtonChecked.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/packages/icons/general/SharpMask.svg b/packages/icons/general/SharpMask.svg index 4b9c6d888739..796f8e56a48a 100644 --- a/packages/icons/general/SharpMask.svg +++ b/packages/icons/general/SharpMask.svg @@ -1,3 +1,3 @@ - - + + diff --git a/packages/icons/general/WebBlack.dark.svg b/packages/icons/general/WebBlack.dark.svg new file mode 100644 index 000000000000..8842eae50b96 --- /dev/null +++ b/packages/icons/general/WebBlack.dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/icons/general/WebBlack.light.svg b/packages/icons/general/WebBlack.light.svg new file mode 100644 index 000000000000..2c3235b7ed41 --- /dev/null +++ b/packages/icons/general/WebBlack.light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/icons/icon-generated-as-jsx.js b/packages/icons/icon-generated-as-jsx.js index 3888c1a989bd..8b27c3581316 100644 --- a/packages/icons/icon-generated-as-jsx.js +++ b/packages/icons/icon-generated-as-jsx.js @@ -98,6 +98,16 @@ export const DiscordRound = /*#__PURE__*/ __createIcon('DiscordRound', [ u: () => new URL('./brands/DiscordRound.svg', import.meta.url), }, ]) +export const DiscordRoundBlack = /*#__PURE__*/ __createIcon('DiscordRoundBlack', [ + { + c: ['dark'], + u: () => new URL('./brands/DiscordRoundBlack.dark.svg', import.meta.url), + }, + { + c: ['light'], + u: () => new URL('./brands/DiscordRoundBlack.light.svg', import.meta.url), + }, +]) export const DiscordRoundGray = /*#__PURE__*/ __createIcon('DiscordRoundGray', [ { u: () => new URL('./brands/DiscordRoundGray.svg', import.meta.url), @@ -2145,7 +2155,12 @@ export const LeftArrow = /*#__PURE__*/ __createIcon('LeftArrow', [ ]) export const LinearCalendar = /*#__PURE__*/ __createIcon('LinearCalendar', [ { - u: () => new URL('./general/LinearCalendar.svg', import.meta.url), + c: ['dark'], + u: () => new URL('./general/LinearCalendar.dark.svg', import.meta.url), + }, + { + c: ['light'], + u: () => new URL('./general/LinearCalendar.light.svg', import.meta.url), }, ]) export const Link = /*#__PURE__*/ __createIcon('Link', [ @@ -2190,11 +2205,35 @@ export const LocalBackup = /*#__PURE__*/ __createIcon('LocalBackup', [ u: () => new URL('./general/LocalBackup.png', import.meta.url), }, ]) -export const Lock = /*#__PURE__*/ __createIcon('Lock', [ - { - u: () => new URL('./general/Lock.svg', import.meta.url), - }, -]) +export const Lock = /*#__PURE__*/ __createIcon( + 'Lock', + [ + { + u: () => new URL('./general/Lock.svg', import.meta.url), + j: () => + /*#__PURE__*/ _jsx('svg', { + xmlns: 'http://www.w3.org/2000/svg', + fill: 'none', + viewBox: '0 0 24 25', + children: /*#__PURE__*/ _jsxs('g', { + fill: 'currentColor', + fillRule: 'evenodd', + clipRule: 'evenodd', + children: [ + /*#__PURE__*/ _jsx('path', { + d: 'M7.748 5.01c-.697.866-.948 2.132-.948 3.74v2a.8.8 0 0 1-1.6 0v-2c0-1.704.25-3.438 1.302-4.744C7.585 2.662 9.363 1.95 12 1.95s4.416.713 5.498 2.057C18.55 5.312 18.8 7.046 18.8 8.75v2a.8.8 0 0 1-1.6 0v-2c0-1.607-.25-2.873-.948-3.74-.667-.828-1.889-1.46-4.252-1.46s-3.584.632-4.252 1.46zM12 15.05a1.7 1.7 0 1 0 0 3.4 1.7 1.7 0 0 0 0-3.4zm-3.3 1.7a3.3 3.3 0 1 1 6.6 0 3.3 3.3 0 0 1-6.6 0z', + }), + /*#__PURE__*/ _jsx('path', { + d: 'M3.566 12.315c-.496.495-.766 1.428-.766 3.434v2c0 2.006.27 2.939.766 3.434.495.496 1.428.766 3.434.766h10c2.006 0 2.939-.27 3.434-.766.496-.495.766-1.428.766-3.434v-2c0-2.006-.27-2.939-.765-3.434-.496-.496-1.43-.766-3.435-.766H7c-2.006 0-2.939.27-3.434.766zm-1.131-1.131C3.439 10.178 5.005 9.949 7 9.949h10c1.994 0 3.561.23 4.566 1.235 1.004 1.004 1.234 2.57 1.234 4.565v2c0 1.995-.23 3.562-1.234 4.566-1.005 1.004-2.572 1.234-4.566 1.234H7c-1.994 0-3.561-.23-4.565-1.234C1.43 21.31 1.2 19.743 1.2 17.749v-2c0-1.994.23-3.561 1.235-4.566z', + }), + ], + }), + }), + s: true, + }, + ], + [24, 25], +) export const MaskAvatar = /*#__PURE__*/ __createIcon('MaskAvatar', [ { c: ['dark'], @@ -2862,10 +2901,11 @@ export const SharpMask = /*#__PURE__*/ __createIcon('SharpMask', [ j: () => /*#__PURE__*/ _jsx('svg', { xmlns: 'http://www.w3.org/2000/svg', - viewBox: '0 0 38 38', + fill: 'none', + viewBox: '0 0 20 20', children: /*#__PURE__*/ _jsx('path', { fill: 'currentColor', - d: 'M3.8 0h30.4A3.8 3.8 0 0 1 38 3.8v30.4a3.8 3.8 0 0 1-3.8 3.8H3.8A3.8 3.8 0 0 1 0 34.2V3.8A3.8 3.8 0 0 1 3.8 0Zm31.294 21.682v-8.94H2.906v19.133a3.219 3.219 0 0 0 3.219 3.22h25.75a3.219 3.219 0 0 0 3.219-3.22v-7.51h-5.199A12.16 12.16 0 0 1 18.98 31.16c-5.795 0-10.642-4.052-11.864-9.477h27.978ZM18.98 28.498a9.468 9.468 0 0 0 7.828-4.133H11.15a9.468 9.468 0 0 0 7.829 4.133Zm-11.821-9.32a5.008 5.008 0 0 1 9.913 0h-2.744a2.326 2.326 0 0 0-4.425 0H7.159Zm13.77 0a5.008 5.008 0 0 1 9.912 0h-2.744a2.326 2.326 0 0 0-4.425 0h-2.744ZM35.093 6.126a3.219 3.219 0 0 0-3.219-3.22H6.125a3.219 3.219 0 0 0-3.22 3.22v3.934h32.189V6.125Z', + d: 'M3.2 1.5H16.8C17.0232 1.5 17.2443 1.54397 17.4506 1.6294C17.6568 1.71484 17.8442 1.84006 18.0021 1.99792C18.1599 2.15578 18.2852 2.34318 18.3706 2.54944C18.456 2.75569 18.5 2.97675 18.5 3.2V16.8C18.5 17.2509 18.3209 17.6833 18.0021 18.0021C17.6833 18.3209 17.2509 18.5 16.8 18.5H3.2C2.74913 18.5 2.31673 18.3209 1.99792 18.0021C1.67911 17.6833 1.5 17.2509 1.5 16.8V3.2C1.5 2.74913 1.67911 2.31673 1.99792 1.99792C2.31673 1.67911 2.74913 1.5 3.2 1.5ZM17.1999 11.1998V7.20037H2.80005V15.7599C2.79999 15.949 2.8372 16.1363 2.90954 16.3111C2.98189 16.4859 3.08795 16.6447 3.22168 16.7784C3.35541 16.9122 3.51419 17.0183 3.68893 17.0907C3.86368 17.1631 4.05098 17.2004 4.24013 17.2004H15.7599C15.949 17.2004 16.1363 17.1631 16.3111 17.0907C16.4858 17.0183 16.6446 16.9122 16.7783 16.7784C16.912 16.6447 17.0181 16.4859 17.0905 16.3111C17.1628 16.1363 17.2 15.949 17.1999 15.7599V12.4001H14.8741C14.4255 13.3125 13.7303 14.0809 12.8672 14.6182C12.0041 15.1555 11.0077 15.4402 9.99105 15.44C7.39855 15.44 5.23016 13.6273 4.68347 11.2003L17.1999 11.1998ZM9.99105 14.2491C10.6812 14.2496 11.3611 14.0813 11.9714 13.7591C12.5818 13.4368 13.1041 12.9703 13.4931 12.4001H6.48816C6.87711 12.9704 7.39953 13.4369 8.00997 13.7592C8.6204 14.0814 9.30078 14.2496 9.99105 14.2491ZM4.70271 10.0796C4.77976 9.5465 5.04632 9.05898 5.45355 8.70637C5.86078 8.35377 6.38142 8.15969 6.92009 8.15969C7.45876 8.15969 7.9794 8.35377 8.38663 8.70637C8.79386 9.05898 9.06042 9.5465 9.13747 10.0796H7.90989C7.84206 9.87052 7.70975 9.68827 7.53193 9.55902C7.35411 9.42976 7.13993 9.36014 6.92009 9.36014C6.70026 9.36014 6.48607 9.42976 6.30825 9.55902C6.13044 9.68827 5.99812 9.87052 5.93029 10.0796H4.70271ZM10.863 10.0796C10.9404 9.54682 11.207 9.05969 11.6142 8.70741C12.0213 8.35512 12.5417 8.16123 13.0801 8.16123C13.6185 8.16123 14.1389 8.35512 14.5461 8.70741C14.9532 9.05969 15.2199 9.54682 15.2973 10.0796H14.0697C14.0019 9.87052 13.8696 9.68827 13.6917 9.55902C13.5139 9.42976 13.2997 9.36014 13.0799 9.36014C12.8601 9.36014 12.6459 9.42976 12.4681 9.55902C12.2903 9.68827 12.1579 9.87052 12.0901 10.0796H10.863ZM17.1995 4.24058C17.1996 4.05143 17.1624 3.86412 17.09 3.68935C17.0177 3.51458 16.9116 3.35577 16.7779 3.222C16.6441 3.08823 16.4854 2.98211 16.3106 2.90971C16.1359 2.83732 15.9486 2.80005 15.7594 2.80005H4.24013C4.05094 2.79999 3.8636 2.83721 3.6888 2.90959C3.514 2.98196 3.35517 3.08807 3.22139 3.22184C3.08762 3.35562 2.98151 3.51444 2.90914 3.68924C2.83677 3.86404 2.79955 4.05139 2.79961 4.24058V6.00053H17.1999L17.1995 4.24058Z', }), }), s: true, @@ -3311,6 +3351,16 @@ export const Web = /*#__PURE__*/ __createIcon('Web', [ u: () => new URL('./general/Web.svg', import.meta.url), }, ]) +export const WebBlack = /*#__PURE__*/ __createIcon('WebBlack', [ + { + c: ['dark'], + u: () => new URL('./general/WebBlack.dark.svg', import.meta.url), + }, + { + c: ['light'], + u: () => new URL('./general/WebBlack.light.svg', import.meta.url), + }, +]) export const MenuPersonas = /*#__PURE__*/ __createIcon('MenuPersonas', [ { u: () => new URL('./menus/MenuPersonas.png', import.meta.url), diff --git a/packages/icons/icon-generated-as-url.js b/packages/icons/icon-generated-as-url.js index 1bf3e34dd256..2105a2a36155 100644 --- a/packages/icons/icon-generated-as-url.js +++ b/packages/icons/icon-generated-as-url.js @@ -16,6 +16,8 @@ export function danger_url() { return new URL("./brands/Danger.svg", import.meta export function debank_url() { return new URL("./brands/Debank.svg", import.meta.url) } export function discord_url() { return new URL("./brands/Discord.svg", import.meta.url) } export function discord_round_url() { return new URL("./brands/DiscordRound.svg", import.meta.url) } +export function discord_round_black_dark_url() { return new URL("./brands/DiscordRoundBlack.dark.svg", import.meta.url) } +export function discord_round_black_light_url() { return new URL("./brands/DiscordRoundBlack.light.svg", import.meta.url) } export function discord_round_gray_url() { return new URL("./brands/DiscordRoundGray.svg", import.meta.url) } export function eip_1577_url() { return new URL("./brands/EIP1577.svg", import.meta.url) } export function eth_url() { return new URL("./brands/ETH.svg", import.meta.url) } @@ -215,7 +217,8 @@ export function japan_url() { return new URL("./general/Japan.svg", import.meta. export function jpy_url() { return new URL("./general/JPY.svg", import.meta.url) } export function key_square_url() { return new URL("./general/KeySquare.svg", import.meta.url) } export function left_arrow_url() { return new URL("./general/LeftArrow.svg", import.meta.url) } -export function linear_calendar_url() { return new URL("./general/LinearCalendar.svg", import.meta.url) } +export function linear_calendar_dark_url() { return new URL("./general/LinearCalendar.dark.svg", import.meta.url) } +export function linear_calendar_light_url() { return new URL("./general/LinearCalendar.light.svg", import.meta.url) } export function link_url() { return new URL("./general/Link.svg", import.meta.url) } export function link_out_url() { return new URL("./general/LinkOut.svg", import.meta.url) } export function loader_url() { return new URL("./general/Loader.svg", import.meta.url) } @@ -312,6 +315,8 @@ export function wallet_setting_url() { return new URL("./general/WalletSetting.s export function warning_url() { return new URL("./general/Warning.svg", import.meta.url) } export function warning_triangle_url() { return new URL("./general/WarningTriangle.svg", import.meta.url) } export function web_url() { return new URL("./general/Web.svg", import.meta.url) } +export function web_black_dark_url() { return new URL("./general/WebBlack.dark.svg", import.meta.url) } +export function web_black_light_url() { return new URL("./general/WebBlack.light.svg", import.meta.url) } export function menu_personas_url() { return new URL("./menus/MenuPersonas.png", import.meta.url) } export function menu_personas_active_url() { return new URL("./menus/MenuPersonasActive.png", import.meta.url) } export function menu_settings_url() { return new URL("./menus/MenuSettings.png", import.meta.url) } diff --git a/packages/icons/package.json b/packages/icons/package.json index 8eb84223a7d1..080a9d38ff9f 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -27,9 +27,5 @@ "./icon-generated-as-url.d.ts" ] } - }, - "scripts": {}, - "dependencies": { - "@emotion/server": "^11.10.0" } } diff --git a/packages/icons/plugins/Valuables.dark.svg b/packages/icons/plugins/Valuables.dark.svg index c122c345916b..47b84502e043 100644 --- a/packages/icons/plugins/Valuables.dark.svg +++ b/packages/icons/plugins/Valuables.dark.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + diff --git a/packages/icons/plugins/Valuables.light.svg b/packages/icons/plugins/Valuables.light.svg index a60ff07d4a14..6e9e5f97dad7 100644 --- a/packages/icons/plugins/Valuables.light.svg +++ b/packages/icons/plugins/Valuables.light.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + diff --git a/packages/injected-script/shared/index.ts b/packages/injected-script/shared/index.ts index 491bfc06d3b4..388841f6816d 100644 --- a/packages/injected-script/shared/index.ts +++ b/packages/injected-script/shared/index.ts @@ -64,24 +64,31 @@ export type EventItemBeforeSerialization = keyof InternalEvents extends infer U const { parse, stringify } = JSON const { isArray } = Array const { setPrototypeOf } = Object -// @ts-expect-error firefox api -const isFirefox = typeof XPCNativeWrapper === 'function' -export const encodeEvent: (key: T, args: InternalEvents[T]) => unknown = isFirefox - ? (key, args) => [key, args] - : (key, args) => stringify(setPrototypeOf([key, args], null)) - -export const decodeEvent: (data: unknown) => EventItemBeforeSerialization = isFirefox - ? (data) => { - if (!isEventItemBeforeSerialization(data)) throw null - return data - } - : (data) => { - const result = parse(data as any) - // Do not throw new Error cause it requires a global lookup. +const { String } = globalThis +export function encodeEvent(key: string, args: unknown[]) { + return stringify(setPrototypeOf([key, args], null), function formatter(key: string, value: unknown) { + if (value instanceof Uint8Array) return { $type: 'u8[]', value: [...value] } + return value + }) +} - if (!isEventItemBeforeSerialization(result)) throw null - return result - } +export function decodeEvent(data: unknown) { + const result = parse(String(data), function reviver(key: string, value: unknown) { + if ( + typeof value === 'object' && + value && + '$type' in value && + 'value' in value && + isArray(value.value) && + value.$type === 'u8[]' + ) + return new Uint8Array(value.value) + return value + }) + // Do not throw new Error cause it requires a global lookup. + if (!isEventItemBeforeSerialization(result)) throw null + return result +} function isEventItemBeforeSerialization(data: unknown): data is EventItemBeforeSerialization { if (!isArray(data)) return false diff --git a/packages/mask/.webpack/config.ts b/packages/mask/.webpack/config.ts index b662398d4088..755182cdc4e5 100644 --- a/packages/mask/.webpack/config.ts +++ b/packages/mask/.webpack/config.ts @@ -26,6 +26,8 @@ const __dirname = fileURLToPath(dirname(import.meta.url)) const require = createRequire(import.meta.url) const patchesDir = join(__dirname, '../../../patches') const templateContent = readFile(join(__dirname, './template.html'), 'utf8') +const popupTemplateContent = readFile(join(__dirname, './popups.html'), 'utf8') + export async function createConfiguration(_inputFlags: BuildFlags): Promise { const VERSION = JSON.parse(await readFile(new URL('../../../package.json', import.meta.url), 'utf-8')).version const flags = normalizeBuildFlags(_inputFlags) @@ -302,9 +304,8 @@ export async function createConfiguration(_inputFlags: BuildFlags): Promise = (baseConfig.entry = { dashboard: withReactDevTools(join(__dirname, '../src/extension/dashboard/index.ts')), - popups: withReactDevTools(join(__dirname, '../src/extension/popups/SSR-client.ts')), + popups: withReactDevTools(join(__dirname, '../src/extension/popups/entry.ts')), contentScript: withReactDevTools(join(__dirname, '../src/content-script.ts')), - debug: withReactDevTools(join(__dirname, '../src/extension/debug-page/index.tsx')), background: normalizeEntryDescription(join(__dirname, '../background/mv2-entry.ts')), backgroundWorker: normalizeEntryDescription(join(__dirname, '../background/mv3-entry.ts')), }) @@ -312,7 +313,6 @@ export async function createConfiguration(_inputFlags: BuildFlags): Promise`, '') } return new HTMLPlugin({ - templateContent, + templateContent: options.filename === 'popups.html' ? popupTemplateContent : templateContent, inject: 'body', scriptLoading: 'defer', minify: false, diff --git a/packages/mask/.webpack/manifest.ts b/packages/mask/.webpack/manifest.ts index e11e9505f6ea..f53f475c7168 100644 --- a/packages/mask/.webpack/manifest.ts +++ b/packages/mask/.webpack/manifest.ts @@ -92,7 +92,7 @@ function editManifest(manifest: ManifestV2 | ManifestV3, flags: ModifyAcceptFlag if (manifest.manifest_version === 2) { if (String(computedFlags.sourceMapKind).includes('eval')) { - manifest.content_security_policy = `script-src 'self' 'unsafe-eval'; object-src 'self'; require-trusted-types-for 'script'; trusted-types default dompurify webpack mask ssr` + manifest.content_security_policy = `script-src 'self' 'unsafe-eval'; object-src 'self'; require-trusted-types-for 'script'; trusted-types default dompurify webpack mask` } if (flags.hmr) { diff --git a/packages/mask/.webpack/manifest/manifest-mv3.json b/packages/mask/.webpack/manifest/manifest-mv3.json index be712246a64a..d34f71b90e1a 100644 --- a/packages/mask/.webpack/manifest/manifest-mv3.json +++ b/packages/mask/.webpack/manifest/manifest-mv3.json @@ -29,7 +29,7 @@ } ], "content_security_policy": { - "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; trusted-types default dompurify mask ssr;" + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; trusted-types default dompurify mask;" }, "minimum_chrome_version": "102" } diff --git a/packages/mask/.webpack/manifest/manifest.json b/packages/mask/.webpack/manifest/manifest.json index 3829ae0d8d5a..930ef8545788 100644 --- a/packages/mask/.webpack/manifest/manifest.json +++ b/packages/mask/.webpack/manifest/manifest.json @@ -16,5 +16,5 @@ "homepage_url": "https://mask.io", "description": "The portal to the new & open Internet. Send encrypted message and decentralized Apps right on top of social networks.", "web_accessible_resources": ["js/*", "*.svg", "*.png", "*.jpg", "*.css", "build-info.json"], - "content_security_policy": "script-src 'self' 'wasm-eval'; object-src 'self'; trusted-types default dompurify mask ssr;" + "content_security_policy": "script-src 'self' 'wasm-eval'; object-src 'self'; trusted-types default dompurify mask;" } diff --git a/packages/mask/.webpack/popups.html b/packages/mask/.webpack/popups.html new file mode 100644 index 000000000000..faecb0034d9e --- /dev/null +++ b/packages/mask/.webpack/popups.html @@ -0,0 +1,98 @@ + + + + Mask Network + + + + + + + + + + + + + + + + + +
+
+ + + + + + +

Loading

+
+
+ + diff --git a/packages/mask/background/services/backup/internal_create.ts b/packages/mask/background/services/backup/internal_create.ts index a3d72dd88096..f18745f5ec95 100644 --- a/packages/mask/background/services/backup/internal_create.ts +++ b/packages/mask/background/services/backup/internal_create.ts @@ -146,7 +146,7 @@ export async function createNewBackup(options: InternalBackupOptions): Promise { - mnemonicWalletMap.set(x.address, { + mnemonicWalletMap.set(formatEthereumAddress(x.address), { mnemonicId, derivationPath: x.derivationPath, }) diff --git a/packages/mask/background/services/helper/popup-opener.ts b/packages/mask/background/services/helper/popup-opener.ts index 0eb05090ebd8..c57e32ee9c6b 100644 --- a/packages/mask/background/services/helper/popup-opener.ts +++ b/packages/mask/background/services/helper/popup-opener.ts @@ -67,7 +67,7 @@ async function openOrUpdatePopupWindow(route: PopupRoutes, params: ParamMap) { ) } -const noWalletUnlockNeeded: PopupRoutes[] = [PopupRoutes.PersonaSignRequest, PopupRoutes.Unlock, PopupRoutes.Personas] +const noWalletUnlockNeeded: PopupRoutes[] = [PopupRoutes.PersonaSignRequest, PopupRoutes.Personas] export async function openPopupWindow( route: T, @@ -80,10 +80,10 @@ export async function openPopupWindow( ...params, }) } else { - return openOrUpdatePopupWindow(PopupRoutes.Unlock, { + return openOrUpdatePopupWindow(PopupRoutes.Wallet, { close_after_unlock: true, from: urlcat(route, params as ParamMap), - } satisfies PopupRoutesParamsMap[PopupRoutes.Unlock]) + } satisfies PopupRoutesParamsMap[PopupRoutes.Wallet]) } } diff --git a/packages/mask/background/services/identity/persona/avatar.ts b/packages/mask/background/services/identity/persona/avatar.ts index 91d8a5dcb3e8..43cb8321b0a5 100644 --- a/packages/mask/background/services/identity/persona/avatar.ts +++ b/packages/mask/background/services/identity/persona/avatar.ts @@ -11,7 +11,10 @@ export async function getPersonaAvatar( if (!identifiers) return undefined // Array.isArray cannot guard for readonly array. // eslint-disable-next-line @masknet/type-no-instanceof-wrapper - return queryAvatarsDataURL(identifiers instanceof Array ? identifiers : [identifiers]) + const map = await queryAvatarsDataURL(identifiers instanceof Array ? identifiers : [identifiers]) + // eslint-disable-next-line @masknet/type-no-instanceof-wrapper + if (identifiers instanceof Array) return map + return map.get(identifiers) } export async function getPersonaAvatarLastUpdateTime(identifier?: PersonaIdentifier | null) { diff --git a/packages/mask/background/services/identity/profile/query.ts b/packages/mask/background/services/identity/profile/query.ts index cabe6283f160..94650c90e4d1 100644 --- a/packages/mask/background/services/identity/profile/query.ts +++ b/packages/mask/background/services/identity/profile/query.ts @@ -4,6 +4,7 @@ import { type ProfileRecord, queryPersonasDB, queryProfilesDB, + queryProfileDB, } from '../../../database/persona/db.js' import { hasLocalKeyOf } from '../../../database/persona/helper.js' import { toProfileInformation } from '../../__utils__/convert.js' @@ -17,6 +18,10 @@ export async function queryProfilesInformation(identifiers: ProfileIdentifier[]) return toProfileInformation(profiles).mustNotAwaitThisWithInATransaction } +export async function queryProfileInformation(identifier: ProfileIdentifier): Promise { + return await queryProfileDB(identifier) +} + /** @deprecated */ export async function hasLocalKey(identifier: ProfileIdentifier) { return hasLocalKeyOf(identifier) diff --git a/packages/mask/background/services/settings/old-settings-accessor.ts b/packages/mask/background/services/settings/old-settings-accessor.ts index 754bec676323..47237f5e8ffa 100644 --- a/packages/mask/background/services/settings/old-settings-accessor.ts +++ b/packages/mask/background/services/settings/old-settings-accessor.ts @@ -8,7 +8,6 @@ import { setCurrentPluginMinimalMode, type PersonaIdentifier, type ValueRefWithReady, - decentralizedSearchSettings, appearanceSettings, BooleanPreference, InjectSwitchSettings, @@ -68,6 +67,4 @@ export async function setInjectSwitchSetting(network: string, value: boolean) { InjectSwitchSettings[network].value = value } -export const [getDecentralizedSearchSettings, setDecentralizedSearchSettings] = create(decentralizedSearchSettings) - export { __deprecated__getStorage as getLegacySettingsInitialValue } from '../../utils/deprecated-storage.js' diff --git a/packages/mask/background/services/wallet/services/select.ts b/packages/mask/background/services/wallet/services/select.ts index baf2c85d4d4c..c3b860ed9c1b 100644 --- a/packages/mask/background/services/wallet/services/select.ts +++ b/packages/mask/background/services/wallet/services/select.ts @@ -14,9 +14,10 @@ export interface MaskAccount { /** * @param chainId Chain ID */ -export async function selectMaskAccount(chainId: ChainId): Promise { +export async function selectMaskAccount(chainId: ChainId, defaultAddress: string): Promise { await openPopupWindow(Providers[ProviderType.MaskWallet].wallets ? PopupRoutes.SelectWallet : PopupRoutes.Wallet, { chainId, + address: defaultAddress, }) deferred = defer() return deferred![0] diff --git a/packages/mask/background/services/wallet/services/wallet/index.ts b/packages/mask/background/services/wallet/services/wallet/index.ts index b56b52d67bc6..e311130fe0c3 100644 --- a/packages/mask/background/services/wallet/services/wallet/index.ts +++ b/packages/mask/background/services/wallet/services/wallet/index.ts @@ -297,6 +297,7 @@ export async function exportKeyStoreJSON(address: string, unverifiedPassword?: s const masterPassword = await password.INTERNAL_getMasterPasswordRequired() const wallet = await database.getWalletRequired(address) if (!wallet.storedKeyInfo) throw new Error(`Cannot export private key of ${address}.`) + const exported = wallet.derivationPath && !wallet.configurable ? await Mask.exportKeyStoreJSONOfPath({ diff --git a/packages/mask/background/services/wallet/services/wallet/locker.ts b/packages/mask/background/services/wallet/services/wallet/locker.ts index fa49ce575d7b..87f7139dbf88 100644 --- a/packages/mask/background/services/wallet/services/wallet/locker.ts +++ b/packages/mask/background/services/wallet/services/wallet/locker.ts @@ -18,6 +18,7 @@ export async function unlockWallet(unverifiedPassword: string) { await password.verifyPasswordRequired(unverifiedPassword) currentMaskWalletLockStatusSettings.value = LockStatus.UNLOCK CrossIsolationMessages.events.walletLockStatusUpdated.sendToAll(false) + await setAutoLockTimer() return true } catch { CrossIsolationMessages.events.walletLockStatusUpdated.sendToAll(true) @@ -38,3 +39,6 @@ export async function setAutoLockTimer() { await lockWallet() }, autoLockDuration) } + +// Reset timer +CrossIsolationMessages.events.walletLockStatusUpdated.on(setAutoLockTimer) diff --git a/packages/mask/background/tasks/Cancellable/PopupSSR/cache.ts b/packages/mask/background/tasks/Cancellable/PopupSSR/cache.ts deleted file mode 100644 index 5883fbc0c73f..000000000000 --- a/packages/mask/background/tasks/Cancellable/PopupSSR/cache.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { throttle } from 'lodash-es' -import type { Storage } from 'webextension-polyfill' -import { type EnhanceableSite, MaskMessages, type ProfileAccount } from '@masknet/shared-base' -import { InternalStorageKeys } from '../../../services/settings/utils.js' -import { getCurrentPersonaIdentifier, getLanguage } from '../../../services/settings/index.js' -import { queryOwnedPersonaInformation } from '../../../services/identity/index.js' -import type { PopupSSR_Props } from './type.js' -import { getSupportedSites } from '../../../services/site-adaptors/connect.js' - -const CACHE_KEY = 'popup-ssr-cache' -export let cache: { - html: string - css: string -} = { html: '', css: '' } -export function startListen( - render: (props: PopupSSR_Props) => Promise<{ - html: string - css: string - }>, - signal: AbortSignal, -) { - async function task() { - cache = await prepareData().then(render) - if ('session' in browser.storage) { - ;(browser.storage.session as Storage.StorageArea).set({ [CACHE_KEY]: cache }) - } - console.log('[Popup SSR] Page ready.', cache) - } - const throttledTask = throttle(task, 2000, { leading: true }) - - if (!('session' in browser.storage)) { - throttledTask() - } else { - ;(browser.storage.session as Storage.StorageArea).get(CACHE_KEY).then((result) => { - if (result[CACHE_KEY]) cache = result[CACHE_KEY] - else throttledTask() - }) - } - MaskMessages.events.ownPersonaChanged.on(throttledTask, { signal }) - MaskMessages.events.legacySettings_broadcast.on( - (event) => { - if (event.key === InternalStorageKeys.currentPersona || event.key === InternalStorageKeys.language) - throttledTask() - }, - { signal }, - ) - - return { task, throttledTask } -} - -async function prepareData(): Promise { - const language = getLanguage() - const [id, personas, networks] = await Promise.all([ - getCurrentPersonaIdentifier(), - queryOwnedPersonaInformation(false), - getSupportedSites({ isSocialNetwork: true }), - ]) - const currentPersona = personas.find((x) => x.identifier === id) || personas.at(0) - - return { - language: await language, - accounts: currentPersona?.linkedProfiles.map((profile) => ({ - ...profile, - identity: profile.identifier.userId, - })), - avatar: currentPersona?.avatar, - currentFingerPrint: id?.rawPublicKey, - hasPersona: !!currentPersona, - currentPublicKeyHex: id?.publicKeyAsHex, - linkedProfilesCount: currentPersona?.linkedProfiles.length ?? 0, - nickname: currentPersona?.nickname, - networks: networks.map((x) => x.networkIdentifier as EnhanceableSite), - } -} diff --git a/packages/mask/background/tasks/Cancellable/PopupSSR/index_hasWorker.ts b/packages/mask/background/tasks/Cancellable/PopupSSR/index_hasWorker.ts deleted file mode 100644 index 53e6da689cd5..000000000000 --- a/packages/mask/background/tasks/Cancellable/PopupSSR/index_hasWorker.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { OnDemandWorker, serializer } from '@masknet/shared-base' -import { hmr } from '../../../../utils-pure/index.js' -import { cache, startListen } from './cache.js' - -const worker = new OnDemandWorker(new URL('./worker.tsx', import.meta.url), { name: 'PopupSSR-Worker' }) -const { signal } = hmr(import.meta.webpackHot) -if (typeof Worker === 'function') { - browser.runtime.onMessage.addListener(listener) - - signal.addEventListener('abort', () => { - browser.runtime.onMessage.removeListener(listener) - worker.terminate() - }) - - const { task } = startListen((props) => { - return new Promise((resolve) => { - Promise.resolve(serializer.serialization(props)).then((data) => worker.postMessage(data)) - worker.addEventListener('message', (data) => resolve(data.data)) - }) - }, signal) - - if (process.env.NODE_ENV === 'development') { - Object.defineProperty(globalThis, '__popup__ssr__recompute__', { - get: () => { - worker.terminate() - return task().then(() => cache) - }, - configurable: true, - enumerable: true, - }) - } -} - -function isRequestPopupSSRCache(x: unknown): boolean { - return typeof x === 'object' && !!x && 'type' in x && (x as any).type === 'popups-ssr' -} -// Do not convert this function to async function. -function listener(message: unknown) { - if (!isRequestPopupSSRCache(message)) return - // Note: return a Promise in browser.runtime.onMessage can return the message to the caller. - return Promise.resolve(cache) -} diff --git a/packages/mask/background/tasks/Cancellable/PopupSSR/index_noWorker.ts b/packages/mask/background/tasks/Cancellable/PopupSSR/index_noWorker.ts deleted file mode 100644 index 80372192f417..000000000000 --- a/packages/mask/background/tasks/Cancellable/PopupSSR/index_noWorker.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { cache, startListen } from './cache.js' -import { hmr } from '../../../../utils-pure/index.js' - -const { signal } = hmr(import.meta.webpackHot) -// https://bugs.chromium.org/p/chromium/issues/detail?id=1219164 -if (typeof Worker === 'undefined') { - browser.runtime.onMessage.addListener(f) - signal.addEventListener('abort', () => browser.runtime.onMessage.removeListener(f), { once: true }) - - startListen(async (props) => { - const { render } = await import('./loader.js') - return render(props) - }, signal) -} - -function f(message: any) { - if (!(message.type === 'popups-ssr')) return - return Promise.resolve(cache) -} diff --git a/packages/mask/background/tasks/Cancellable/PopupSSR/loader.d.ts b/packages/mask/background/tasks/Cancellable/PopupSSR/loader.d.ts deleted file mode 100644 index 269129a2c197..000000000000 --- a/packages/mask/background/tasks/Cancellable/PopupSSR/loader.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function render(props: PopupSSR_Props): Promise<{ - html: string - css: string -}> diff --git a/packages/mask/background/tasks/Cancellable/PopupSSR/loader.js b/packages/mask/background/tasks/Cancellable/PopupSSR/loader.js deleted file mode 100644 index 2f4f1f983267..000000000000 --- a/packages/mask/background/tasks/Cancellable/PopupSSR/loader.js +++ /dev/null @@ -1,2 +0,0 @@ -// This is a JS file to make TypeScript happy. -export { render } from '../../../../src/extension/popups/SSR-server.tsx' diff --git a/packages/mask/background/tasks/Cancellable/PopupSSR/type.ts b/packages/mask/background/tasks/Cancellable/PopupSSR/type.ts deleted file mode 100644 index ac48ca560a27..000000000000 --- a/packages/mask/background/tasks/Cancellable/PopupSSR/type.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { LanguageOptions } from '@masknet/public-api' -import type { ProfileAccount, EnhanceableSite } from '@masknet/shared-base' - -export interface PopupSSR_Props { - language: LanguageOptions - currentFingerPrint: string | undefined - currentPublicKeyHex: string | undefined - hasPersona: boolean - avatar: string | null | undefined - nickname: string | undefined - linkedProfilesCount: number - networks: EnhanceableSite[] - accounts: ProfileAccount[] | undefined -} diff --git a/packages/mask/background/tasks/Cancellable/PopupSSR/worker.tsx b/packages/mask/background/tasks/Cancellable/PopupSSR/worker.tsx deleted file mode 100644 index 858326ea22b2..000000000000 --- a/packages/mask/background/tasks/Cancellable/PopupSSR/worker.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { setupBuildInfo } from '@masknet/flags/build-info' - -globalThis.addEventListener('message', async (event) => { - await setupBuildInfo() - const { serializer } = await import(/* webpackMode: 'eager' */ '@masknet/shared-base') - const data = serializer.deserialization(event.data) - const { render } = await import(/* webpackMode: 'eager' */ './loader.js') - postMessage(await render(await data)) -}) - -globalThis.postMessage('alive') diff --git a/packages/mask/background/tasks/NotCancellable/OnInstall.ts b/packages/mask/background/tasks/NotCancellable/OnInstall.ts index c95930de3c1e..3cb0b94f8958 100644 --- a/packages/mask/background/tasks/NotCancellable/OnInstall.ts +++ b/packages/mask/background/tasks/NotCancellable/OnInstall.ts @@ -1,6 +1,5 @@ // ALL IMPORTS MUST BE DEFERRED import { PersistentStorages, type DashboardRoutes } from '@masknet/shared-base' -import * as connect /* webpackDefer: true */ from '../../services/site-adaptors/connect.js' type DashboardRoutes_Welcome = DashboardRoutes.Welcome extends `${infer T}` ? T : never function openWelcome() { @@ -14,6 +13,7 @@ browser.runtime.onInstalled.addListener(async (detail) => { if (detail.reason === 'install') { openWelcome() } else if (detail.reason === 'update') { + const connect = await import('../../services/site-adaptors/connect.js') const groups = await connect.getOriginsWithoutPermission() if (groups.length) openWelcome() if ((globalThis as any).localStorage) { @@ -22,13 +22,20 @@ browser.runtime.onInstalled.addListener(async (detail) => { if (backupPassword) { const backupMethod = localStorage.getItem('backupMethod') PersistentStorages.Settings.storage.backupConfig.setValue({ - backupPassword: atob(backupPassword), + backupPassword, email: localStorage.getItem('email'), phone: localStorage.getItem('phone'), cloudBackupAt: backupMethod && backupMethod === 'cloud' ? localStorage.getItem('backupAt') : null, localBackupAt: backupMethod && backupMethod === 'local' ? localStorage.getItem('backupAt') : null, + cloudBackupMethod: null, }) } + // remove old data after migrate + localStorage.removeItem('backupPassword') + localStorage.removeItem('backupMethod') + localStorage.removeItem('email') + localStorage.removeItem('phone') + localStorage.removeItem('backupAt') } } }) diff --git a/packages/mask/background/tasks/setup.hmr.ts b/packages/mask/background/tasks/setup.hmr.ts index 07684c36cbce..ffa0dadcadf3 100644 --- a/packages/mask/background/tasks/setup.hmr.ts +++ b/packages/mask/background/tasks/setup.hmr.ts @@ -1,8 +1,6 @@ import './Cancellable/InjectContentScripts_imperative.js' import './Cancellable/InjectContentScripts_declarative.js' import './Cancellable/CleanProfileAndAvatar.js' -import './Cancellable/PopupSSR/index_hasWorker.js' -import './Cancellable/PopupSSR/index_noWorker.js' import './Cancellable/SettingsListener.js' import './Cancellable/StartPluginHost.js' import './Cancellable/StartSandboxedPluginHost.js' diff --git a/packages/mask/dashboard/assets/images/SetupTutorial.svg b/packages/mask/dashboard/assets/images/SetupTutorial.svg deleted file mode 100644 index c7e6ac80bf2b..000000000000 --- a/packages/mask/dashboard/assets/images/SetupTutorial.svg +++ /dev/null @@ -1,582 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/mask/dashboard/assets/index.ts b/packages/mask/dashboard/assets/index.ts index 8ca588e5d492..48ef42aa59cf 100644 --- a/packages/mask/dashboard/assets/index.ts +++ b/packages/mask/dashboard/assets/index.ts @@ -2,7 +2,6 @@ * Manage local static resource */ export const ABOUT_DIALOG_BACKGROUND = new URL('./images/AboutDialogBackground.png', import.meta.url) -export const SetupTutorialURL = new URL('./images/SetupTutorial.svg', import.meta.url) export const WatermarkURL = new URL('./images/MaskWatermark.png', import.meta.url) // A workaround for Opera file name validation export const Welcome = new URL('./Welcome.splinecode.png', import.meta.url) diff --git a/packages/mask/dashboard/assets/region.json b/packages/mask/dashboard/assets/region.json deleted file mode 100644 index f30d2f79569d..000000000000 --- a/packages/mask/dashboard/assets/region.json +++ /dev/null @@ -1,1212 +0,0 @@ -[ - { - "name": "Afghanistan", - "dial_code": "+93", - "code": "AF" - }, - { - "name": "Aland Islands", - "dial_code": "+358-18", - "code": "AX" - }, - { - "name": "Albania", - "dial_code": "+355", - "code": "AL" - }, - { - "name": "Algeria", - "dial_code": "+213", - "code": "DZ" - }, - { - "name": "AmericanSamoa", - "dial_code": "+1684", - "code": "AS" - }, - { - "name": "Andorra", - "dial_code": "+376", - "code": "AD" - }, - { - "name": "Angola", - "dial_code": "+244", - "code": "AO" - }, - { - "name": "Anguilla", - "dial_code": "+1264", - "code": "AI" - }, - { - "name": "Antarctica", - "dial_code": "+672", - "code": "AQ" - }, - { - "name": "Antigua and Barbuda", - "dial_code": "+1268", - "code": "AG" - }, - { - "name": "Argentina", - "dial_code": "+54", - "code": "AR" - }, - { - "name": "Armenia", - "dial_code": "+374", - "code": "AM" - }, - { - "name": "Aruba", - "dial_code": "+297", - "code": "AW" - }, - { - "name": "Australia", - "dial_code": "+61", - "code": "AU" - }, - { - "name": "Austria", - "dial_code": "+43", - "code": "AT" - }, - { - "name": "Azerbaijan", - "dial_code": "+994", - "code": "AZ" - }, - { - "name": "Bahamas", - "dial_code": "+1242", - "code": "BS" - }, - { - "name": "Bahrain", - "dial_code": "+973", - "code": "BH" - }, - { - "name": "Bangladesh", - "dial_code": "+880", - "code": "BD" - }, - { - "name": "Barbados", - "dial_code": "+1246", - "code": "BB" - }, - { - "name": "Belarus", - "dial_code": "+375", - "code": "BY" - }, - { - "name": "Belgium", - "dial_code": "+32", - "code": "BE" - }, - { - "name": "Belize", - "dial_code": "+501", - "code": "BZ" - }, - { - "name": "Benin", - "dial_code": "+229", - "code": "BJ" - }, - { - "name": "Bermuda", - "dial_code": "+1441", - "code": "BM" - }, - { - "name": "Bhutan", - "dial_code": "+975", - "code": "BT" - }, - { - "name": "Bolivia, Plurinational State of", - "dial_code": "+591", - "code": "BO" - }, - { - "name": "Bosnia and Herzegovina", - "dial_code": "+387", - "code": "BA" - }, - { - "name": "Botswana", - "dial_code": "+267", - "code": "BW" - }, - { - "name": "Brazil", - "dial_code": "+55", - "code": "BR" - }, - { - "name": "British Indian Ocean Territory", - "dial_code": "+246", - "code": "IO" - }, - { - "name": "Brunei Darussalam", - "dial_code": "+673", - "code": "BN" - }, - { - "name": "Bulgaria", - "dial_code": "+359", - "code": "BG" - }, - { - "name": "Burkina Faso", - "dial_code": "+226", - "code": "BF" - }, - { - "name": "Burundi", - "dial_code": "+257", - "code": "BI" - }, - { - "name": "Cambodia", - "dial_code": "+855", - "code": "KH" - }, - { - "name": "Cameroon", - "dial_code": "+237", - "code": "CM" - }, - { - "name": "Canada", - "dial_code": "+1", - "code": "CA" - }, - { - "name": "Cape Verde", - "dial_code": "+238", - "code": "CV" - }, - { - "name": "Cayman Islands", - "dial_code": "+ 345", - "code": "KY" - }, - { - "name": "Central African Republic", - "dial_code": "+236", - "code": "CF" - }, - { - "name": "Chad", - "dial_code": "+235", - "code": "TD" - }, - { - "name": "Chile", - "dial_code": "+56", - "code": "CL" - }, - { - "name": "China", - "dial_code": "+86", - "code": "CN" - }, - { - "name": "Christmas Island", - "dial_code": "+61", - "code": "CX" - }, - { - "name": "Cocos (Keeling) Islands", - "dial_code": "+61", - "code": "CC" - }, - { - "name": "Colombia", - "dial_code": "+57", - "code": "CO" - }, - { - "name": "Comoros", - "dial_code": "+269", - "code": "KM" - }, - { - "name": "Congo", - "dial_code": "+242", - "code": "CG" - }, - { - "name": "Congo, The Democratic Republic of the Congo", - "dial_code": "+243", - "code": "CD" - }, - { - "name": "Cook Islands", - "dial_code": "+682", - "code": "CK" - }, - { - "name": "Costa Rica", - "dial_code": "+506", - "code": "CR" - }, - { - "name": "Cote d'Ivoire", - "dial_code": "+225", - "code": "CI" - }, - { - "name": "Croatia", - "dial_code": "+385", - "code": "HR" - }, - { - "name": "Cuba", - "dial_code": "+53", - "code": "CU" - }, - { - "name": "Cyprus", - "dial_code": "+357", - "code": "CY" - }, - { - "name": "Czech Republic", - "dial_code": "+420", - "code": "CZ" - }, - { - "name": "Denmark", - "dial_code": "+45", - "code": "DK" - }, - { - "name": "Djibouti", - "dial_code": "+253", - "code": "DJ" - }, - { - "name": "Dominica", - "dial_code": "+1767", - "code": "DM" - }, - { - "name": "Dominican Republic", - "dial_code": "+1849", - "code": "DO" - }, - { - "name": "Ecuador", - "dial_code": "+593", - "code": "EC" - }, - { - "name": "Egypt", - "dial_code": "+20", - "code": "EG" - }, - { - "name": "El Salvador", - "dial_code": "+503", - "code": "SV" - }, - { - "name": "Equatorial Guinea", - "dial_code": "+240", - "code": "GQ" - }, - { - "name": "Eritrea", - "dial_code": "+291", - "code": "ER" - }, - { - "name": "Estonia", - "dial_code": "+372", - "code": "EE" - }, - { - "name": "Ethiopia", - "dial_code": "+251", - "code": "ET" - }, - { - "name": "Falkland Islands (Malvinas)", - "dial_code": "+500", - "code": "FK" - }, - { - "name": "Faroe Islands", - "dial_code": "+298", - "code": "FO" - }, - { - "name": "Fiji", - "dial_code": "+679", - "code": "FJ" - }, - { - "name": "Finland", - "dial_code": "+358", - "code": "FI" - }, - { - "name": "France", - "dial_code": "+33", - "code": "FR" - }, - { - "name": "French Guiana", - "dial_code": "+594", - "code": "GF" - }, - { - "name": "French Polynesia", - "dial_code": "+689", - "code": "PF" - }, - { - "name": "Gabon", - "dial_code": "+241", - "code": "GA" - }, - { - "name": "Gambia", - "dial_code": "+220", - "code": "GM" - }, - { - "name": "Georgia", - "dial_code": "+995", - "code": "GE" - }, - { - "name": "Germany", - "dial_code": "+49", - "code": "DE" - }, - { - "name": "Ghana", - "dial_code": "+233", - "code": "GH" - }, - { - "name": "Gibraltar", - "dial_code": "+350", - "code": "GI" - }, - { - "name": "Greece", - "dial_code": "+30", - "code": "GR" - }, - { - "name": "Greenland", - "dial_code": "+299", - "code": "GL" - }, - { - "name": "Grenada", - "dial_code": "+1473", - "code": "GD" - }, - { - "name": "Guadeloupe", - "dial_code": "+590", - "code": "GP" - }, - { - "name": "Guam", - "dial_code": "+1671", - "code": "GU" - }, - { - "name": "Guatemala", - "dial_code": "+502", - "code": "GT" - }, - { - "name": "Guernsey", - "dial_code": "+44", - "code": "GG" - }, - { - "name": "Guinea", - "dial_code": "+224", - "code": "GN" - }, - { - "name": "Guinea-Bissau", - "dial_code": "+245", - "code": "GW" - }, - { - "name": "Guyana", - "dial_code": "+595", - "code": "GY" - }, - { - "name": "Haiti", - "dial_code": "+509", - "code": "HT" - }, - { - "name": "Holy See (Vatican City State)", - "dial_code": "+379", - "code": "VA" - }, - { - "name": "Honduras", - "dial_code": "+504", - "code": "HN" - }, - { - "name": "Hong Kong", - "dial_code": "+852", - "code": "HK" - }, - { - "name": "Hungary", - "dial_code": "+36", - "code": "HU" - }, - { - "name": "Iceland", - "dial_code": "+354", - "code": "IS" - }, - { - "name": "India", - "dial_code": "+91", - "code": "IN" - }, - { - "name": "Indonesia", - "dial_code": "+62", - "code": "ID" - }, - { - "name": "Iran, Islamic Republic of Persian Gulf", - "dial_code": "+98", - "code": "IR" - }, - { - "name": "Iraq", - "dial_code": "+964", - "code": "IQ" - }, - { - "name": "Ireland", - "dial_code": "+353", - "code": "IE" - }, - { - "name": "Isle of Man", - "dial_code": "+44", - "code": "IM" - }, - { - "name": "Israel", - "dial_code": "+972", - "code": "IL" - }, - { - "name": "Italy", - "dial_code": "+39", - "code": "IT" - }, - { - "name": "Jamaica", - "dial_code": "+1876", - "code": "JM" - }, - { - "name": "Japan", - "dial_code": "+81", - "code": "JP" - }, - { - "name": "Jersey", - "dial_code": "+44", - "code": "JE" - }, - { - "name": "Jordan", - "dial_code": "+962", - "code": "JO" - }, - { - "name": "Kazakhstan", - "dial_code": "+77", - "code": "KZ" - }, - { - "name": "Kenya", - "dial_code": "+254", - "code": "KE" - }, - { - "name": "Kiribati", - "dial_code": "+686", - "code": "KI" - }, - { - "name": "Korea, Democratic People's Republic of Korea", - "dial_code": "+850", - "code": "KP" - }, - { - "name": "Korea, Republic of South Korea", - "dial_code": "+82", - "code": "KR" - }, - { - "name": "Kuwait", - "dial_code": "+965", - "code": "KW" - }, - { - "name": "Kyrgyzstan", - "dial_code": "+996", - "code": "KG" - }, - { - "name": "Laos", - "dial_code": "+856", - "code": "LA" - }, - { - "name": "Latvia", - "dial_code": "+371", - "code": "LV" - }, - { - "name": "Lebanon", - "dial_code": "+961", - "code": "LB" - }, - { - "name": "Lesotho", - "dial_code": "+266", - "code": "LS" - }, - { - "name": "Liberia", - "dial_code": "+231", - "code": "LR" - }, - { - "name": "Libyan Arab Jamahiriya", - "dial_code": "+218", - "code": "LY" - }, - { - "name": "Liechtenstein", - "dial_code": "+423", - "code": "LI" - }, - { - "name": "Lithuania", - "dial_code": "+370", - "code": "LT" - }, - { - "name": "Luxembourg", - "dial_code": "+352", - "code": "LU" - }, - { - "name": "Macao", - "dial_code": "+853", - "code": "MO" - }, - { - "name": "Macedonia", - "dial_code": "+389", - "code": "MK" - }, - { - "name": "Madagascar", - "dial_code": "+261", - "code": "MG" - }, - { - "name": "Malawi", - "dial_code": "+265", - "code": "MW" - }, - { - "name": "Malaysia", - "dial_code": "+60", - "code": "MY" - }, - { - "name": "Maldives", - "dial_code": "+960", - "code": "MV" - }, - { - "name": "Mali", - "dial_code": "+223", - "code": "ML" - }, - { - "name": "Malta", - "dial_code": "+356", - "code": "MT" - }, - { - "name": "Marshall Islands", - "dial_code": "+692", - "code": "MH" - }, - { - "name": "Martinique", - "dial_code": "+596", - "code": "MQ" - }, - { - "name": "Mauritania", - "dial_code": "+222", - "code": "MR" - }, - { - "name": "Mauritius", - "dial_code": "+230", - "code": "MU" - }, - { - "name": "Mayotte", - "dial_code": "+262", - "code": "YT" - }, - { - "name": "Mexico", - "dial_code": "+52", - "code": "MX" - }, - { - "name": "Micronesia, Federated States of Micronesia", - "dial_code": "+691", - "code": "FM" - }, - { - "name": "Moldova", - "dial_code": "+373", - "code": "MD" - }, - { - "name": "Monaco", - "dial_code": "+377", - "code": "MC" - }, - { - "name": "Mongolia", - "dial_code": "+976", - "code": "MN" - }, - { - "name": "Montenegro", - "dial_code": "+382", - "code": "ME" - }, - { - "name": "Montserrat", - "dial_code": "+1664", - "code": "MS" - }, - { - "name": "Morocco", - "dial_code": "+212", - "code": "MA" - }, - { - "name": "Mozambique", - "dial_code": "+258", - "code": "MZ" - }, - { - "name": "Myanmar", - "dial_code": "+95", - "code": "MM" - }, - { - "name": "Namibia", - "dial_code": "+264", - "code": "NA" - }, - { - "name": "Nauru", - "dial_code": "+674", - "code": "NR" - }, - { - "name": "Nepal", - "dial_code": "+977", - "code": "NP" - }, - { - "name": "Netherlands", - "dial_code": "+31", - "code": "NL" - }, - { - "name": "Netherlands Antilles", - "dial_code": "+599", - "code": "AN" - }, - { - "name": "New Caledonia", - "dial_code": "+687", - "code": "NC" - }, - { - "name": "New Zealand", - "dial_code": "+64", - "code": "NZ" - }, - { - "name": "Nicaragua", - "dial_code": "+505", - "code": "NI" - }, - { - "name": "Niger", - "dial_code": "+227", - "code": "NE" - }, - { - "name": "Nigeria", - "dial_code": "+234", - "code": "NG" - }, - { - "name": "Niue", - "dial_code": "+683", - "code": "NU" - }, - { - "name": "Norfolk Island", - "dial_code": "+672", - "code": "NF" - }, - { - "name": "Northern Mariana Islands", - "dial_code": "+1670", - "code": "MP" - }, - { - "name": "Norway", - "dial_code": "+47", - "code": "NO" - }, - { - "name": "Oman", - "dial_code": "+968", - "code": "OM" - }, - { - "name": "Pakistan", - "dial_code": "+92", - "code": "PK" - }, - { - "name": "Palau", - "dial_code": "+680", - "code": "PW" - }, - { - "name": "Palestinian Territory, Occupied", - "dial_code": "+970", - "code": "PS" - }, - { - "name": "Panama", - "dial_code": "+507", - "code": "PA" - }, - { - "name": "Papua New Guinea", - "dial_code": "+675", - "code": "PG" - }, - { - "name": "Paraguay", - "dial_code": "+595", - "code": "PY" - }, - { - "name": "Peru", - "dial_code": "+51", - "code": "PE" - }, - { - "name": "Philippines", - "dial_code": "+63", - "code": "PH" - }, - { - "name": "Pitcairn", - "dial_code": "+872", - "code": "PN" - }, - { - "name": "Poland", - "dial_code": "+48", - "code": "PL" - }, - { - "name": "Portugal", - "dial_code": "+351", - "code": "PT" - }, - { - "name": "Puerto Rico", - "dial_code": "+1939", - "code": "PR" - }, - { - "name": "Qatar", - "dial_code": "+974", - "code": "QA" - }, - { - "name": "Romania", - "dial_code": "+40", - "code": "RO" - }, - { - "name": "Russia", - "dial_code": "+7", - "code": "RU" - }, - { - "name": "Rwanda", - "dial_code": "+250", - "code": "RW" - }, - { - "name": "Reunion", - "dial_code": "+262", - "code": "RE" - }, - { - "name": "Saint Barthelemy", - "dial_code": "+590", - "code": "BL" - }, - { - "name": "Saint Helena, Ascension and Tristan Da Cunha", - "dial_code": "+290", - "code": "SH" - }, - { - "name": "Saint Kitts and Nevis", - "dial_code": "+1869", - "code": "KN" - }, - { - "name": "Saint Lucia", - "dial_code": "+1758", - "code": "LC" - }, - { - "name": "Saint Martin", - "dial_code": "+590", - "code": "MF" - }, - { - "name": "Saint Pierre and Miquelon", - "dial_code": "+508", - "code": "PM" - }, - { - "name": "Saint Vincent and the Grenadines", - "dial_code": "+1784", - "code": "VC" - }, - { - "name": "Samoa", - "dial_code": "+685", - "code": "WS" - }, - { - "name": "San Marino", - "dial_code": "+378", - "code": "SM" - }, - { - "name": "Sao Tome and Principe", - "dial_code": "+239", - "code": "ST" - }, - { - "name": "Saudi Arabia", - "dial_code": "+966", - "code": "SA" - }, - { - "name": "Senegal", - "dial_code": "+221", - "code": "SN" - }, - { - "name": "Serbia", - "dial_code": "+381", - "code": "RS" - }, - { - "name": "Seychelles", - "dial_code": "+248", - "code": "SC" - }, - { - "name": "Sierra Leone", - "dial_code": "+232", - "code": "SL" - }, - { - "name": "Singapore", - "dial_code": "+65", - "code": "SG" - }, - { - "name": "Slovakia", - "dial_code": "+421", - "code": "SK" - }, - { - "name": "Slovenia", - "dial_code": "+386", - "code": "SI" - }, - { - "name": "Solomon Islands", - "dial_code": "+677", - "code": "SB" - }, - { - "name": "Somalia", - "dial_code": "+252", - "code": "SO" - }, - { - "name": "South Africa", - "dial_code": "+27", - "code": "ZA" - }, - { - "name": "South Sudan", - "dial_code": "+211", - "code": "SS" - }, - { - "name": "South Georgia and the South Sandwich Islands", - "dial_code": "+500", - "code": "GS" - }, - { - "name": "Spain", - "dial_code": "+34", - "code": "ES" - }, - { - "name": "Sri Lanka", - "dial_code": "+94", - "code": "LK" - }, - { - "name": "Sudan", - "dial_code": "+249", - "code": "SD" - }, - { - "name": "Suriname", - "dial_code": "+597", - "code": "SR" - }, - { - "name": "Svalbard and Jan Mayen", - "dial_code": "+47", - "code": "SJ" - }, - { - "name": "Swaziland", - "dial_code": "+268", - "code": "SZ" - }, - { - "name": "Sweden", - "dial_code": "+46", - "code": "SE" - }, - { - "name": "Switzerland", - "dial_code": "+41", - "code": "CH" - }, - { - "name": "Syrian Arab Republic", - "dial_code": "+963", - "code": "SY" - }, - { - "name": "Taiwan", - "dial_code": "+886", - "code": "TW" - }, - { - "name": "Tajikistan", - "dial_code": "+992", - "code": "TJ" - }, - { - "name": "Tanzania, United Republic of Tanzania", - "dial_code": "+255", - "code": "TZ" - }, - { - "name": "Thailand", - "dial_code": "+66", - "code": "TH" - }, - { - "name": "Timor-Leste", - "dial_code": "+670", - "code": "TL" - }, - { - "name": "Togo", - "dial_code": "+228", - "code": "TG" - }, - { - "name": "Tokelau", - "dial_code": "+690", - "code": "TK" - }, - { - "name": "Tonga", - "dial_code": "+676", - "code": "TO" - }, - { - "name": "Trinidad and Tobago", - "dial_code": "+1868", - "code": "TT" - }, - { - "name": "Tunisia", - "dial_code": "+216", - "code": "TN" - }, - { - "name": "Turkey", - "dial_code": "+90", - "code": "TR" - }, - { - "name": "Turkmenistan", - "dial_code": "+993", - "code": "TM" - }, - { - "name": "Turks and Caicos Islands", - "dial_code": "+1649", - "code": "TC" - }, - { - "name": "Tuvalu", - "dial_code": "+688", - "code": "TV" - }, - { - "name": "Uganda", - "dial_code": "+256", - "code": "UG" - }, - { - "name": "Ukraine", - "dial_code": "+380", - "code": "UA" - }, - { - "name": "United Arab Emirates", - "dial_code": "+971", - "code": "AE" - }, - { - "name": "United Kingdom", - "dial_code": "+44", - "code": "GB" - }, - { - "name": "United States", - "dial_code": "+1", - "code": "US" - }, - { - "name": "Uruguay", - "dial_code": "+598", - "code": "UY" - }, - { - "name": "Uzbekistan", - "dial_code": "+998", - "code": "UZ" - }, - { - "name": "Vanuatu", - "dial_code": "+678", - "code": "VU" - }, - { - "name": "Venezuela, Bolivarian Republic of Venezuela", - "dial_code": "+58", - "code": "VE" - }, - { - "name": "Vietnam", - "dial_code": "+84", - "code": "VN" - }, - { - "name": "Virgin Islands, British", - "dial_code": "+1284", - "code": "VG" - }, - { - "name": "Virgin Islands, U.S.", - "dial_code": "+1340", - "code": "VI" - }, - { - "name": "Wallis and Futuna", - "dial_code": "+681", - "code": "WF" - }, - { - "name": "Yemen", - "dial_code": "+967", - "code": "YE" - }, - { - "name": "Zambia", - "dial_code": "+260", - "code": "ZM" - }, - { - "name": "Zimbabwe", - "dial_code": "+263", - "code": "ZW" - } -] diff --git a/packages/mask/dashboard/components/BackupPreview/index.tsx b/packages/mask/dashboard/components/BackupPreview/index.tsx index 38bfbe8b4692..6a1898b2b5c3 100644 --- a/packages/mask/dashboard/components/BackupPreview/index.tsx +++ b/packages/mask/dashboard/components/BackupPreview/index.tsx @@ -39,7 +39,7 @@ const useStyles = makeStyles()((theme) => ({ fontWeight: 700, }, cardContent: { - padding: theme.spacing(1, 2), + padding: theme.spacing(1, 0), '&:last-child': { paddingBottom: theme.spacing(1), }, @@ -225,7 +225,7 @@ export const WalletsBackupPreview = memo(function Wal } /> - + {wallets.map((wallet) => ( diff --git a/packages/mask/dashboard/components/ConfirmDialog/index.tsx b/packages/mask/dashboard/components/ConfirmDialog/index.tsx deleted file mode 100644 index 2eccef112d69..000000000000 --- a/packages/mask/dashboard/components/ConfirmDialog/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { PropsWithChildren, ReactNode } from 'react' -import { MaskDialog } from '@masknet/theme' -import { DialogContent, DialogActions, Button, styled, buttonClasses } from '@mui/material' -import { useDashboardI18N } from '../../locales/index.js' - -const StyledButton: typeof Button = styled(Button)(() => ({ - [`&.${buttonClasses.root}`]: { - minWidth: 100, - }, -})) as any - -export interface ConfirmDialogProps extends PropsWithChildren<{}> { - title: string - open: boolean - cancelText?: ReactNode | string - confirmText?: ReactNode | string - confirmDisabled?: boolean - maxWidth?: false | 'sm' | 'xs' | 'md' | 'lg' | 'xl' - onClose(): void - onConfirm?(): void -} - -export default function ConfirmDialog(props: ConfirmDialogProps) { - const t = useDashboardI18N() - const { - title, - open, - onClose, - onConfirm, - children, - maxWidth = 'sm', - cancelText = t.settings_button_cancel(), - confirmText = t.settings_button_confirm(), - confirmDisabled = false, - } = props - return ( - - {children} - - - {cancelText} - - - {confirmText} - - - - ) -} diff --git a/packages/mask/dashboard/components/ConnectActionList/ConnectActionListItem.tsx b/packages/mask/dashboard/components/ConnectActionList/ConnectActionListItem.tsx deleted file mode 100644 index ce96aef30b4a..000000000000 --- a/packages/mask/dashboard/components/ConnectActionList/ConnectActionListItem.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { ListItemIcon, ListItemText, styled, ListItemButton } from '@mui/material' -import { MaskColorVar } from '@masknet/theme' -import type { ReactNode } from 'react' - -export interface ConnectActionListItemProps { - title: string - icon: ReactNode - onClick(): void -} - -export function ConnectActionListItem(props: ConnectActionListItemProps) { - const { title, icon, onClick } = props - return ( - - - - {icon} - - ) -} - -const Icon = styled(ListItemIcon)` - font-size: 48px; -` - -const Container = styled(ListItemButton)` - border: 1px solid ${MaskColorVar.border}; - border-radius: 8px; -` - -const Dot = styled('div')( - ({ theme }) => ` - width: 10px; - height: 10px; - border-radius: 50%; - background-color: ${MaskColorVar.iconLight}; - margin-right: ${theme.spacing(2)}; -`, -) diff --git a/packages/mask/dashboard/components/ConnectActionList/index.tsx b/packages/mask/dashboard/components/ConnectActionList/index.tsx deleted file mode 100644 index c5ea41bf5421..000000000000 --- a/packages/mask/dashboard/components/ConnectActionList/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { StyledComponent } from '@emotion/styled' -import { styled, listItemClasses, type Theme } from '@mui/material' -import type { MUIStyledCommonProps } from '@mui/system' -import type { DetailedHTMLProps, HTMLAttributes } from 'react' - -export const ConnectActionList: StyledComponent< - MUIStyledCommonProps, - DetailedHTMLProps, HTMLUListElement> -> = styled('ul')(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - listStyle: 'none', - padding: 0, - width: 520, - // TODO: deep style - [`& .${listItemClasses.root}`]: { - marginBottom: theme.spacing(1.5), - }, -})) - -export * from './ConnectActionListItem.js' diff --git a/packages/mask/dashboard/components/ContentContainer/index.tsx b/packages/mask/dashboard/components/ContentContainer/index.tsx deleted file mode 100644 index e4c1a2e4c491..000000000000 --- a/packages/mask/dashboard/components/ContentContainer/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { styled } from '@mui/material/styles' -import { MaskColorVar } from '@masknet/theme' - -export const ContentContainer = styled('div')(({ theme }) => ({ - flex: 1, - borderRadius: Number(theme.shape.borderRadius) * 5, - backgroundColor: MaskColorVar.primaryBackground, -})) diff --git a/packages/mask/dashboard/components/CreateWalletForm/index.tsx b/packages/mask/dashboard/components/CreateWalletForm/index.tsx deleted file mode 100644 index 9c472dae2745..000000000000 --- a/packages/mask/dashboard/components/CreateWalletForm/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useCallback, useState, type ReactNode } from 'react' -import { - FormControl, - ListItemIcon, - MenuItem, - Select, - styled, - Typography, - FilledInput, - type SelectChangeEvent, -} from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { useDashboardI18N } from '../../locales/index.js' - -const useStyles = makeStyles()((theme) => ({ - root: { - width: 380, - marginTop: theme.spacing(1.5), - }, - input: { - paddingTop: theme.spacing(2), - paddingBottom: theme.spacing(2), - }, - filled: { - display: 'flex', - paddingTop: theme.spacing(2), - paddingBottom: theme.spacing(2), - }, -})) - -// TODO: actions, and icon may be an img url -export interface CreateWalletFormProps { - options: Array<{ - label: string - icon: ReactNode - value: number - }> -} - -export function CreateWalletForm(props: CreateWalletFormProps) { - const { options } = props - const { classes } = useStyles() - const [selected, setSelected] = useState() - - const t = useDashboardI18N() - - return ( - - - - - - - - ) -} - -const Container = styled('div')` - display: flex; - flex-direction: column; -` - -const FormContainer = styled(FormControl)` - width: 380px; -` diff --git a/packages/mask/dashboard/components/DashboardFrame/FollowUs.png b/packages/mask/dashboard/components/DashboardFrame/FollowUs.png deleted file mode 100644 index 8d9ec75d17cf..000000000000 Binary files a/packages/mask/dashboard/components/DashboardFrame/FollowUs.png and /dev/null differ diff --git a/packages/mask/dashboard/components/DashboardFrame/Navigation.tsx b/packages/mask/dashboard/components/DashboardFrame/Navigation.tsx deleted file mode 100644 index 304fdfab4096..000000000000 --- a/packages/mask/dashboard/components/DashboardFrame/Navigation.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { useContext, type MouseEvent } from 'react' -import { useMatch, useNavigate } from 'react-router-dom' -import { - List, - ListItem as MuiListItem, - ListItemText, - ListItemIcon, - Collapse, - type Theme, - useMediaQuery, - styled, - listItemClasses, - listItemIconClasses, - type ListItemProps, - listItemTextClasses, - useTheme, -} from '@mui/material' -import { ExpandLess, ExpandMore } from '@mui/icons-material' -import { Icons } from '@masknet/icons' -import { MaskColorVar } from '@masknet/theme' -import { DashboardRoutes, NetworkPluginID } from '@masknet/shared-base' -import { useNetworkContext } from '@masknet/web3-hooks-base' -import { DashboardContext } from './context.js' -import { useDashboardI18N } from '../../locales/index.js' - -function ListItemLinkUnStyled({ - to, - ...props -}: ListItemProps & { - to: string -}) { - const navigate = useNavigate() - - return ( - { - navigate(to) - props.onClick?.(event) - }} - /> - ) -} - -const ListItemLink = styled(ListItemLinkUnStyled)(({ theme }) => { - return { - [`&.${listItemClasses.root}`]: { - color: theme.palette.mode === 'light' ? '' : 'rgba(255,255,255,.8)', - paddingLeft: theme.spacing(2), - cursor: 'pointer', - '&:hover': { - background: theme.palette.background.default, - }, - }, - [`&.${listItemClasses.selected}`]: { - color: MaskColorVar.textLink, - backgroundColor: theme.palette.background.default, - position: 'relative', - [listItemIconClasses.root]: { - color: MaskColorVar.textLink, - }, - '&:after': { - content: '""', - display: 'inline-block', - width: 5, - height: 40, - boxShadow: '-2px 0 10px 2px rgba(0, 56, 255, 0.15)', - borderRadius: 50, - background: MaskColorVar.textLink, - position: 'absolute', - right: 0, - }, - }, - } -}) - -const LogoItem = styled(MuiListItem)(({ theme }) => ({ - [`&.${listItemClasses.root}`]: { - justifyContent: 'start', - marginBottom: theme.spacing(3.5), - }, -})) as any as typeof MuiListItem - -const ListSubTextItem = styled(ListItemText)(({ theme }) => ({ - [`&.${listItemTextClasses.inset}`]: { - marginLeft: theme.spacing(2), - '&:before': { - content: '""', - display: 'inline-block', - width: 4, - height: 4, - borderRadius: 2, - background: 'currentColor', - position: 'absolute', - left: theme.spacing(9), - top: 22, - }, - }, -})) - -export interface NavigationProps { - onClose?: () => void -} -export function Navigation({ onClose }: NavigationProps) { - const { expanded, toggleNavigationExpand } = useContext(DashboardContext) - const isWalletPath = useMatch(DashboardRoutes.Wallets) - const isWalletTransferPath = useMatch(DashboardRoutes.WalletsTransfer) - const isWalletHistoryPath = useMatch(DashboardRoutes.WalletsHistory) - - const isLargeScreen = useMediaQuery((theme) => theme.breakpoints.up('lg')) - const t = useDashboardI18N() - const mode = useTheme().palette.mode - const { pluginID } = useNetworkContext() - - const onExpand = (e: MouseEvent) => { - e.stopPropagation() - toggleNavigationExpand() - } - - return ( - onClose?.()}> - {isLargeScreen ? ( - - {mode === 'dark' ? ( - - ) : ( - - )} - - ) : null} - - - {useMatch(DashboardRoutes.Personas) ? ( - - ) : ( - - )} - - - - - - {isWalletPath || isWalletHistoryPath || isWalletTransferPath ? ( - - ) : ( - - )} - - {t.wallets()} - {expanded ? : } - - - - - - - {pluginID === NetworkPluginID.PLUGIN_EVM && ( - - - - )} - {pluginID === NetworkPluginID.PLUGIN_EVM && ( - - - - )} - - - - - {useMatch(DashboardRoutes.Settings) ? ( - - ) : ( - - )} - - - - - ) -} diff --git a/packages/mask/dashboard/components/DashboardFrame/context.ts b/packages/mask/dashboard/components/DashboardFrame/context.ts deleted file mode 100644 index c648ef64fd61..000000000000 --- a/packages/mask/dashboard/components/DashboardFrame/context.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createContext } from 'react' - -export interface DashboardState { - expanded: boolean - drawerOpen: boolean - toggleNavigationExpand: () => void - toggleDrawer: () => void -} -export const DashboardContextDefault = { - expanded: true, - drawerOpen: false, - toggleDrawer: () => {}, - toggleNavigationExpand: () => {}, -} - -export const DashboardContext = createContext(DashboardContextDefault) -DashboardContext.displayName = 'DashboardContext' diff --git a/packages/mask/dashboard/components/DashboardFrame/index.tsx b/packages/mask/dashboard/components/DashboardFrame/index.tsx deleted file mode 100644 index f15d6cd6f7ac..000000000000 --- a/packages/mask/dashboard/components/DashboardFrame/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { ErrorBoundary } from '@masknet/shared-base-ui' -import { MaskColorVar } from '@masknet/theme' -import { Grid, styled, type Theme, useMediaQuery } from '@mui/material' -import { memo, Suspense, useMemo, useState, type PropsWithChildren } from 'react' -import { FollowUs } from '../FollowUs/index.js' -import { NavigationVersionFooter } from '../NavigationVersionFooter/index.js' -import { DashboardContext } from './context.js' -import { Navigation } from './Navigation.js' - -const Root = styled(Grid)(({ theme }) => ({ - backgroundColor: MaskColorVar.primaryBackground, -})) - -const LeftContainer = styled(Grid)(({ theme }) => ({ - height: '100vh', - [theme.breakpoints.up('lg')]: { - // Just meet the design size - minWidth: 232, - }, - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - paddingBottom: '22px', -})) - -export interface DashboardFrameProps extends PropsWithChildren<{}> {} - -export const DashboardFrame = memo((props: DashboardFrameProps) => { - const isLargeScreen = useMediaQuery((theme) => theme.breakpoints.up('lg')) - const [navigationExpanded, setNavigationExpanded] = useState(true) - const [drawerOpen, setDrawerOpen] = useState(false) - - const context = useMemo( - () => ({ - drawerOpen, - expanded: navigationExpanded, - toggleNavigationExpand: () => setNavigationExpanded((e) => !e), - toggleDrawer: () => setDrawerOpen((e) => !e), - }), - [drawerOpen, navigationExpanded], - ) - - return ( - - - {isLargeScreen ? ( - - -
- - -
-
- ) : null} - - - {props.children} - - -
-
- ) -}) - -DashboardFrame.displayName = 'DashboardFrame' diff --git a/packages/mask/dashboard/components/FileUpload/index.tsx b/packages/mask/dashboard/components/FileUpload/index.tsx deleted file mode 100644 index 753b339c7ffe..000000000000 --- a/packages/mask/dashboard/components/FileUpload/index.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { type ReactNode, useEffect, useState, type ChangeEvent } from 'react' -import { MaskColorVar, makeStyles } from '@masknet/theme' -import { Card, Typography } from '@mui/material' -import { Icons } from '@masknet/icons' - -const useStyles = makeStyles()({ - root: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - position: 'relative', - height: '100%', - borderRadius: 8, - }, - container: { - textAlign: 'center', - }, - file: { - position: 'absolute', - width: '100%', - height: '100%', - top: 0, - left: 0, - opacity: 0, - cursor: 'pointer', - }, - text: { - color: MaskColorVar.textSecondary, - fontSize: 13, - }, -}) - -export interface FileUploadProps { - width?: number - height?: number - readAsText?: boolean - onChange: (file: File, content?: string) => void - accept?: string - icon?: ReactNode -} - -export default function FileUpload({ width, height, readAsText, onChange, accept, icon }: FileUploadProps) { - const { classes } = useStyles() - const [file, setFile] = useState() - - const handleChange = ({ target }: ChangeEvent) => { - if (target.files) { - setFile(target.files[0]) - } - } - - useEffect(() => { - if (file) { - if (readAsText) { - file.text().then((result) => onChange(file, result)) - } else { - onChange(file) - } - } - }, [file, readAsText, onChange]) - return ( - -
- {icon ?? } - Please click or drag the file to here -
- -
- ) -} diff --git a/packages/mask/dashboard/components/FollowUs/index.tsx b/packages/mask/dashboard/components/FollowUs/index.tsx deleted file mode 100644 index 5d9dc2d66e14..000000000000 --- a/packages/mask/dashboard/components/FollowUs/index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { styled, Typography, useTheme } from '@mui/material' -import { openWindow } from '@masknet/shared-base-ui' -import { Icons } from '@masknet/icons' -import { useDashboardI18N } from '../../locales/index.js' - -const FollowUsContainer = styled('div')(() => ({ - background: 'linear-gradient(90deg, #ACCBEE 0%, #E7F0FD 100%)', - borderRadius: 12, - fontSize: 16, - lineHeight: '22px', - fontWeight: 500, - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - height: '100%', - padding: '0 27px', - color: '#ffffff', - cursor: 'pointer', -})) - -const TwitterColoredContainer = styled('div')(() => ({ - display: 'flex', - alignItems: 'center', - padding: 9, - borderRadius: 99, - background: 'linear-gradient(90deg, rgba(253, 251, 251, 0.8) 0%, rgba(235, 237, 238, 0.8) 100%)', -})) - -function openMaskNetwork() { - return openWindow('https://twitter.com/realMaskNetwork') -} -export function FollowUs() { - const t = useDashboardI18N() - const theme = useTheme() - - return ( -
openMaskNetwork()}> - - {t.follow_us()} - - - - -
- ) -} diff --git a/packages/mask/dashboard/components/LoadingButton/index.tsx b/packages/mask/dashboard/components/LoadingButton/index.tsx deleted file mode 100644 index 6a84a75f8d62..000000000000 --- a/packages/mask/dashboard/components/LoadingButton/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { getMaskColor, makeStyles, MaskLoadingButton, LoadingBase } from '@masknet/theme' -import { memo } from 'react' -import type { LoadingButtonProps } from '@mui/lab' - -interface DashboardLoadingButtonProps extends LoadingButtonProps { - onClick(event: React.MouseEvent): Promise -} -const useStyles = makeStyles()((theme) => ({ - icon: { - color: getMaskColor(theme).white, - width: '100%', - }, - loadingButtonOverride: { - opacity: '1 !important', - }, -})) - -export const LoadingButton = memo((props) => { - const { onClick, children, ...rest } = props - const { classes, cx } = useStyles() - return ( - } - onClick={onClick} - {...rest}> - {children} - - ) -}) diff --git a/packages/mask/dashboard/components/LoadingCard/index.tsx b/packages/mask/dashboard/components/LoadingCard/index.tsx deleted file mode 100644 index ec471afdb6a4..000000000000 --- a/packages/mask/dashboard/components/LoadingCard/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { LoadingStatus } from '@masknet/shared' -import { Card } from '@mui/material' -import { memo } from 'react' - -interface LoadingProps { - text?: string -} - -export const LoadingCard = memo(function LoadingCard({ text = 'Loading' }: LoadingProps) { - return ( - - {text} - - ) -}) diff --git a/packages/mask/dashboard/components/LoadingPlaceholder/index.tsx b/packages/mask/dashboard/components/LoadingPlaceholder/index.tsx deleted file mode 100644 index 73ec174fe5bf..000000000000 --- a/packages/mask/dashboard/components/LoadingPlaceholder/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { memo } from 'react' -import { Box, Typography } from '@mui/material' -import { makeStyles, MaskColorVar, LoadingBase } from '@masknet/theme' -import { useDashboardI18N } from '../../locales/index.js' - -const useStyles = makeStyles()((theme) => ({ - container: { - width: '100%', - height: '100%', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - }, - prompt: { - color: MaskColorVar.textLight, - fontSize: theme.typography.pxToRem(12), - lineHeight: theme.typography.pxToRem(16), - marginTop: theme.spacing(2.5), - }, -})) - -export const LoadingPlaceholder = memo(() => { - const { classes } = useStyles() - const t = useDashboardI18N() - return ( - - - {t.wallets_loading_token()} - - ) -}) diff --git a/packages/mask/dashboard/components/MaskAlert/index.tsx b/packages/mask/dashboard/components/MaskAlert/index.tsx deleted file mode 100644 index 97baaac9d7b0..000000000000 --- a/packages/mask/dashboard/components/MaskAlert/index.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { memo, useCallback, useState } from 'react' -import { Alert, alertClasses, Collapse, styled, IconButton } from '@mui/material' -import { Close as CloseIcon } from '@mui/icons-material' -import { getMaskColor, MaskColorVar } from '@masknet/theme' -import { Icons } from '@masknet/icons' - -const InfoAlert = styled(Alert)(({ theme }) => ({ - [`& > .${alertClasses.message}`]: { - display: 'flex', - alignItems: 'center', - lineHeight: '16px', - color: MaskColorVar.secondaryInfoText, - fontSize: theme.typography.caption.fontSize, - }, - [`& > .${alertClasses.icon}`]: { - alignItems: 'center', - }, - [`& > .${alertClasses.action}`]: { - alignItems: 'center', - }, - // standard - [`&.${alertClasses.standardInfo}`]: { - backgroundColor: getMaskColor(theme).infoBackground, - }, - [`&.${alertClasses.standardInfo} .${alertClasses.icon}`]: { - color: getMaskColor(theme).secondaryInfoText, - }, - [`&.${alertClasses.standardInfo} .${alertClasses.action}`]: { - color: getMaskColor(theme).secondaryInfoText, - }, - // error - [`&.${alertClasses.standardError}`]: { - backgroundColor: MaskColorVar.redMain.alpha(0.1), - }, - [`&.${alertClasses.standardError} .${alertClasses.icon}`]: { - color: getMaskColor(theme).redMain, - 'path:first-child': { - fill: MaskColorVar.redMain.alpha(0.5), - }, - path: { - fill: getMaskColor(theme).redMain, - }, - }, - [`&.${alertClasses.standardError} .${alertClasses.action}`]: { - color: getMaskColor(theme).redMain, - }, - [`&.${alertClasses.standardError} .${alertClasses.message}`]: { - color: getMaskColor(theme).redMain, - }, - // success - [`&.${alertClasses.standardSuccess}`]: { - backgroundColor: MaskColorVar.greenMain.alpha(0.1), - }, - [`&.${alertClasses.standardSuccess} .${alertClasses.icon}`]: { - color: getMaskColor(theme).greenMain, - }, - [`&.${alertClasses.standardSuccess} .${alertClasses.action}`]: { - color: getMaskColor(theme).greenMain, - }, - [`&.${alertClasses.standardSuccess} .${alertClasses.message}`]: { - color: getMaskColor(theme).greenMain, - }, -})) - -export interface MaskAlertProps { - description: string - type?: 'error' | 'info' | 'success' | 'warning' -} - -const AlertIconMapping = { - error: , - info: , - success: , - warning: , -} - -export const MaskAlert = memo(({ description, type = 'info' }: MaskAlertProps) => { - const [openAlert, setOpenAlert] = useState(true) - - return ( - - setOpenAlert(false), [])}> - - - }> - {description} - - - ) -}) diff --git a/packages/mask/dashboard/components/MaskAvatar/index.tsx b/packages/mask/dashboard/components/MaskAvatar/index.tsx deleted file mode 100644 index 649f2d085e00..000000000000 --- a/packages/mask/dashboard/components/MaskAvatar/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { memo } from 'react' -import { Avatar, Stack } from '@mui/material' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { Icons } from '@masknet/icons' -import { usePersonaAvatar } from '../../../shared-ui/index.js' - -const useStyles = makeStyles()((theme) => ({ - author: { - color: MaskColorVar.secondaryBackground, - cursor: 'pointer', - }, -})) - -interface MaskAvatarProps { - size?: number - onClick?(): void -} - -export const MaskAvatar = memo(({ size = 36, onClick }) => { - const { classes } = useStyles() - const avatar = usePersonaAvatar() - const commonProps = { - sx: { - width: size, - height: size, - display: 'inline-block', - backgroundColor: MaskColorVar.lightBackground, - borderRadius: '50%', - }, - onClick, - className: classes.author, - } - - if (!avatar) { - return ( - - - - ) - } - - return -}) diff --git a/packages/mask/dashboard/components/Mnemonic/DesktopMnemonicConfirm.tsx b/packages/mask/dashboard/components/Mnemonic/DesktopMnemonicConfirm.tsx index c5b25a5f93cc..525723b4cfef 100644 --- a/packages/mask/dashboard/components/Mnemonic/DesktopMnemonicConfirm.tsx +++ b/packages/mask/dashboard/components/Mnemonic/DesktopMnemonicConfirm.tsx @@ -1,7 +1,7 @@ import { memo, useCallback } from 'react' import { useDrop } from 'react-use' -import { MaskTextField, makeStyles } from '@masknet/theme' -import { Grid, Typography } from '@mui/material' +import { makeStyles } from '@masknet/theme' +import { Grid, TextField, Typography } from '@mui/material' const useStyles = makeStyles()((theme) => ({ input: { @@ -50,7 +50,7 @@ export const DesktopMnemonicConfirm = memo(function DesktopMnemonicConfirm(props const no = i + 1 return ( - {no}., + size: 'small', inputProps: { style: { textAlign: 'center', diff --git a/packages/mask/dashboard/components/NavigationVersionFooter/index.tsx b/packages/mask/dashboard/components/NavigationVersionFooter/index.tsx deleted file mode 100644 index a9280afde18e..000000000000 --- a/packages/mask/dashboard/components/NavigationVersionFooter/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { memo } from 'react' -import { MaskColorVar } from '@masknet/theme' -import { styled } from '@mui/system' -import { useDashboardI18N } from '../../locales/index.js' -import { useBuildInfo } from '@masknet/shared-base-ui' - -const VersionContainer = styled('div')(() => ({ - color: MaskColorVar.textSecondary, - textAlign: 'center', - fontSize: '12px', -})) - -export const NavigationVersionFooter = memo(() => { - const t = useDashboardI18N() - const version = useBuildInfo().VERSION || 'unknown' - return {t.version_of_stable({ version })} -}) diff --git a/packages/mask/dashboard/components/OnboardingWriter/index.tsx b/packages/mask/dashboard/components/OnboardingWriter/index.tsx index ca6e46932407..49b369f5a88b 100644 --- a/packages/mask/dashboard/components/OnboardingWriter/index.tsx +++ b/packages/mask/dashboard/components/OnboardingWriter/index.tsx @@ -1,7 +1,7 @@ import { isArray, sum } from 'lodash-es' import { useState, useMemo, useEffect, cloneElement } from 'react' -import { useDashboardI18N } from '../../locales/i18n_generated.js' import { makeStyles } from '@masknet/theme' +import { useDashboardI18N } from '../../locales/i18n_generated.js' const useStyles = makeStyles()((theme) => ({ typed: { diff --git a/packages/mask/dashboard/components/PageFrame/index.tsx b/packages/mask/dashboard/components/PageFrame/index.tsx deleted file mode 100644 index 413416442e45..000000000000 --- a/packages/mask/dashboard/components/PageFrame/index.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { memo, useContext } from 'react' -import { - AppBar, - Box, - Drawer, - Grid, - IconButton, - paperClasses, - styled, - type Theme, - Toolbar, - Typography, - useMediaQuery, - useTheme, -} from '@mui/material' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { Close as CloseIcon, Menu as MenuIcon } from '@mui/icons-material' -import Color from 'color' -import { FollowUs } from '../FollowUs/index.js' -import { DashboardContext } from '../DashboardFrame/context.js' -import { Navigation } from '../DashboardFrame/Navigation.js' -import { Icons } from '@masknet/icons' -import { ErrorBoundary } from '@masknet/shared-base-ui' -import { NavigationVersionFooter } from '../NavigationVersionFooter/index.js' - -const MaskLogo = styled(Grid)` - flex-basis: 212px; - max-width: 212px; - & > svg { - flex: 1; - } -` - -const MenuButton = styled(IconButton)(({ theme }) => ({ - paddingLeft: theme.spacing(1.5), - paddingRight: theme.spacing(1.5), -})) - -const PageTitle = styled(Grid)(({ theme }) => ({ - minHeight: 40, - alignItems: 'center', - paddingLeft: theme.spacing(4.25), - '& > h6': { - fontSize: '1.5rem', - }, - [theme.breakpoints.down('lg')]: { - flex: 1, - }, -})) - -const Containment = styled(Grid)(({ theme }) => ({ - maxWidth: '100%', - display: 'flex', - height: 'calc(100vh - 64px)', - overflow: 'hidden', -})) - -const NavigationDrawer = styled(Drawer)(({ theme }) => ({ - top: `${theme.mixins.toolbar.minHeight}px !important`, - // https://github.com/mui-org/material-ui/issues/20012#issuecomment-770654893 - [`& > .${paperClasses.root}`]: { - width: 232, - top: theme.mixins.toolbar.minHeight, - paddingTop: '28px', - background: new Color(theme.palette.background.paper).alpha(0.8).toString(), - backdropFilter: 'blur(4px)', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - paddingBottom: `calc( 22px + ${theme.mixins.toolbar.minHeight}px)`, - }, -})) - -const ShapeHelper = styled('div')(({ theme }) => ({ - padding: theme.spacing(3), - paddingBottom: 0, - borderTopLeftRadius: Number(theme.shape.borderRadius) * 5, - borderTopRightRadius: Number(theme.shape.borderRadius) * 5, - backgroundColor: theme.palette.mode === 'dark' ? '#1B1E38' : MaskColorVar.secondaryBackground, - display: 'flex', - flexDirection: 'column', - flex: 1, - overflow: 'auto', -})) - -const ContentContainer = styled('div')(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - borderRadius: Number(theme.shape.borderRadius) * 5, - backgroundColor: 'transparent', - minHeight: '100%', - position: 'relative', - '&:after': { - content: '""', - display: 'block', - paddingTop: theme.spacing(3), - }, -})) - -const useStyle = makeStyles()((theme) => ({ - toolbarGutters: { - backgroundColor: MaskColorVar.primaryBackground, - [theme.breakpoints.up('lg')]: { - paddingLeft: theme.spacing(0), - }, - [theme.breakpoints.down('lg')]: { - paddingLeft: theme.spacing(1), - }, - }, -})) - -export interface PageFrameProps extends React.PropsWithChildren<{}> { - title: React.ReactNode | string - primaryAction?: React.ReactNode - noBackgroundFill?: boolean -} - -export const PageFrame = memo((props: PageFrameProps) => { - const left = typeof props.title === 'string' ? {props.title} : props.title - const right = props.primaryAction - const isLargeScreen = useMediaQuery((theme) => theme.breakpoints.up('lg')) - const { drawerOpen, toggleDrawer } = useContext(DashboardContext) - const isDark = useTheme().palette.mode === 'dark' - const { classes } = useStyle() - - return ( - <> - - - {!isLargeScreen && ( - - - {drawerOpen ? : } - - {isDark ? ( - - ) : ( - - )} - - )} - - {left} - - {right} - - - - - {!isLargeScreen && ( - - -
- - -
-
- )} - - - {props.children} - - -
- - ) -}) diff --git a/packages/mask/dashboard/components/PasswordField/index.tsx b/packages/mask/dashboard/components/PasswordField/index.tsx index 8a3b8884fe34..b1fefc2dca53 100644 --- a/packages/mask/dashboard/components/PasswordField/index.tsx +++ b/packages/mask/dashboard/components/PasswordField/index.tsx @@ -13,8 +13,10 @@ const PasswordField = forwardRef(({ show = true, ...props }: PasswordFieldProps, {...props} ref={ref} type={showPassword ? 'text' : 'password'} + size="medium" InputProps={{ ...props.InputProps, + size: 'medium', disableUnderline: true, endAdornment: show ? ( diff --git a/packages/mask/dashboard/components/PrimaryButton/index.tsx b/packages/mask/dashboard/components/PrimaryButton/index.tsx index 79a92f86a6c7..c7b6d606709b 100644 --- a/packages/mask/dashboard/components/PrimaryButton/index.tsx +++ b/packages/mask/dashboard/components/PrimaryButton/index.tsx @@ -1,6 +1,6 @@ +import { memo } from 'react' import { ActionButton, makeStyles } from '@masknet/theme' import { buttonClasses, type ButtonProps } from '@mui/material/Button' -import { memo } from 'react' export interface ActionButtonProps extends ButtonProps { width?: number | string diff --git a/packages/mask/dashboard/components/RegisterFrame/ColumnContentLayout.tsx b/packages/mask/dashboard/components/RegisterFrame/ColumnContentLayout.tsx index 678a189e58d0..2a96c519cbde 100644 --- a/packages/mask/dashboard/components/RegisterFrame/ColumnContentLayout.tsx +++ b/packages/mask/dashboard/components/RegisterFrame/ColumnContentLayout.tsx @@ -4,33 +4,7 @@ import { Box, Typography } from '@mui/material' import { styled } from '@mui/material/styles' import { useDashboardI18N } from '../../locales/index.js' -export const ColumnContentLayout = styled('div')` - display: flex; - flex-direction: column; - flex: 1; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; -` - -export const Body = styled('main')(({ theme }) => ({ - flex: '1 5', - width: '78%', - [theme.breakpoints.down('md')]: { - width: '95%', - }, -})) - -export const Footer = styled('footer')(({ theme }) => ({ - flex: 1, - width: '78%', - [theme.breakpoints.down('md')]: { - width: '95%', - }, -})) - -export const LogoBoxStyled = styled(Box)(({ theme }) => ({ +const LogoBoxStyled = styled(Box)(({ theme }) => ({ marginBottom: theme.spacing(10), display: 'flex', flexDirection: 'column', diff --git a/packages/mask/dashboard/components/RegisterFrame/RowLayout.tsx b/packages/mask/dashboard/components/RegisterFrame/RowLayout.tsx deleted file mode 100644 index 547ccb5cbf77..000000000000 --- a/packages/mask/dashboard/components/RegisterFrame/RowLayout.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { memo } from 'react' -import { Icons } from '@masknet/icons' -import { styled } from '@mui/material/styles' -import { Container } from '@mui/material' -import { MaskColorVar } from '@masknet/theme' - -const LayoutContainer = styled('div')( - ({ theme }) => ` - display: flex; - position: absolute; - height: 100%; - width: 100%; - background: ${MaskColorVar.primaryBackground} -`, -) - -const LeftSide = styled('div')(({ theme }) => ({ - padding: theme.spacing(5), - width: '30%', - maxWidth: '400px', - background: theme.palette.primary.main, - [theme.breakpoints.down('md')]: { - width: '25%', - padding: theme.spacing(3), - }, - [theme.breakpoints.down('sm')]: { - display: 'none', - }, -})) - -const RightContent = styled('div')( - ({ theme }) => ` - flex: 1; - display: flex; - justify-content: center; - max-height: 100%; - overflow: auto; - background: transparent; -`, -) - -interface RowLayoutProps extends React.PropsWithChildren<{}> {} - -export const RowLayout = memo(({ children }: RowLayoutProps) => { - return ( - - - - - - {children} - - - ) -}) diff --git a/packages/mask/dashboard/components/Restore/BackupInfoCard.tsx b/packages/mask/dashboard/components/Restore/BackupInfoCard.tsx index edbe4e5901ab..5aad0b12bdcf 100644 --- a/packages/mask/dashboard/components/Restore/BackupInfoCard.tsx +++ b/packages/mask/dashboard/components/Restore/BackupInfoCard.tsx @@ -5,6 +5,7 @@ import type { BackupFileInfo } from '../../type.js' import { formatFileSize } from '@masknet/kit' import { FileFrame } from '@masknet/shared' import { makeStyles } from '@masknet/theme' +import fromUnixTime from 'date-fns/fromUnixTime' const useStyles = makeStyles()((theme) => ({ file: { @@ -35,7 +36,7 @@ export const BackupInfoCard = memo(function BackupInfoCard({ info }: BackupInfoP operations={{formatFileSize(info.size, true)}}> {Number.isNaN(info.uploadedAt) ? null : ( - {formatDateTime(info.uploadedAt, 'yyyy-MM-dd hh:mm')} + {formatDateTime(fromUnixTime(info.uploadedAt), 'yyyy-MM-dd HH:mm')} )} diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/ConfirmBackupInfo.tsx b/packages/mask/dashboard/components/Restore/RestoreFromCloud/ConfirmBackupInfo.tsx index 85638dccbe0f..07140d9e093f 100644 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/ConfirmBackupInfo.tsx +++ b/packages/mask/dashboard/components/Restore/RestoreFromCloud/ConfirmBackupInfo.tsx @@ -75,7 +75,8 @@ export const ConfirmBackupInfo = memo(function ConfirmBackupInfo() { { setErrorMessage('') setPassword(e.currentTarget.value) diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/EmailField.tsx b/packages/mask/dashboard/components/Restore/RestoreFromCloud/EmailField.tsx index d7f6ee517ff0..846782a6704b 100644 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/EmailField.tsx +++ b/packages/mask/dashboard/components/Restore/RestoreFromCloud/EmailField.tsx @@ -1,5 +1,5 @@ -import { MaskTextField, SendingCodeField, useCustomSnackbar } from '@masknet/theme' -import { Box } from '@mui/material' +import { SendingCodeField, useCustomSnackbar } from '@masknet/theme' +import { Box, TextField } from '@mui/material' import { memo, useCallback, useLayoutEffect, useState } from 'react' import { useAsyncFn } from 'react-use' import { usePersonaRecovery } from '../../../contexts/RecoveryContext.js' @@ -74,12 +74,15 @@ export const EmailField = memo(function EmailField() { ) }, [account, code, loading, disabled]) - const hasError = invalidEmail || !!error - const errorMessage = invalidEmail ? t.sign_in_account_cloud_backup_email_format_error() : error || '' + const hasError = sendCodeError?.message.includes('SendTemplatedEmail') || invalidEmail || !!error + const errorMessage = + sendCodeError?.message.includes('SendTemplatedEmail') || invalidEmail + ? t.sign_in_account_cloud_backup_email_format_error() + : error || '' return ( <> - pick(phoneForm, 'dialingCode', 'phone', 'country'), [phoneForm]) - const setPhoneConfig = useCallback((newConfig: PhoneConfig) => dispatch({ type: 'SET_PHONE', form: newConfig }), []) + const phoneConfig = useMemo(() => pick(phoneForm, 'dialingCode', 'phone'), [phoneForm]) + const onPhoneNumberChange = useCallback( + (phoneNumber: string) => { + dispatch({ type: 'SET_PHONE', form: { ...phoneConfig, phone: phoneNumber } }) + }, + [phoneConfig], + ) + const onCountryCodeChange = useCallback( + (code: string) => { + dispatch({ type: 'SET_PHONE', form: { ...phoneConfig, dialingCode: code } }) + }, + [phoneConfig], + ) + useEffect(() => { if (dialingCode) return dispatch({ type: 'SET_PHONE', form: { dialingCode: guessCallingCode() } }) @@ -84,14 +92,18 @@ export const PhoneField = memo(function PhoneField() { return ( <> onPhoneNumberChange(event.target.value)} + error={invalidPhone} + helperText={invalidPhone ? t.data_recovery_invalid_mobile() : error || ''} + value={phoneForm.phone} /> { setCodeError('') diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/index.tsx b/packages/mask/dashboard/components/Restore/RestoreFromCloud/index.tsx index 8e2de18a85f4..96bf6156661d 100644 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/index.tsx +++ b/packages/mask/dashboard/components/Restore/RestoreFromCloud/index.tsx @@ -14,10 +14,10 @@ import { RestoreContext } from './RestoreProvider.js' import { RestoreStep } from './restoreReducer.js' import { InputForm } from './InputForm.js' import { ConfirmBackupInfo } from './ConfirmBackupInfo.js' -import { PersonaContext } from '../../../hooks/usePersonaContext.js' import { UserContext } from '../../../../shared-ui/index.js' import { AccountType } from '../../../type.js' import { BackupPreview } from '../../BackupPreview/index.js' +import { PersonaContext } from '@masknet/shared' interface RestoreProps { onRestore: () => Promise @@ -46,12 +46,14 @@ const RestoreFromCloudInner = memo(function RestoreFromCloudInner() { const navigate = useNavigate() const { showSnackbar } = useCustomSnackbar() const { user, updateUser } = UserContext.useContainer() - const { currentPersona, changeCurrentPersona } = PersonaContext.useContainer() + const { currentPersona } = PersonaContext.useContainer() const { state, dispatch } = RestoreContext.useContainer() const { account, accountType, backupSummary, password, backupDecrypted } = state const [openSynchronizePasswordDialog, toggleSynchronizePasswordDialog] = useState(false) + const changeCurrentPersona = useCallback(Services.Settings.setCurrentPersonaIdentifier, []) + const restoreCallback = useCallback(async () => { if (!currentPersona) { const lastedPersona = await Services.Identity.queryLastPersonaCreated() @@ -67,7 +69,7 @@ const RestoreFromCloudInner = memo(function RestoreFromCloudInner() { } } toggleSynchronizePasswordDialog(true) - }, [currentPersona, account, accountType, user, toggleSynchronizePasswordDialog, updateUser]) + }, [currentPersona, account, accountType, user, toggleSynchronizePasswordDialog, updateUser, changeCurrentPersona]) const handleRestore = useCallback(async () => { dispatch({ type: 'SET_LOADING', loading: true }) diff --git a/packages/mask/dashboard/components/Restore/RestoreFromCloud/restoreReducer.ts b/packages/mask/dashboard/components/Restore/RestoreFromCloud/restoreReducer.ts index 41b1aeabf7ed..d06f6908b497 100644 --- a/packages/mask/dashboard/components/Restore/RestoreFromCloud/restoreReducer.ts +++ b/packages/mask/dashboard/components/Restore/RestoreFromCloud/restoreReducer.ts @@ -1,5 +1,4 @@ import type { BackupSummary } from '@masknet/backup-format' -import type { PhoneNumberFieldValue } from '@masknet/theme' import { produce } from 'immer' import { AccountType, type BackupFileInfo } from '../../../type.js' @@ -23,7 +22,9 @@ export interface RestoreState { phoneForm: { account: string code: string - } & PhoneNumberFieldValue + dialingCode: string + phone: string + } backupFileInfo: BackupFileInfo | null backupSummary: BackupSummary | null backupDecrypted: string diff --git a/packages/mask/dashboard/components/Restore/RestoreFromPrivateKey.tsx b/packages/mask/dashboard/components/Restore/RestoreFromPrivateKey.tsx index 8dac8aafda77..dade56e54959 100644 --- a/packages/mask/dashboard/components/Restore/RestoreFromPrivateKey.tsx +++ b/packages/mask/dashboard/components/Restore/RestoreFromPrivateKey.tsx @@ -1,8 +1,8 @@ import { zodResolver } from '@hookform/resolvers/zod' import { Controller, useForm } from 'react-hook-form' import type { UseFormSetError, SubmitHandler } from 'react-hook-form' -import { MaskTextField, makeStyles } from '@masknet/theme' -import { Box } from '@mui/material' +import { makeStyles } from '@masknet/theme' +import { Box, TextField } from '@mui/material' import { memo, useCallback, useLayoutEffect } from 'react' import { useNavigate } from 'react-router-dom' import { z } from 'zod' @@ -12,6 +12,7 @@ import { usePersonaRecovery } from '../../contexts/index.js' const useStyles = makeStyles()((theme) => ({ input: { + paddingTop: 12, backgroundColor: theme.palette.maskColor.input, color: theme.palette.maskColor.main, }, @@ -73,11 +74,12 @@ export const RestoreFromPrivateKey = memo(function RestoreFromPrivateKey({ ( - setPassword(e.target.value)} diff --git a/packages/mask/dashboard/components/Restore/RestoreWalletFromLocal.tsx b/packages/mask/dashboard/components/Restore/RestoreWalletFromLocal.tsx index 017b6814679b..cd99a870130b 100644 --- a/packages/mask/dashboard/components/Restore/RestoreWalletFromLocal.tsx +++ b/packages/mask/dashboard/components/Restore/RestoreWalletFromLocal.tsx @@ -95,6 +95,7 @@ export const RestoreWalletFromLocal = memo(function RestorePersonaFromLocal({ {!readingFile ? ( { diff --git a/packages/mask/dashboard/components/WalletConnect/index.tsx b/packages/mask/dashboard/components/WalletConnect/index.tsx deleted file mode 100644 index eca7c2f9e3c4..000000000000 --- a/packages/mask/dashboard/components/WalletConnect/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { memo } from 'react' -import { styled, Typography } from '@mui/material' -import { WalletQRCodeContainer } from '../WalletQRCodeContainer/index.js' -import { MaskColorVar } from '@masknet/theme' -import { useDashboardI18N } from '../../locales/index.js' - -const Container = styled('div')` - display: flex; - flex-direction: column; - align-items: center; -` - -const Tip = styled(Typography)(({ theme }) => ({ - textAlign: 'center', - color: MaskColorVar.textSecondary, - fontSize: theme.typography.body1.fontSize, - marginBottom: theme.spacing(5), -})) - -export const WalletConnect = memo(() => { - const t = useDashboardI18N() - return ( - - {t.wallets_wallet_connect_title()} - - - ) -}) diff --git a/packages/mask/dashboard/components/WalletQRCodeContainer/index.tsx b/packages/mask/dashboard/components/WalletQRCodeContainer/index.tsx deleted file mode 100644 index f6ba94217902..000000000000 --- a/packages/mask/dashboard/components/WalletQRCodeContainer/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { memo } from 'react' -import { styled } from '@mui/material' - -const QRCodeContainer = styled('div')( - ({ width, height, border: { borderWidth, borderHeight } }: WalletQRCodeProps) => ` - width: ${width}px; - height: ${height}px; - background: linear-gradient(to right, black ${borderHeight}px, transparent ${borderHeight}px) 0 0, - linear-gradient(to right, black ${borderHeight}px, transparent ${borderHeight}px) 0 100%, - linear-gradient(to left, black ${borderHeight}px, transparent ${borderHeight}px) 100% 0, - linear-gradient(to left, black ${borderHeight}px, transparent ${borderHeight}px) 100% 100%, - linear-gradient(to bottom, black ${borderHeight}px, transparent ${borderHeight}px) 0 0, - linear-gradient(to bottom, black ${borderHeight}px, transparent ${borderHeight}px) 100% 0, - linear-gradient(to top, black ${borderHeight}px, transparent ${borderHeight}px) 0 100%, - linear-gradient(to top, black ${borderHeight}px, transparent ${borderHeight}px) 100% 100%; - - background-repeat: no-repeat; - background-size: ${borderWidth}px ${borderWidth}px; - padding: ${borderHeight}px; -`, -) - -export interface WalletQRCodeProps extends React.PropsWithChildren<{}> { - width: number - height: number - border: { - borderWidth: number - borderHeight: number - } -} - -export const WalletQRCodeContainer = memo((props: WalletQRCodeProps) => { - return {props.children} -}) diff --git a/packages/mask/dashboard/constants.ts b/packages/mask/dashboard/constants.ts deleted file mode 100644 index 46de6b3f5667..000000000000 --- a/packages/mask/dashboard/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const COUNTRY_ICON_URL = 'https://mask-assets.pages.dev/countries/' diff --git a/packages/mask/dashboard/contexts/CloudBackupFormContext.tsx b/packages/mask/dashboard/contexts/CloudBackupFormContext.tsx index c4d1aa0b2c20..b4d18c8f3d05 100644 --- a/packages/mask/dashboard/contexts/CloudBackupFormContext.tsx +++ b/packages/mask/dashboard/contexts/CloudBackupFormContext.tsx @@ -4,7 +4,8 @@ import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' import { useTabs } from '@masknet/theme' -import { phoneRegexp } from '../utils/regexp.js' +import { emailRegexp, phoneRegexp } from '../utils/regexp.js' +import guessCallingCode from 'guess-calling-code' export interface CloudBackupFormInputs { email: string @@ -28,14 +29,16 @@ function useCloudBackupFormContext() { email: '', phone: '', code: '', - countryCode: '+93', + countryCode: guessCallingCode(), }, resolver: zodResolver( z .object({ email: currentTab === tabs.email - ? z.string().email(t.cloud_backup_incorrect_email_address()) + ? z + .string() + .refine((email) => emailRegexp.test(email), t.cloud_backup_incorrect_email_address()) : z.string().optional(), countryCode: currentTab === tabs.mobile ? z.string() : z.string().optional(), phone: @@ -51,7 +54,7 @@ function useCloudBackupFormContext() { (data) => { if (currentTab !== tabs.mobile) return true if (!data.countryCode || !data.phone) return false - return phoneRegexp.test(data.countryCode + data.phone) + return phoneRegexp.test(`+${data.countryCode} ${data.phone}`) }, { message: t.settings_dialogs_incorrect_phone(), diff --git a/packages/mask/dashboard/hooks/useBackupFormState.ts b/packages/mask/dashboard/hooks/useBackupFormState.ts index f8410a69e84d..8fcfb170e0bc 100644 --- a/packages/mask/dashboard/hooks/useBackupFormState.ts +++ b/packages/mask/dashboard/hooks/useBackupFormState.ts @@ -18,14 +18,13 @@ export function useBackupFormState() { const { value: hasPassword } = useAsync(Services.Wallet.hasPassword, []) const { value: previewInfo, loading } = useAsync(Services.Backup.generateBackupPreviewInfo, []) const { user } = UserContext.useContainer() - const [backupPersonas, setBackupPersonas] = useState(true) const [backupWallets, setBackupWallets] = useState(false) const formState = useForm({ mode: 'onBlur', context: { user, - backupPersonas, + backupWallets, hasPassword, }, @@ -58,9 +57,7 @@ export function useBackupFormState() { hasPassword, previewInfo, loading, - backupPersonas, backupWallets, - setBackupPersonas, setBackupWallets, formState, } diff --git a/packages/mask/dashboard/hooks/useConnectSite.ts b/packages/mask/dashboard/hooks/useConnectSite.ts index 9c77b8a53d63..2ecc7a72a280 100644 --- a/packages/mask/dashboard/hooks/useConnectSite.ts +++ b/packages/mask/dashboard/hooks/useConnectSite.ts @@ -1,16 +1,6 @@ import { useAsyncFn } from 'react-use' import Services from '#services' -import type { ProfileIdentifier } from '@masknet/shared-base' -import type { AsyncFnReturn } from 'react-use/lib/useAsyncFn.js' export function useConnectSite() { return useAsyncFn(Services.SiteAdaptor.connectSite) } - -export function useOpenProfilePage() { - return useAsyncFn(Services.SiteAdaptor.openProfilePage) -} - -export function useDisconnectSite(): AsyncFnReturn<(identifier: ProfileIdentifier) => Promise> { - return useAsyncFn((identifier: ProfileIdentifier) => Services.Identity.detachProfile(identifier)) -} diff --git a/packages/mask/dashboard/hooks/useCreatePersona.ts b/packages/mask/dashboard/hooks/useCreatePersona.ts deleted file mode 100644 index 2cce8d4906ca..000000000000 --- a/packages/mask/dashboard/hooks/useCreatePersona.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useAsyncFn } from 'react-use' -import { MaskMessages } from '@masknet/shared-base' -import Services from '#services' -import { delay } from '@masknet/kit' -import type { AsyncFnReturn } from 'react-use/lib/useAsyncFn.js' - -export function useCreatePersona(): AsyncFnReturn<(nickName: string) => Promise> { - return useAsyncFn(async (nickName: string) => { - // TODO: should second parameter be the password? - await Services.Identity.createPersonaByMnemonic(nickName, '') - await delay(300) - MaskMessages.events.ownPersonaChanged.sendToAll(undefined) - }) -} diff --git a/packages/mask/dashboard/hooks/useOperateBindingProof.ts b/packages/mask/dashboard/hooks/useOperateBindingProof.ts deleted file mode 100644 index 17778d9d509c..000000000000 --- a/packages/mask/dashboard/hooks/useOperateBindingProof.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useAsyncFn } from 'react-use' -import { NextIDProof } from '@masknet/web3-providers' -import { - type ECKeyIdentifier, - type NextIDAction, - NextIDPlatform, - type ProfileIdentifier, - SignType, -} from '@masknet/shared-base' -import { MaskMessages } from '@masknet/shared-base' -import Services from '#services' - -export function useDeleteBound() { - return useAsyncFn( - async (identifier: ECKeyIdentifier, profile: ProfileIdentifier, network: string, action: NextIDAction) => { - const persona = await Services.Identity.queryPersona(identifier) - if (!persona) throw new Error('Failed to get persona') - const username = profile.userId.toLowerCase() - const platform = network.split('.')[0] || NextIDPlatform.Twitter - const payload = await NextIDProof.createPersonaPayload( - persona.identifier.publicKeyAsHex, - action, - username, - platform as NextIDPlatform, - ) - if (!payload) throw new Error('Failed to create persona payload.') - const signature = await Services.Identity.signWithPersona( - SignType.Message, - payload.signPayload, - persona.identifier, - location.origin, - true, - ) - if (!signature) throw new Error('Failed to sign by persona.') - await NextIDProof.bindProof( - payload.uuid, - persona.identifier.publicKeyAsHex, - action, - platform, - username, - payload.createdAt, - { - signature, - }, - ) - Services.Identity.detachProfile(profile) - MaskMessages.events.ownProofChanged.sendToAll(undefined) - }, - ) -} diff --git a/packages/mask/dashboard/hooks/usePersonaContext.ts b/packages/mask/dashboard/hooks/usePersonaContext.ts deleted file mode 100644 index 99f08857d67e..000000000000 --- a/packages/mask/dashboard/hooks/usePersonaContext.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useCallback, useState } from 'react' -import { createContainer } from 'unstated-next' -import { useConnectSite, useDisconnectSite, useOpenProfilePage } from './useConnectSite.js' -import Services from '#services' -import { - useCurrentPersonaIdentifier, - useSupportedSocialNetworkSites, - useOwnedPersonas, - type SiteAdaptor, -} from '../../shared-ui/index.js' -import { useCreatePersona } from './useCreatePersona.js' -import { useDeleteBound } from './useOperateBindingProof.js' - -function usePersonaContext() { - const currentPersonaIdentifier = useCurrentPersonaIdentifier() - const definedSocialNetworkAdaptors: SiteAdaptor[] = useSupportedSocialNetworkSites() - const personas = useOwnedPersonas() - const currentPersona = personas.find((x) => x.identifier === currentPersonaIdentifier) - const [open, setOpen] = useState(false) - - const [, connectPersona] = useConnectSite() - const [, openProfilePage] = useOpenProfilePage() - const [, disconnectPersona] = useDisconnectSite() - const [, createPersona] = useCreatePersona() - const [, deleteBound] = useDeleteBound() - const renamePersona = Services.Identity.renamePersona - const changeCurrentPersona = useCallback(Services.Settings.setCurrentPersonaIdentifier, []) - - return { - connectPersona, - disconnectPersona, - createPersona, - renamePersona, - changeCurrentPersona, - deleteBound, - currentPersona, - definedSocialNetworkAdaptors, - personas, - openProfilePage, - drawerOpen: open, - toggleDrawer: () => setOpen((e) => !e), - } -} - -export const PersonaContext = createContainer(usePersonaContext) -PersonaContext.Provider.displayName = 'PersonaProvider' diff --git a/packages/mask/dashboard/initialization/Dashboard.tsx b/packages/mask/dashboard/initialization/Dashboard.tsx index 9e16ac70c1b1..f88bccf0c85e 100644 --- a/packages/mask/dashboard/initialization/Dashboard.tsx +++ b/packages/mask/dashboard/initialization/Dashboard.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react' -import { CssBaseline, ThemeProvider, StyledEngineProvider } from '@mui/material' +import { CssBaseline, ThemeProvider, StyledEngineProvider, GlobalStyles } from '@mui/material' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { CustomSnackbarProvider, @@ -22,8 +22,24 @@ import { Pages } from '../pages/routes.js' import { UserContext, useAppearance } from '../../shared-ui/index.js' import Services from '#services' +const GlobalCss = ( + +) + const PluginRender = createInjectHooksRenderer(useActivatedPluginsDashboard, (x) => x.GlobalInjection) +const PersonaContextIO = { + queryOwnedPersonaInformation: Services.Identity.queryOwnedPersonaInformation, + queryPersonaAvatarLastUpdateTime: Services.Identity.getPersonaAvatarLastUpdateTime, +} export default function DashboardRoot(props: React.PropsWithChildren<{}>) { useEffect(() => queryRemoteI18NBundle(Services.Helper.queryRemoteI18NBundle), []) @@ -50,11 +66,12 @@ export default function DashboardRoot(props: React.PropsWithChildren<{}>) { - + + {GlobalCss} {props.children} diff --git a/packages/mask/dashboard/locales/en-US.json b/packages/mask/dashboard/locales/en-US.json index a1d0b15b9616..769e185369ce 100644 --- a/packages/mask/dashboard/locales/en-US.json +++ b/packages/mask/dashboard/locales/en-US.json @@ -103,7 +103,7 @@ "sign_in_account_cloud_backup_decrypt_failed": "Decrypt failed, please check password", "incorrect_backup_password": "Incorrect Backup Password.", "incorrect_identity_mnemonic": "Incorrect recovery phrase.", - "sign_in_account_cloud_backup_email_format_error": "The email is incorrect.", + "sign_in_account_cloud_backup_email_format_error": " Invalid email address format.", "sign_in_account_cloud_backup_phone_format_error": "The phone number is incorrect.", "sign_in_account_cloud_backup_synchronize_password_tip": "You have successfully verified your cloud password and recovered your backup. To unify backup passwords, do you want to synchronize your cloud password as local backup password?", "cloud_backup": "Cloud Backup", @@ -443,7 +443,7 @@ "persona_create_tips": "Create your persona to get started", "persona_setup_persona_example": "Example: Alice", "data_recovery_title": "Recover your data", - "data_recovery_description": "12-word recovery phrase is used to recover your persona data.", + "data_recovery_description": "Please select the appropriate method to restore your personal data.", "data_recovery_email": "Email", "data_recovery_email_code": "Email verification code", "data_recovery_mobile": "Mobile", @@ -455,14 +455,14 @@ "data_backup_title": "Select the contents of the backup", "data_backup_description": "Please select the appropriate method to restore your personal data.", "cloud_backup_title": "login to your Mask Cloud", - "cloud_backup_email_exists": "The email address used for the last backup was {{account}}.", - "cloud_backup_mobile_exists": "The mobile number used for last backup is {{account}}.", + "cloud_backup_backup_exists": "You used {{account}} for the last cloud backup.", "cloud_backup_no_exist_tips": "Please use your frequently used email account or mobile phone for backup.", "cloud_backup_email_title": "E-mail", "cloud_backup_phone_title": "Mobile", - "cloud_backup_incorrect_email_address": "The email address is incorrect.", + "cloud_backup_incorrect_email_address": "Invalid email address format.", "cloud_backup_incorrect_verified_code": "The code is incorrect.", "cloud_backup_email_verification_code": "Email verification code", + "cloud_backup_phone_verification_code": "Phone verification code", "cloud_backup_preview_title": "Welcome to Mask Cloud Services", "cloud_backup_preview_description": "Please select the appropriate method to restore your personal data.", "cloud_backup_preview_switch_other_account": "Switch other account", diff --git a/packages/mask/dashboard/modals/BackupPreviewModal/BackupPreviewDialog.tsx b/packages/mask/dashboard/modals/BackupPreviewModal/BackupPreviewDialog.tsx index 554c6d9b1bc2..40055e55f2f3 100644 --- a/packages/mask/dashboard/modals/BackupPreviewModal/BackupPreviewDialog.tsx +++ b/packages/mask/dashboard/modals/BackupPreviewModal/BackupPreviewDialog.tsx @@ -5,7 +5,7 @@ import { Box, DialogActions, DialogContent, Typography } from '@mui/material' import { useBackupFormState, type BackupFormInputs } from '../../hooks/useBackupFormState.js' import { ActionButton, makeStyles, useCustomSnackbar } from '@masknet/theme' import { Icons } from '@masknet/icons' -import { useAsyncFn } from 'react-use' +import { useAsyncFn, useUpdateEffect } from 'react-use' import Services from '#services' import type { AccountType } from '../../type.js' import { fetchUploadLink, uploadBackupValue } from '../../utils/api.js' @@ -14,10 +14,11 @@ import { encode } from '@msgpack/msgpack' import { Controller } from 'react-hook-form' import { PersonasBackupPreview, WalletsBackupPreview } from '../../components/BackupPreview/index.js' import PasswordField from '../../components/PasswordField/index.js' -import { useNavigate } from 'react-router-dom' +import { useNavigate, useSearchParams } from 'react-router-dom' import { DashboardRoutes } from '@masknet/shared-base' import formatDateTime from 'date-fns/format' import { UserContext } from '../../../shared-ui/index.js' +import millisecondsToSeconds from 'date-fns/millisecondsToSeconds' const useStyles = makeStyles()((theme) => ({ container: { @@ -75,6 +76,7 @@ export const BackupPreviewDialog = memo(function Backu }) { const controllerRef = useRef(null) const { classes, theme } = useStyles() + const [params, setParams] = useSearchParams() const navigate = useNavigate() const t = useDashboardI18N() const { updateUser } = UserContext.useContainer() @@ -82,19 +84,20 @@ export const BackupPreviewDialog = memo(function Backu hasPassword, previewInfo, loading, - backupPersonas, backupWallets, - setBackupPersonas, setBackupWallets, formState: { clearErrors, setError, control, handleSubmit, + reset, + resetField, formState: { errors, isDirty, isValid }, }, } = useBackupFormState() const { showSnackbar } = useCustomSnackbar() + const [{ loading: uploadLoading, value }, handleUploadBackup] = useAsyncFn( async (data: BackupFormInputs) => { try { @@ -106,15 +109,17 @@ export const BackupPreviewDialog = memo(function Backu } } - const { file, personaNickNames } = await Services.Backup.createBackupFile({ - excludeBase: backupPersonas, - excludeWallet: backupWallets, + const { file } = await Services.Backup.createBackupFile({ + excludeBase: false, + excludeWallet: !backupWallets, }) + + const name = `mask-network-keystore-backup-${formatDateTime(new Date(), 'yyyy-MM-dd')}` const uploadUrl = await fetchUploadLink({ code, account, type, - abstract: abstract ?? personaNickNames.join(','), + abstract: name, }) const encrypted = await encryptBackup(encode(account + data.backupPassword), encode(file)) const controller = new AbortController() @@ -124,7 +129,14 @@ export const BackupPreviewDialog = memo(function Backu if (response.ok) { const now = formatDateTime(new Date(), 'yyyy-MM-dd HH:mm') showSnackbar(t.settings_alert_backup_success(), { variant: 'success' }) - updateUser({ cloudBackupAt: now }) + updateUser({ cloudBackupAt: now, cloudBackupMethod: type }) + if (!params.get('downloadURL')) navigate(DashboardRoutes.CloudBackup, { replace: true }) + setParams((params) => { + params.set('size', encrypted.byteLength.toString()) + params.set('abstract', name) + params.set('uploadedAt', millisecondsToSeconds(Date.now()).toString()) + return params.toString() + }) } return true } catch (error) { @@ -134,7 +146,7 @@ export const BackupPreviewDialog = memo(function Backu return false } }, - [code, hasPassword, backupWallets, abstract, backupPersonas, code, account, type, t, navigate, updateUser], + [code, hasPassword, backupWallets, abstract, code, account, type, t, navigate, updateUser, params], ) const handleClose = useCallback(() => { @@ -143,6 +155,10 @@ export const BackupPreviewDialog = memo(function Backu onClose() }, [uploadLoading, onClose]) + useUpdateEffect(() => { + resetField('paymentPassword') + }, [backupWallets, resetField]) + const content = useMemo(() => { if (value) return ( @@ -172,29 +188,22 @@ export const BackupPreviewDialog = memo(function Backu ) return !loading && previewInfo ? ( - + - {backupPersonas ? ( - ( - clearErrors()} - sx={{ mb: 2 }} - placeholder={t.settings_label_backup_password()} - error={!!errors.backupPassword?.message} - helperText={errors.backupPassword?.message} - /> - )} - name="backupPassword" - /> - ) : null} + ( + clearErrors('backupPassword')} + sx={{ mb: 2 }} + placeholder={t.settings_label_backup_password()} + error={!!errors.backupPassword?.message} + helperText={errors.backupPassword?.message} + /> + )} + name="backupPassword" + /> (function Backu onChange={setBackupWallets} /> - {backupWallets ? ( + {backupWallets && hasPassword ? ( ( clearErrors()} + onFocus={() => clearErrors('paymentPassword')} sx={{ mb: 2 }} placeholder={t.sign_in_account_local_backup_payment_password()} error={!!errors.paymentPassword?.message} @@ -232,10 +241,8 @@ export const BackupPreviewDialog = memo(function Backu loading, previewInfo, control, - backupPersonas, - setBackupPersonas, t, - errors, + JSON.stringify(errors), backupWallets, setBackupWallets, isOverwrite, @@ -269,6 +276,7 @@ export const BackupPreviewDialog = memo(function Backu ) }, [ + backupWallets, isOverwrite, isDirty, isValid, @@ -285,7 +293,7 @@ export const BackupPreviewDialog = memo(function Backu return ( - {content} + {content} {action} ) diff --git a/packages/mask/dashboard/modals/MergeBackupModal/MergeBackupDialog.tsx b/packages/mask/dashboard/modals/MergeBackupModal/MergeBackupDialog.tsx index ba99870a8032..a59dcf01d0b3 100644 --- a/packages/mask/dashboard/modals/MergeBackupModal/MergeBackupDialog.tsx +++ b/packages/mask/dashboard/modals/MergeBackupModal/MergeBackupDialog.tsx @@ -12,7 +12,6 @@ import { formatFileSize } from '@masknet/kit' import formatDateTime from 'date-fns/format' import fromUnixTime from 'date-fns/fromUnixTime' import PasswordField from '../../components/PasswordField/index.js' -import { setPassword } from '../../../background/services/wallet/services/index.js' import { passwordRegexp } from '../../utils/regexp.js' import { decryptBackup } from '@masknet/backup-format' import { decode, encode } from '@msgpack/msgpack' @@ -89,10 +88,11 @@ export const MergeBackupDialog = memo(function MergeBack onClose() }, [onClose]) - const { value: encrypted } = useAsync(async () => { - if (!downloadLink) return + const { value: encrypted, error } = useAsync(async () => { + if (!downloadLink || !open) return + + const response = await fetch(downloadLink, { method: 'GET', cache: 'no-store' }) - const response = await fetch(downloadLink, { method: 'GET' }) if (!response.ok || response.status !== 200) { showSnackbar(t.cloud_backup_download_link_expired(), { variant: 'error' }) handleClose() @@ -104,7 +104,7 @@ export const MergeBackupDialog = memo(function MergeBack if (!contentLength || !reader) return let received = 0 - + const chunks: number[] = [] // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await reader.read() @@ -113,13 +113,13 @@ export const MergeBackupDialog = memo(function MergeBack setProcess(100) break } - + chunks.push(...value) received += value.length + setProcess((received / Number(contentLength)) * 100) } - - return response.arrayBuffer() - }, [downloadLink, handleClose]) + return Uint8Array.from(chunks).buffer + }, [downloadLink, handleClose, open]) const fileName = useMemo(() => { try { @@ -167,12 +167,12 @@ export const MergeBackupDialog = memo(function MergeBack type, account, }) - onClose() - }, [code, abstract, type, account]) + handleClose() + }, [code, abstract, type, account, handleClose]) if (showCongratulation) return ( - + 🎉 @@ -236,7 +236,7 @@ export const MergeBackupDialog = memo(function MergeBack value={backupPassword} placeholder={t.settings_label_backup_password()} onChange={(e) => { - setPassword(e.target.value) + setBackupPassword(e.target.value) setBackupPasswordError('') }} onBlur={(e) => { diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/index.tsx b/packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/index.tsx index d97e134eedb5..ae0bb267aef1 100644 --- a/packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/index.tsx +++ b/packages/mask/dashboard/pages/CreateMaskWallet/CreateMnemonic/index.tsx @@ -1,26 +1,29 @@ -import { toBlob } from 'html-to-image' -import { memo, useCallback, useMemo, useRef, useState } from 'react' -import { useAsync, useAsyncFn } from 'react-use' -import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' -import { Alert, alpha, Box, Button, Stack, Typography, useTheme } from '@mui/material' -import { makeStyles } from '@masknet/theme' import { Icons } from '@masknet/icons' +import { defer, timeout } from '@masknet/kit' import { CopyButton } from '@masknet/shared' import { DashboardRoutes } from '@masknet/shared-base' -import { generateNewWalletName } from '@masknet/web3-shared-base' +import { makeStyles } from '@masknet/theme' +import { useWallets } from '@masknet/web3-hooks-base' +import { Providers, Web3 } from '@masknet/web3-providers' +import { generateNewWalletName, isSameAddress } from '@masknet/web3-shared-base' +import { ProviderType } from '@masknet/web3-shared-evm' +import { Telemetry } from '@masknet/web3-telemetry' +import { EventID, EventType } from '@masknet/web3-telemetry/types' +import { Alert, Box, Button, Stack, Typography, alpha, useTheme } from '@mui/material' +import { toBlob } from 'html-to-image' +import { memo, useCallback, useMemo, useRef, useState } from 'react' +import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' +import { useAsync, useAsyncFn } from 'react-use' +import urlcat from 'urlcat' +import Services from '#services' import { MnemonicReveal } from '../../../components/Mnemonic/index.js' import { PrimaryButton } from '../../../components/PrimaryButton/index.js' import { SecondaryButton } from '../../../components/SecondaryButton/index.js' -import { ResetWalletContext } from '../context.js' +import { SetupFrameController } from '../../../components/SetupFrame/index.js' import { useMnemonicWordsPuzzle, type PuzzleWord } from '../../../hooks/useMnemonicWordsPuzzle.js' import { useDashboardI18N } from '../../../locales/index.js' +import { ResetWalletContext } from '../context.js' import { ComponentToPrint } from './ComponentToPrint.js' -import { SetupFrameController } from '../../../components/SetupFrame/index.js' -import { useWallets } from '@masknet/web3-hooks-base' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventType, EventID } from '@masknet/web3-telemetry/types' -import Services from '#services' -import urlcat from 'urlcat' const useStyles = makeStyles()((theme) => ({ title: { @@ -131,6 +134,7 @@ const useStyles = makeStyles()((theme) => ({ padding: '9px 8px', alignItems: 'center', marginRight: 24, + cursor: 'pointer', }, puzzleWordText: { fontSize: 14, @@ -162,6 +166,16 @@ const useStyles = makeStyles()((theme) => ({ }, })) +async function pollResult(address: string) { + const subscription = Providers[ProviderType.MaskWallet].subscription.wallets + if (subscription.getCurrentValue().find((x) => isSameAddress(x.address, address))) return + const [promise, resolve] = defer() + const unsubscribe = subscription.subscribe(() => { + if (subscription.getCurrentValue().find((x) => isSameAddress(x.address, address))) resolve(true) + }) + return timeout(promise, 10_000, 'It takes too long to create a wallet. You might try again.').finally(unsubscribe) +} + const CreateMnemonic = memo(function CreateMnemonic() { const location = useLocation() const navigate = useNavigate() @@ -204,6 +218,12 @@ const CreateMnemonic = memo(function CreateMnemonic() { const result = await handlePasswordAndWallets(location.state?.password, location.state?.isReset) if (!result) return const address = await Services.Wallet.createWalletFromMnemonicWords(walletName, words.join(' ')) + await pollResult(address) + await Web3.connect({ + silent: true, + providerType: ProviderType.MaskWallet, + account: address, + }) await Services.Wallet.resolveMaskAccount([{ address }]) Telemetry.captureEvent(EventType.Access, EventID.EntryPopupWalletCreate) navigate(urlcat(DashboardRoutes.SignUpMaskWalletOnboarding, { external_request }), { replace: true }) diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/CreateWalletForm/index.tsx b/packages/mask/dashboard/pages/CreateMaskWallet/CreateWalletForm/index.tsx index fd53df3079ee..afd1815de34c 100644 --- a/packages/mask/dashboard/pages/CreateMaskWallet/CreateWalletForm/index.tsx +++ b/packages/mask/dashboard/pages/CreateMaskWallet/CreateWalletForm/index.tsx @@ -1,6 +1,6 @@ import { memo, useMemo } from 'react' -import { Box, formHelperTextClasses, Typography } from '@mui/material' -import { makeStyles, MaskColorVar } from '@masknet/theme' +import { Box, Typography } from '@mui/material' +import { makeStyles } from '@masknet/theme' import { z as zod } from 'zod' import { useForm, Controller } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' @@ -35,16 +35,6 @@ const useStyles = makeStyles()((theme) => ({ width: '100%', marginTop: 10, }, - textField: { - padding: theme.spacing(1), - fontSize: 12, - lineHeight: '16px', - borderRadius: 6, - [`&.${formHelperTextClasses.error}`]: { - boxShadow: `0 0 0 ${theme.spacing(0.5)} ${MaskColorVar.redMain.alpha(0.2)}`, - border: `1px solid ${MaskColorVar.redMain.alpha(0.8)}`, - }, - }, tips: { fontSize: 14, lineHeight: '18px', @@ -140,7 +130,6 @@ const CreateWalletForm = memo(function CreateWalletForm() { placeholder={t.create_wallet_payment_password_place_holder()} error={!isValid && !!errors.password?.message} helperText={!isValid ? errors.password?.message : ''} - InputProps={{ className: classes.textField }} /> )} name="password" @@ -153,7 +142,6 @@ const CreateWalletForm = memo(function CreateWalletForm() { error={!isValid && !!errors.confirm?.message} helperText={!isValid ? errors.confirm?.message : ''} placeholder={t.create_wallet_re_enter_payment_password()} - InputProps={{ className: classes.textField }} /> )} name="confirm" diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/Recovery/index.tsx b/packages/mask/dashboard/pages/CreateMaskWallet/Recovery/index.tsx index f37e825bb3f9..14832fee9ce6 100644 --- a/packages/mask/dashboard/pages/CreateMaskWallet/Recovery/index.tsx +++ b/packages/mask/dashboard/pages/CreateMaskWallet/Recovery/index.tsx @@ -235,7 +235,7 @@ const Recovery = memo(function Recovery() { - +
diff --git a/packages/mask/dashboard/pages/CreateMaskWallet/context.ts b/packages/mask/dashboard/pages/CreateMaskWallet/context.ts index 5a8c02948876..d3dd19cf6367 100644 --- a/packages/mask/dashboard/pages/CreateMaskWallet/context.ts +++ b/packages/mask/dashboard/pages/CreateMaskWallet/context.ts @@ -32,7 +32,7 @@ function useContext() { await resetWallets(password, isReset) if (isLocked && password) await Services.Wallet.unlockWallet(password) } else if (hasPassword && isLocked) { - await Services.Helper.openPopupWindow(PopupRoutes.Unlock, {}) + await Services.Helper.openPopupWindow(PopupRoutes.Wallet, {}) return false } else if (password && !hasPassword) { await Services.Wallet.changePassword(getDefaultWalletPassword(), password) diff --git a/packages/mask/dashboard/pages/SetupPersona/CloudBackup/EmailForm.tsx b/packages/mask/dashboard/pages/SetupPersona/CloudBackup/EmailForm.tsx index 6d1c3d76cefc..eb578de563dd 100644 --- a/packages/mask/dashboard/pages/SetupPersona/CloudBackup/EmailForm.tsx +++ b/packages/mask/dashboard/pages/SetupPersona/CloudBackup/EmailForm.tsx @@ -26,6 +26,7 @@ export const EmailForm = memo(function EmailForm() { clearErrors, control, watch, + trigger, formState: { errors }, }, } = CloudBackupFormContext.useContainer() @@ -39,7 +40,10 @@ export const EmailForm = memo(function EmailForm() { scenario: user.email ? Scenario.change : Scenario.create, locale: language.includes('zh') ? Locale.zh : Locale.en, }).catch((error) => { - showSnackbar(error.message, { variant: 'error' }) + showSnackbar( + error.message.includes('SendTemplatedEmail') ? t.cloud_backup_incorrect_email_address() : error.message, + { variant: 'error' }, + ) }) if (response) { @@ -56,6 +60,7 @@ export const EmailForm = memo(function EmailForm() { clearErrors('email')} + onBlur={() => trigger('email')} fullWidth placeholder={t.email()} type="email" @@ -84,9 +89,9 @@ export const EmailForm = memo(function EmailForm() { className={classes.send} disableFocusRipple disableRipple - disabled={!email && !!errors.email?.message} + disabled={!email || !!errors.email?.message} variant="text" - sx={{ width: 120 }} + sx={{ px: 0 }} onClick={handleSendVerificationCode} repeatContent={t.resend()}> {t.send()} diff --git a/packages/mask/dashboard/pages/SetupPersona/CloudBackup/PhoneForm.tsx b/packages/mask/dashboard/pages/SetupPersona/CloudBackup/PhoneForm.tsx index f76dac18147d..d7db47e36f98 100644 --- a/packages/mask/dashboard/pages/SetupPersona/CloudBackup/PhoneForm.tsx +++ b/packages/mask/dashboard/pages/SetupPersona/CloudBackup/PhoneForm.tsx @@ -1,16 +1,14 @@ -import { memo, useCallback, useMemo, useState } from 'react' +import { memo, useCallback } from 'react' import { useDashboardI18N } from '../../../locales/i18n_generated.js' import { UserContext, useLanguage } from '../../../../shared-ui/index.js' import { CloudBackupFormContext } from '../../../contexts/CloudBackupFormContext.js' -import { Box, TextField, Typography } from '@mui/material' +import { Box, TextField } from '@mui/material' import { Controller } from 'react-hook-form' -import { Icons } from '@masknet/icons' -import { CountryCodePicker } from '../../../components/CountryCodePicker/index.js' -import REGIONS from '../../../assets/region.json' + import { CountdownButton, makeStyles, useCustomSnackbar } from '@masknet/theme' import { AccountType, Scenario, Locale } from '../../../type.js' import { sendCode } from '../../../utils/api.js' -import { COUNTRY_ICON_URL } from '../../../constants.js' +import { PhoneNumberField } from '@masknet/shared' const useStyles = makeStyles()((theme) => ({ send: { @@ -26,9 +24,6 @@ export const PhoneForm = memo(function PhoneForm() { const lang = useLanguage() const { showSnackbar } = useCustomSnackbar() - const [open, setOpen] = useState(false) - const [anchorEl, setAnchorEl] = useState(null) - const { formState: { control, @@ -41,16 +36,9 @@ export const PhoneForm = memo(function PhoneForm() { const [countryCode, phone] = watch(['countryCode', 'phone']) - const countryIcon = useMemo(() => { - if (!countryCode) return - const target = REGIONS.find((x) => x.dial_code === countryCode) - if (!target) return - return `${COUNTRY_ICON_URL}${target.code.toLowerCase()}.svg` - }, [countryCode]) - const handleSendVerificationCode = useCallback(async () => { const response = await sendCode({ - account: phone, + account: `+${countryCode}${phone}`, type: AccountType.Phone, scenario: user.phone ? Scenario.change : Scenario.create, locale: lang.includes('zh') ? Locale.zh : Locale.en, @@ -61,7 +49,7 @@ export const PhoneForm = memo(function PhoneForm() { if (response) { showSnackbar(t.settings_alert_validation_code_sent(), { variant: 'success' }) } - }, [phone, user, lang]) + }, [phone, user, lang, countryCode]) return ( @@ -69,36 +57,15 @@ export const PhoneForm = memo(function PhoneForm() { control={control} name="phone" render={({ field }) => ( - setValue('countryCode', code)} onFocus={() => clearErrors('phone')} fullWidth placeholder={t.mobile_number()} - type="tel" error={!!errors.phone?.message} helperText={errors.phone?.message} - InputProps={{ - disableUnderline: true, - startAdornment: ( - { - setAnchorEl(event.currentTarget) - setOpen(true) - }}> - - - {countryCode} - - - - ), - }} /> )} /> @@ -110,7 +77,7 @@ export const PhoneForm = memo(function PhoneForm() { {...field} onFocus={() => clearErrors('code')} fullWidth - placeholder={t.cloud_backup_email_verification_code()} + placeholder={t.cloud_backup_phone_verification_code()} error={!!errors.code?.message} helperText={errors.code?.message} InputProps={{ @@ -122,9 +89,9 @@ export const PhoneForm = memo(function PhoneForm() { className={classes.send} disableFocusRipple disableRipple - disabled={!phone && !!errors.phone?.message} + disabled={!phone || !!errors.phone?.message} variant="text" - sx={{ width: 120 }} + sx={{ px: 0 }} onClick={handleSendVerificationCode} repeatContent={t.resend()}> {t.send()} @@ -134,15 +101,6 @@ export const PhoneForm = memo(function PhoneForm() { /> )} /> - { - if (code) setValue('countryCode', code) - setOpen(false) - }} - /> ) }) diff --git a/packages/mask/dashboard/pages/SetupPersona/CloudBackup/index.tsx b/packages/mask/dashboard/pages/SetupPersona/CloudBackup/index.tsx index 7c14fe30b6fa..fb98d2ca6a03 100644 --- a/packages/mask/dashboard/pages/SetupPersona/CloudBackup/index.tsx +++ b/packages/mask/dashboard/pages/SetupPersona/CloudBackup/index.tsx @@ -66,13 +66,11 @@ const CloudBackupInner = memo(function CloudBackupInner() { const { currentTab, onChange, tabs, formState } = CloudBackupFormContext.useContainer() - console.log(formState) - const [{ loading }, handleSubmit] = useAsyncFn( async (data: CloudBackupFormInputs) => { const response = await fetchDownloadLink({ - account: currentTab === tabs.email ? data.email : data.countryCode + data.phone, - type: AccountType.Email, + account: currentTab === tabs.email ? data.email : `+${data.countryCode} ${data.phone}`, + type: currentTab === tabs.email ? AccountType.Email : AccountType.Phone, code: data.code, }).catch((error) => { if (error.status === 400) { @@ -82,7 +80,13 @@ const CloudBackupInner = memo(function CloudBackupInner() { }) } else if (error.status === 404) { // No cloud backup file - navigate(DashboardRoutes.CloudBackupPreview) + navigate( + urlcat(DashboardRoutes.CloudBackupPreview, { + type: currentTab === tabs.email ? AccountType.Email : AccountType.Phone, + account: currentTab === tabs.email ? data.email : `+${data.countryCode} ${data.phone}`, + code: data.code, + }), + ) } }) @@ -96,36 +100,32 @@ const CloudBackupInner = memo(function CloudBackupInner() { urlcat(DashboardRoutes.CloudBackupPreview, { ...response, type: currentTab === tabs.email ? AccountType.Email : AccountType.Phone, - account: currentTab === tabs.email ? data.email : data.countryCode + data.phone, + account: currentTab === tabs.email ? data.email : `+${data.countryCode} ${data.phone}`, + code: data.code, }), - { - state: { - code: data.code, - }, - }, ) }, [currentTab, tabs, formState, navigate, updateUser, user], ) const description = useMemo(() => { - if (currentTab === tabs.email && user.email) + if (user.cloudBackupMethod === AccountType.Email && user.email) return ( - }} values={{ account: user.email }} /> ) - else if (currentTab === tabs.mobile && user.phone) + if (user.cloudBackupMethod === AccountType.Phone && user.phone) return ( - }} values={{ account: user.phone }} /> ) return t.cloud_backup_no_exist_tips() - }, [t, currentTab, tabs, user]) + }, [user, DashboardTrans, t]) return ( <> diff --git a/packages/mask/dashboard/pages/SetupPersona/CloudBackupPreview/index.tsx b/packages/mask/dashboard/pages/SetupPersona/CloudBackupPreview/index.tsx index 6dfbc6f81565..1f2a4a7fc331 100644 --- a/packages/mask/dashboard/pages/SetupPersona/CloudBackupPreview/index.tsx +++ b/packages/mask/dashboard/pages/SetupPersona/CloudBackupPreview/index.tsx @@ -1,8 +1,8 @@ -import { Box, Tooltip, Typography } from '@mui/material' -import { memo, useCallback, useEffect, useMemo } from 'react' +import { Box, Typography } from '@mui/material' +import { memo, useCallback, useMemo } from 'react' import { useDashboardI18N } from '../../../locales/i18n_generated.js' -import { ActionButton, makeStyles } from '@masknet/theme' -import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' +import { ActionButton, TextOverflowTooltip, makeStyles } from '@masknet/theme' +import { useNavigate, useSearchParams } from 'react-router-dom' import { DashboardRoutes } from '@masknet/shared-base' import { Icons } from '@masknet/icons' import { formatFileSize } from '@masknet/kit' @@ -14,18 +14,6 @@ import type { AccountType } from '../../../type.js' import { EmptyStatus } from '@masknet/shared' import { SetupFrameController } from '../../../components/SetupFrame/index.js' -function economizeAbstract(input: string) { - if (!input.length) return
error
- if (input.length < 30) return
{input}
- return ( - -
- {input.slice(0, 30)}...({input.split(',').length}) -
-
- ) -} - const useStyles = makeStyles()((theme) => ({ title: { fontSize: 36, @@ -65,11 +53,9 @@ const useStyles = makeStyles()((theme) => ({ export const CloudBackupPreview = memo(function CloudBackupPreview() { const t = useDashboardI18N() + const { classes, theme, cx } = useStyles() const [params] = useSearchParams() - const location = useLocation() - - const code = location.state?.code as string const navigate = useNavigate() @@ -81,8 +67,9 @@ export const CloudBackupPreview = memo(function CloudBackupPreview() { uploadedAt: params.get('uploadedAt'), size: params.get('size'), type: params.get('type'), + code: params.get('code'), } - }, []) + }, [params]) const [{ loading: mergeLoading }, handleMergeClick] = useAsyncFn(async () => { if ( @@ -90,7 +77,8 @@ export const CloudBackupPreview = memo(function CloudBackupPreview() { !previewInfo.account || !previewInfo.size || !previewInfo.uploadedAt || - !previewInfo.type + !previewInfo.type || + !previewInfo.code ) return await MergeBackupModal.openAndWaitForClose({ @@ -98,17 +86,17 @@ export const CloudBackupPreview = memo(function CloudBackupPreview() { account: previewInfo.account, size: previewInfo.size, uploadedAt: previewInfo.uploadedAt, - code, + code: previewInfo.code, abstract: previewInfo.abstract ? previewInfo.abstract : undefined, type: previewInfo.type as AccountType, }) - }, [t, previewInfo, code]) + }, [t, previewInfo]) const handleBackupClick = useCallback(() => { - if (!previewInfo.type || !previewInfo.account) return + if (!previewInfo.type || !previewInfo.account || !previewInfo.code) return BackupPreviewModal.open({ isOverwrite: false, - code, + code: previewInfo.code, abstract: previewInfo.abstract ? previewInfo.abstract : undefined, type: previewInfo.type as AccountType, account: previewInfo.account, @@ -126,11 +114,11 @@ export const CloudBackupPreview = memo(function CloudBackupPreview() { }, onConfirm: () => { ConfirmDialog.close(false) - if (!previewInfo.type || !previewInfo.account) return + if (!previewInfo.type || !previewInfo.account || !previewInfo.code) return BackupPreviewModal.open({ isOverwrite: true, - code, + code: previewInfo.code, abstract: previewInfo.abstract ? previewInfo.abstract : undefined, type: previewInfo.type as AccountType, account: previewInfo.account, @@ -139,10 +127,6 @@ export const CloudBackupPreview = memo(function CloudBackupPreview() { }) }, [t, previewInfo]) - useEffect(() => { - if (!code) navigate(DashboardRoutes.CloudBackup, { replace: true }) - }, [code, navigate]) - return ( <> @@ -161,10 +145,17 @@ export const CloudBackupPreview = memo(function CloudBackupPreview() { - - - {economizeAbstract(previewInfo.abstract ?? '')} - + + + + {previewInfo.abstract} + + + {formatFileSize(Number(previewInfo.size), false)} @@ -181,22 +172,25 @@ export const CloudBackupPreview = memo(function CloudBackupPreview() { - } - color="primary" - className={classes.button} - loading={mergeLoading} - onClick={handleMergeClick}> - {t.cloud_backup_merge_local_data()} - - } - color="error" - className={cx(classes.button)}> - {t.cloud_backup_overwrite_backup()} - + + + } + color="primary" + className={classes.button} + loading={mergeLoading} + onClick={handleMergeClick}> + {t.cloud_backup_merge_local_data()} + + } + color="error" + className={cx(classes.button)}> + {t.cloud_backup_overwrite_backup()} + + ) : ( @@ -216,7 +210,7 @@ export const CloudBackupPreview = memo(function CloudBackupPreview() { {!previewInfo.downloadLink ? ( - }> + }> {t.backup()} diff --git a/packages/mask/dashboard/pages/SetupPersona/LocalBackup/index.tsx b/packages/mask/dashboard/pages/SetupPersona/LocalBackup/index.tsx index 6fb60a046474..0715969ec104 100644 --- a/packages/mask/dashboard/pages/SetupPersona/LocalBackup/index.tsx +++ b/packages/mask/dashboard/pages/SetupPersona/LocalBackup/index.tsx @@ -28,6 +28,7 @@ const useStyles = makeStyles()((theme) => ({ color: theme.palette.maskColor.second, fontSize: 14, marginTop: theme.spacing(1.5), + marginBottom: theme.spacing(3), }, })) @@ -39,9 +40,7 @@ export const LocalBackup = memo(function LocalBackup() { hasPassword, previewInfo, loading, - backupPersonas, backupWallets, - setBackupPersonas, setBackupWallets, formState: { setError, @@ -62,8 +61,8 @@ export const LocalBackup = memo(function LocalBackup() { } } const { file } = await Services.Backup.createBackupFile({ - excludeBase: backupPersonas, - excludeWallet: backupWallets, + excludeBase: false, + excludeWallet: !backupWallets, }) const encrypted = await encryptBackup(encode(data.backupPassword), encode(file)) @@ -81,7 +80,7 @@ export const LocalBackup = memo(function LocalBackup() { window.close() }, - [backupPersonas, backupWallets, hasPassword, setError, updateUser, user], + [backupWallets, hasPassword, setError, updateUser, user], ) return ( @@ -93,29 +92,22 @@ export const LocalBackup = memo(function LocalBackup() { {t.data_backup_description()} {!loading && previewInfo ? ( - + - {backupPersonas ? ( - ( - clearErrors()} - sx={{ mb: 2 }} - placeholder={t.settings_label_backup_password()} - error={!!errors.backupPassword?.message} - helperText={errors.backupPassword?.message} - /> - )} - name="backupPassword" - /> - ) : null} + ( + clearErrors()} + sx={{ mb: 2 }} + placeholder={t.settings_label_backup_password()} + error={!!errors.backupPassword?.message} + helperText={errors.backupPassword?.message} + /> + )} + name="backupPassword" + /> { try { const identifier = await createPersona(words.join(' '), state.personaName) @@ -125,7 +126,7 @@ export const SignUpMnemonic = memo(function SignUpMnemonic() { } catch (error) { showSnackbar((error as Error).message, { variant: 'error' }) } - }, [words]) + }, [words, changeCurrentPersona]) const handleRecovery = useCallback(() => { navigate(DashboardRoutes.RecoveryPersona) diff --git a/packages/mask/dashboard/pages/SetupPersona/Recovery/index.tsx b/packages/mask/dashboard/pages/SetupPersona/Recovery/index.tsx index 620191f1bdd5..7822ef21fc76 100644 --- a/packages/mask/dashboard/pages/SetupPersona/Recovery/index.tsx +++ b/packages/mask/dashboard/pages/SetupPersona/Recovery/index.tsx @@ -14,11 +14,11 @@ import { RestoreFromCloud } from '../../../components/Restore/RestoreFromCloud/i import { RecoveryProvider, RecoveryContext } from '../../../contexts/index.js' import { RestoreFromMnemonic } from '../../../components/Restore/RestoreFromMnemonic.js' import Services from '#services' -import { PersonaContext } from '../../../hooks/usePersonaContext.js' import { delay } from '@masknet/kit' import urlcat from 'urlcat' import { SignUpRoutePath } from '../../SignUp/routePath.js' +import { PersonaContext } from '@masknet/shared' const useStyles = makeStyles()((theme) => ({ header: { @@ -78,13 +78,15 @@ const useStyles = makeStyles()((theme) => ({ export const Recovery = memo(function Recovery() { const t = useDashboardI18N() const { classes } = useStyles() - const { currentPersona, changeCurrentPersona } = PersonaContext.useContainer() + const { currentPersona } = PersonaContext.useContainer() const tabPanelClasses = useMemo(() => ({ root: classes.panels }), [classes.panels]) const navigate = useNavigate() const [error, setError] = useState('') const [currentTab, onChange, tabs] = useTabs('mnemonic', 'privateKey', 'local', 'cloud') + const changeCurrentPersona = useCallback(Services.Settings.setCurrentPersonaIdentifier, []) + const handleRestoreFromMnemonic = useCallback( async (values: string[]) => { try { @@ -104,7 +106,7 @@ export const Recovery = memo(function Recovery() { setError(t.incorrect_identity_mnemonic()) } }, - [t, navigate], + [t, navigate, changeCurrentPersona], ) const handleRestoreFromPrivateKey = useCallback( diff --git a/packages/mask/dashboard/pages/SetupPersona/SignUp/index.tsx b/packages/mask/dashboard/pages/SetupPersona/SignUp/index.tsx index e850708e07d1..5a853b8db6ef 100644 --- a/packages/mask/dashboard/pages/SetupPersona/SignUp/index.tsx +++ b/packages/mask/dashboard/pages/SetupPersona/SignUp/index.tsx @@ -4,8 +4,8 @@ import { useNavigate } from 'react-router-dom' import Services from '#services' import { useDashboardI18N } from '../../../locales/i18n_generated.js' import { delay } from '@masknet/kit' -import { MaskTextField, makeStyles } from '@masknet/theme' -import { Typography, Button } from '@mui/material' +import { makeStyles } from '@masknet/theme' +import { Typography, Button, TextField } from '@mui/material' import { Box } from '@mui/system' import { PrimaryButton } from '../../../components/PrimaryButton/index.js' import { SecondaryButton } from '../../../components/SecondaryButton/index.js' @@ -97,7 +97,7 @@ export const SignUp = memo(function SignUp() { {t.persona_name()} - { if (error) setError('') setPersonaName(e.target.value) @@ -105,7 +105,7 @@ export const SignUp = memo(function SignUp() { autoFocus placeholder={t.persona_setup_persona_example()} required - InputProps={{ disableUnderline: true }} + InputProps={{ disableUnderline: true, size: 'large' }} inputProps={{ maxLength: 24 }} error={!!error} helperText={error} diff --git a/packages/mask/dashboard/pages/SignUp/steps/ConnectSocialMedia.tsx b/packages/mask/dashboard/pages/SignUp/steps/ConnectSocialMedia.tsx index 0311fd94dc4c..38b927ebe76f 100644 --- a/packages/mask/dashboard/pages/SignUp/steps/ConnectSocialMedia.tsx +++ b/packages/mask/dashboard/pages/SignUp/steps/ConnectSocialMedia.tsx @@ -2,23 +2,49 @@ import { useNavigate } from 'react-router-dom' import { upperFirst } from 'lodash-es' import { DashboardRoutes } from '@masknet/shared-base' import { Button, Stack } from '@mui/material' -import { SOCIAL_MEDIA_ICON_MAPPING } from '@masknet/shared' -import { - Body, - ColumnContentLayout, - Footer, - PersonaLogoBox, - SignUpAccountLogo, -} from '../../../components/RegisterFrame/ColumnContentLayout.js' +import { styled } from '@mui/material/styles' +import { PersonaContext, SOCIAL_MEDIA_ICON_MAPPING } from '@masknet/shared' +import { PersonaLogoBox, SignUpAccountLogo } from '../../../components/RegisterFrame/ColumnContentLayout.js' import { Header } from '../../../components/RegisterFrame/ColumnContentHeader.js' import { useDashboardI18N } from '../../../locales/index.js' -import { PersonaContext } from '../../../hooks/usePersonaContext.js' import { ActionCard } from '../../../components/ActionCard/index.js' +import { useConnectSite } from '../../../hooks/useConnectSite.js' +import { type SiteAdaptor, useSupportedSocialNetworkSites } from '../../../../shared-ui/index.js' + +const ColumnContentLayout = styled('div')` + display: flex; + flex-direction: column; + flex: 1; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +` + +const Body = styled('main')(({ theme }) => ({ + flex: '1 5', + width: '78%', + [theme.breakpoints.down('md')]: { + width: '95%', + }, +})) + +const Footer = styled('footer')(({ theme }) => ({ + flex: 1, + width: '78%', + [theme.breakpoints.down('md')]: { + width: '95%', + }, +})) export function ConnectSocialMedia() { const navigate = useNavigate() const t = useDashboardI18N() - const { currentPersona, connectPersona, definedSocialNetworkAdaptors } = PersonaContext.useContainer() + const { currentPersona } = PersonaContext.useContainer() + + const definedSocialNetworkAdaptors: SiteAdaptor[] = useSupportedSocialNetworkSites() + + const [, connectPersona] = useConnectSite() const handleConnect = async (networkIdentifier: string) => { if (currentPersona) { diff --git a/packages/mask/dashboard/pages/SignUp/steps/MnemonicRevealForm.tsx b/packages/mask/dashboard/pages/SignUp/steps/MnemonicRevealForm.tsx index c733b3ab58e7..d11dc41bbde9 100644 --- a/packages/mask/dashboard/pages/SignUp/steps/MnemonicRevealForm.tsx +++ b/packages/mask/dashboard/pages/SignUp/steps/MnemonicRevealForm.tsx @@ -1,31 +1,44 @@ -import { memo, useEffect, useState } from 'react' +import { memo, useCallback, useEffect, useState } from 'react' +import { useAsync } from 'react-use' import { useLocation, useNavigate } from 'react-router-dom' import { Button, Stack, Box, IconButton, FormControlLabel, Checkbox } from '@mui/material' +import { styled } from '@mui/material/styles' import { Refresh as RefreshIcon, Print as PrintIcon } from '@mui/icons-material' +import { Icons } from '@masknet/icons' import { MaskColorVar, useCustomSnackbar } from '@masknet/theme' import { DashboardRoutes, type ECKeyIdentifier } from '@masknet/shared-base' import { Header } from '../../../components/RegisterFrame/ColumnContentHeader.js' -import { - Body, - ColumnContentLayout, - PersonaLogoBox, - SignUpAccountLogo, -} from '../../../components/RegisterFrame/ColumnContentLayout.js' +import { PersonaLogoBox, SignUpAccountLogo } from '../../../components/RegisterFrame/ColumnContentLayout.js' import { useDashboardI18N } from '../../../locales/index.js' import { MnemonicReveal } from '../../../components/Mnemonic/index.js' import { SignUpRoutePath } from '../routePath.js' import { ButtonContainer } from '../../../components/RegisterFrame/ButtonContainer.js' import { useMnemonicWordsPuzzle } from '../../../hooks/useMnemonicWordsPuzzle.js' import { useCreatePersonaV2 } from '../../../hooks/useCreatePersonaV2.js' -import { PersonaContext } from '../../../hooks/usePersonaContext.js' import Services from '#services' import { PreviewDialog } from './PreviewDialog.js' -import { Icons } from '@masknet/icons' -import { useAsync } from 'react-use' + +const ColumnContentLayout = styled('div')` + display: flex; + flex-direction: column; + flex: 1; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +` + +const Body = styled('main')(({ theme }) => ({ + flex: '1 5', + width: '78%', + [theme.breakpoints.down('md')]: { + width: '95%', + }, +})) export const MnemonicRevealForm = memo(() => { const createPersona = useCreatePersonaV2() - const { changeCurrentPersona } = PersonaContext.useContainer() + const t = useDashboardI18N() const navigate = useNavigate() const { state } = useLocation() as { @@ -46,6 +59,8 @@ export const MnemonicRevealForm = memo(() => { const [privateKey, setPrivateKey] = useState('') const [checked, setChecked] = useState(false) + const changeCurrentPersona = useCallback(Services.Settings.setCurrentPersonaIdentifier, []) + const create = async () => { try { const identifier = await createPersona(words.join(' '), state.personaName) diff --git a/packages/mask/dashboard/pages/SignUp/steps/PersonaNameUI.tsx b/packages/mask/dashboard/pages/SignUp/steps/PersonaNameUI.tsx index 4ebf40488c31..174589f6357e 100644 --- a/packages/mask/dashboard/pages/SignUp/steps/PersonaNameUI.tsx +++ b/packages/mask/dashboard/pages/SignUp/steps/PersonaNameUI.tsx @@ -1,5 +1,5 @@ -import { MaskTextField, makeStyles } from '@masknet/theme' -import { Box, Typography } from '@mui/material' +import { makeStyles } from '@masknet/theme' +import { Box, TextField, Typography } from '@mui/material' import { useState } from 'react' import { PrimaryButton } from '../../../components/PrimaryButton/index.js' import { SetupFrameController } from '../../../components/SetupFrame/index.js' @@ -52,7 +52,7 @@ export function PersonaNameUI({ onNext, error, loading }: PersonaNameUIProps) { {t.persona_name()} - { setPersonaName(e.target.value) }} diff --git a/packages/mask/dashboard/pages/SignUp/steps/PersonaRecovery.tsx b/packages/mask/dashboard/pages/SignUp/steps/PersonaRecovery.tsx index eccb55d946bf..583b36906c56 100644 --- a/packages/mask/dashboard/pages/SignUp/steps/PersonaRecovery.tsx +++ b/packages/mask/dashboard/pages/SignUp/steps/PersonaRecovery.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useCallback, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' import { useCustomSnackbar } from '@masknet/theme' import { DashboardRoutes, EMPTY_LIST, type ECKeyIdentifier, type EC_Public_JsonWebKey } from '@masknet/shared-base' @@ -10,7 +10,6 @@ import { delay } from '@masknet/kit' import { useAsync, useAsyncFn } from 'react-use' import { SmartPayBundler, SmartPayOwner } from '@masknet/web3-providers' import urlcat from 'urlcat' -import { PersonaContext } from '../../../hooks/usePersonaContext.js' export function PersonaRecovery() { const t = useDashboardI18N() @@ -19,7 +18,7 @@ export function PersonaRecovery() { const createPersona = useCreatePersonaV2() const createPersonaByPrivateKey = useCreatePersonaByPrivateKey() const { showSnackbar } = useCustomSnackbar() - const { changeCurrentPersona } = PersonaContext.useContainer() + const state = useLocation().state as { mnemonic?: string[] privateKey?: string @@ -33,6 +32,8 @@ export function PersonaRecovery() { navigate(DashboardRoutes.SignUp, { replace: true }) }, [state.mnemonic, state.privateKey]) + const changeCurrentPersona = useCallback(Services.Settings.setCurrentPersonaIdentifier, []) + const [{ loading }, onNext] = useAsyncFn( async (personaName: string) => { setError('') @@ -84,7 +85,7 @@ export function PersonaRecovery() { setError((error as Error).message) } }, - [state?.mnemonic, state?.privateKey], + [state?.mnemonic, state?.privateKey, changeCurrentPersona], ) return diff --git a/packages/mask/dashboard/pages/Wallets/StartUp.tsx b/packages/mask/dashboard/pages/Wallets/StartUp.tsx deleted file mode 100644 index 98cf739a696c..000000000000 --- a/packages/mask/dashboard/pages/Wallets/StartUp.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { styled } from '@mui/material/styles' -import { Icons } from '@masknet/icons' -import { useDashboardI18N } from '../../locales/index.js' -import { Paper, Stack, Box } from '@mui/material' -import { ActionCard } from '../../components/ActionCard/index.js' -import { SelectProviderModal } from '@masknet/shared' - -const Container = styled('div')` - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; -` - -export function StartUp() { - const t = useDashboardI18N() - - return ( - - - - - } - subtitle={t.wallets_startup_connect_desc()} - action={{ - type: 'primary', - text: t.wallets_startup_connect_action(), - handler: SelectProviderModal.open, - }} - /> - - - - - ) -} diff --git a/packages/mask/dashboard/pages/Wallets/components/AddCollectibleDialog/index.tsx b/packages/mask/dashboard/pages/Wallets/components/AddCollectibleDialog/index.tsx deleted file mode 100644 index 7c2699250e23..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/AddCollectibleDialog/index.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { type FormEvent, memo, useCallback, useEffect, useState } from 'react' -import { Controller, useForm } from 'react-hook-form' -import { z } from 'zod' -import { MaskDialog, MaskTextField } from '@masknet/theme' -import { Box, Button, DialogActions, DialogContent } from '@mui/material' -import { isSameAddress } from '@masknet/web3-shared-base' -import { NetworkPluginID } from '@masknet/shared-base' -import { zodResolver } from '@hookform/resolvers/zod' -import { - useNonFungibleTokenContract, - useChainContext, - useWeb3State, - useTrustedNonFungibleTokens, - useNetworkContext, -} from '@masknet/web3-hooks-base' -import type { Web3Helper } from '@masknet/web3-helpers' -import { isValidAddress, type ChainId } from '@masknet/web3-shared-evm' -import { Web3, Hub } from '@masknet/web3-providers' -import { useDashboardI18N } from '../../../../locales/index.js' - -export interface AddCollectibleDialogProps { - selectedNetwork: Web3Helper.NetworkDescriptorAll - open: boolean - onClose: () => void -} - -type FormInputs = { - address: string - tokenId: string -} - -enum FormErrorType { - Added = 'ADDED', - NotExist = 'NOT_EXIST', -} - -export const AddCollectibleDialog = memo(({ open, onClose, selectedNetwork }) => { - const { pluginID } = useNetworkContext() - const { account } = useChainContext() - const { Token } = useWeb3State<'all'>() - const trustedNonFungibleTokens = useTrustedNonFungibleTokens(pluginID) - - const [address, setAddress] = useState('') - const [tokenId, setTokenId] = useState('') - - const { value: contract, loading } = useNonFungibleTokenContract(NetworkPluginID.PLUGIN_EVM, address, undefined, { - chainId: selectedNetwork.chainId as ChainId, - }) - - const onSubmit = useCallback(async () => { - if (loading || !account) return - if (address && tokenId && !contract) throw new Error(FormErrorType.NotExist) - - // If the NonFungible token is added - const tokenInDB = trustedNonFungibleTokens.find( - (x) => - isSameAddress(x.contract?.owner, account) && x.tokenId === tokenId && isSameAddress(x.address, address), - ) - if (tokenInDB) throw new Error(FormErrorType.Added) - - const tokenAsset = await Hub.getNonFungibleAsset(address ?? '', tokenId, { - chainId: selectedNetwork.chainId as ChainId, - }) - const token = await Web3.getNonFungibleToken(address ?? '', tokenId, undefined, { - chainId: selectedNetwork.chainId as ChainId, - }) - const tokenDetailed = { ...token, ...tokenAsset } - const isOwner = await Web3.getNonFungibleTokenOwnership(address, tokenId, account, undefined, { - chainId: selectedNetwork.chainId as ChainId, - }) - - // If the NonFungible token is belong this account - if (!isOwner) { - throw new Error(FormErrorType.NotExist) - } else { - tokenDetailed.owner = { address: account } - tokenDetailed.ownerId = account - await Token?.addToken?.(account, tokenDetailed) - onClose() - } - }, [account, address, tokenId, contract, loading, trustedNonFungibleTokens.length]) - - return ( - - ) -}) - -export interface AddCollectibleDialogUIProps { - open: boolean - onClose: () => void - address: string - onAddressChange: (address: string) => void - onTokenIdChange: (tokenId: string) => void - onSubmit: () => void -} - -export const AddCollectibleDialogUI = memo( - ({ open, onClose, onAddressChange, onTokenIdChange, onSubmit }) => { - const t = useDashboardI18N() - - const schema = z.object({ - address: z - .string() - .min(1, t.wallets_collectible_field_contract_require()) - .refine((address) => isValidAddress(address), t.wallets_incorrect_address()), - tokenId: z.string().min(1, t.wallets_collectible_field_token_id_require()), - }) - - const { - control, - handleSubmit, - setError, - watch, - reset, - formState: { errors, isSubmitting, isDirty }, - } = useForm({ - resolver: zodResolver(schema), - defaultValues: { address: '', tokenId: '' }, - }) - - useEffect(() => { - const subscription = watch((value) => { - onAddressChange(value.address!) - onTokenIdChange(value.tokenId!) - }) - return () => subscription.unsubscribe() - }, [watch]) - - const handleFormSubmit = (event: FormEvent) => { - handleSubmit(onSubmit)(event).catch((error) => { - setError('tokenId', { - type: 'value', - message: - error.message === FormErrorType.Added - ? t.wallets_collectible_been_added() - : t.wallets_collectible_error_not_exist(), - }) - }) - } - - const handleClose = () => { - reset() - onClose() - } - - return ( - -
- - - ( - - )} - name="address" - /> - - - ( - - )} - name="tokenId" - /> - - - - - - -
-
- ) - }, -) diff --git a/packages/mask/dashboard/pages/Wallets/components/AddTokenConfirmUI/index.tsx b/packages/mask/dashboard/pages/Wallets/components/AddTokenConfirmUI/index.tsx deleted file mode 100644 index fb0371b4e1f3..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/AddTokenConfirmUI/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { memo } from 'react' -import { useDashboardI18N } from '../../../../locales/index.js' -import { Box, Button, DialogActions, DialogContent, Stack, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { TokenIcon } from '@masknet/shared' -import { useFormContext } from 'react-hook-form' -import type { FungibleToken } from '@masknet/web3-shared-base' -import type { ChainId, SchemaType } from '@masknet/web3-shared-evm' - -export interface AddTokenConfirmUIProps { - onBack: () => void - onConfirm: () => void - token?: FungibleToken - balance?: string -} - -const useStyles = makeStyles()((theme) => ({ - actions: { - padding: theme.spacing(1, 5, 6.75, 5), - display: 'grid', - gridTemplateColumns: 'repeat(2, 1fr)', - gap: theme.spacing(3.5), - }, - button: { - borderRadius: Number(theme.shape.borderRadius) * 5, - }, - content: { - padding: theme.spacing(3.5, 5, 5), - minWidth: 600, - }, - container: { - '& > *': { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - }, - }, - confirmTitle: { - fontWeight: 500, - }, -})) - -export const AddTokenConfirmUI = memo(({ token, balance, onBack, onConfirm }) => { - const t = useDashboardI18N() - const { classes } = useStyles() - const { getValues } = useFormContext() - - return ( - <> - - - - {t.wallets_assets_token()} - {t.wallets_assets_balance()} - - - - {token?.address ? ( - - ) : null} - - {getValues('symbol')} - - - - {balance} {token?.symbol} - - - - - - - - - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/Assets/index.tsx b/packages/mask/dashboard/pages/Wallets/components/Assets/index.tsx deleted file mode 100644 index 907df12e32ee..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/Assets/index.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { memo, useCallback, useEffect, useState } from 'react' -import { useChainContext, useNetworkContext } from '@masknet/web3-hooks-base' -import type { Web3Helper } from '@masknet/web3-helpers' -import { makeStyles, useTabs } from '@masknet/theme' -import { TabContext, TabList, TabPanel } from '@mui/lab' -import { Box, Button, Tab } from '@mui/material' -import { - CollectionList, - UserAssetsProvider, - type CollectibleGridProps, - SelectFungibleTokenModal, -} from '@masknet/shared' -import { DashboardRoutes, NetworkPluginID } from '@masknet/shared-base' -import { ContentContainer } from '../../../../components/ContentContainer/index.js' -import { useDashboardI18N } from '../../../../locales/index.js' -import { AddCollectibleDialog } from '../AddCollectibleDialog/index.js' -import { FungibleTokenTable } from '../FungibleTokenTable/index.js' -import { useNavigate } from 'react-router-dom' -import { TransferTab } from '../Transfer/index.js' -import { Context } from '../../hooks/useContext.js' -import { useContainer } from 'unstated-next' - -const useStyles = makeStyles()((theme) => ({ - caption: { - paddingRight: theme.spacing(2.5), - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - }, - addCustomTokenButton: { - borderRadius: Number(theme.shape.borderRadius) * 3.5, - fontSize: theme.typography.caption.fontSize, - }, -})) - -export enum AssetTab { - Token = 'Token', - Investment = 'Investment', - Collectibles = 'Collectibles', -} - -const assetTabs = [AssetTab.Token, AssetTab.Collectibles] as const - -export interface AssetsProps { - network: Web3Helper.NetworkDescriptorAll | null -} - -const gridProps: CollectibleGridProps = { - columns: 'repeat(auto-fill, minmax(180px, 1fr))', - gap: 4, -} -export const Assets = memo(({ network }) => { - const { chainId } = useContainer(Context) - const t = useDashboardI18N() - const navigate = useNavigate() - const { pluginID } = useNetworkContext() - const { account } = useChainContext() - const { classes } = useStyles() - const assetTabsLabel: Record = { - [AssetTab.Token]: t.wallets_assets_token(), - [AssetTab.Investment]: t.wallets_assets_investment(), - [AssetTab.Collectibles]: t.wallets_assets_collectibles(), - } - - const [currentTab, onChange, , setTab] = useTabs(AssetTab.Token, AssetTab.Collectibles) - - const [addCollectibleOpen, setAddCollectibleOpen] = useState(false) - - useEffect(() => { - setTab(AssetTab.Token) - }, [pluginID]) - - const showCollectibles = [NetworkPluginID.PLUGIN_EVM, NetworkPluginID.PLUGIN_SOLANA].includes(pluginID) - const handleActionClick = useCallback( - (asset: Web3Helper.NonFungibleAssetAll) => { - // Sending NFT is only available on EVM currently. - if (pluginID !== NetworkPluginID.PLUGIN_EVM) return - navigate(DashboardRoutes.WalletsTransfer, { - state: { - type: TransferTab.Collectibles, - nonFungibleToken: { ...asset, chainId }, - }, - }) - }, - [pluginID, chainId], - ) - - return ( - <> - - - - - {assetTabs - .filter((x) => showCollectibles || x === AssetTab.Token) - .map((key) => ( - - ))} - - {pluginID === NetworkPluginID.PLUGIN_EVM && network ? ( - - ) : null} - - - - - - - - - - - - {addCollectibleOpen && network ? ( - setAddCollectibleOpen(false)} /> - ) : null} - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/Balance/index.tsx b/packages/mask/dashboard/pages/Wallets/components/Balance/index.tsx deleted file mode 100644 index 757a64d6feef..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/Balance/index.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { memo, useMemo } from 'react' -import { noop } from 'lodash-es' -import { BigNumber } from 'bignumber.js' -import { useContainer } from 'unstated-next' -import { Icons } from '@masknet/icons' -import { FormattedCurrency, MiniNetworkSelector } from '@masknet/shared' -import { DashboardRoutes } from '@masknet/shared-base' -import { MaskColorVar } from '@masknet/theme' -import { formatCurrency, getTokenUSDValue } from '@masknet/web3-shared-base' -import { Box, Button, buttonClasses, styled, Typography } from '@mui/material' -import { useDashboardI18N } from '../../../../locales/index.js' -import { useIsMatched } from '../../hooks/index.js' -import type { Web3Helper } from '@masknet/web3-helpers' -import { Context } from '../../hooks/useContext.js' - -const BalanceContainer = styled('div')( - ({ theme }) => ` - display: flex; - justify-content: space-between; - border-radius: 16px; - align-items: center; - padding: ${theme.spacing(2.5)}; - background: ${MaskColorVar.primaryBackground}; -`, -) - -const IconContainer = styled('div')` - width: 48px; - height: 48px; - display: flex; - justify-content: center; - align-items: center; - background: ${MaskColorVar.infoBackground}; - border-radius: 24px; -` - -const BalanceDisplayContainer = styled('div')( - ({ theme }) => ` - display: flex; - flex-direction: column; - justify-content: space-between; - margin-left: ${theme.spacing(1)}; -`, -) - -const BalanceTitle = styled(Typography)( - ({ theme }) => ` - font-size: ${theme.typography.subtitle2.fontSize}; - color: ${MaskColorVar.iconLight}; -`, -) - -const BalanceContent = styled(Typography)( - ({ theme }) => ` - font-size: ${theme.typography.h5.fontSize}; - color: ${MaskColorVar.textPrimary}; - line-height: ${theme.typography.h2.lineHeight}; -`, -) - -const ButtonGroup = styled('div')` - display: inline-grid; - gap: 10px; - grid-template-columns: repeat(4, 1fr); - & > * { - font-size: 12px; - white-space: nowrap; - & .${buttonClasses.endIcon} > *:nth-of-type(1) { - font-size: 0; - } - } -` - -export interface BalanceCardProps { - onSend(): void - onBuy(): void - onSwap(): void - onReceive(): void - networks: Web3Helper.NetworkDescriptorAll[] - selectedNetwork: Web3Helper.NetworkDescriptorAll | null - showOperations: boolean - onSelectNetwork(network: Web3Helper.NetworkDescriptorAll | null): void -} - -export const Balance = memo( - ({ onSend, onBuy, onSwap, onReceive, onSelectNetwork, networks, selectedNetwork, showOperations }) => { - const t = useDashboardI18N() - - const isWalletTransferPath = useIsMatched(DashboardRoutes.WalletsTransfer) - const isWalletHistoryPath = useIsMatched(DashboardRoutes.WalletsHistory) - - const { fungibleAssets } = useContainer(Context) - - const balance = useMemo(() => { - if (!fungibleAssets.data?.length) return 0 - - const values = fungibleAssets.data - .filter((x) => (selectedNetwork ? x.chainId === selectedNetwork.chainId : true)) - .map((y) => getTokenUSDValue(y.value)) - return BigNumber.sum(...values).toNumber() - }, [selectedNetwork, fungibleAssets.data]) - - const isHiddenAllButton = isWalletHistoryPath || isWalletTransferPath || networks.length <= 1 - - return ( - - - - - - - - {t.wallets_balance()} {selectedNetwork?.name ?? t.wallets_balance_all_chain()} - - - - - - networks.length <= 1 ? noop : onSelectNetwork(network) - } - /> - - - {showOperations ? ( - - - - - - - ) : null} - - ) - }, -) diff --git a/packages/mask/dashboard/pages/Wallets/components/CreateWallet/index.tsx b/packages/mask/dashboard/pages/Wallets/components/CreateWallet/index.tsx deleted file mode 100644 index 00d3f317b085..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/CreateWallet/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { Button, styled, FilledInput, Tab, Typography } from '@mui/material' -import { makeStyles, ButtonGroupTabList, MaskColorVar, useTabs } from '@masknet/theme' -import { memo } from 'react' -import { Icons } from '@masknet/icons' -import { MnemonicReveal } from '../../../../components/Mnemonic/index.js' -import { MaskAlert } from '../../../../components/MaskAlert/index.js' -import { useDashboardI18N } from '../../../../locales/index.js' -import { TabContext, TabPanel } from '@mui/lab' - -const Container = styled('div')` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -` - -const Refresh = styled('div')( - ({ theme }) => ` - display: flex; - align-items: center; - justify-content: flex-end; - width: 584px; - margin: ${theme.spacing(2, 0)}; - font-size: ${theme.typography.fontSize}; - line-height: 20px; - color: ${theme.palette.primary.main}; - cursor: pointer; -`, -) - -const MnemonicGeneratorContainer = styled('div')( - ({ theme }) => ` - padding: ${theme.spacing(4, 5)}; - background-color: ${theme.palette.background.default}; - border-radius: 8px; -`, -) - -const ControlContainer = styled('div')( - ({ theme }) => ` - margin-top: ${theme.spacing(6)}; - display: grid; - justify-content: center; - grid-template-columns: repeat(2, 180px); - gap: 24px; - width: 584px; -`, -) - -const AlertContainer = styled('div')( - ({ theme }) => ` - width: 676px; - margin-top: ${theme.spacing(7)}; - color: ${MaskColorVar.textSecondary}; -`, -) - -const PrivateKeyInput = styled(FilledInput)( - ({ theme }) => ` - width: 582px; - height: 182px; - margin-top: ${theme.spacing(3)}; -`, -) - -const useTabPanelStyles = makeStyles()({ - root: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: 0, - }, -}) - -export const CreateWallet = memo(() => { - const t = useDashboardI18N() - const { classes } = useTabPanelStyles() - const [currentTab, onChange, tabs] = useTabs('mnemonic', 'json', 'privateKey') - return ( - - - - - - - - - - - {t.wallets_create_wallet_refresh()} - - - - - - - TODO - - - - - - - - - - - - - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/EmptyPlaceholder/index.tsx b/packages/mask/dashboard/pages/Wallets/components/EmptyPlaceholder/index.tsx deleted file mode 100644 index f2b38fd4739c..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/EmptyPlaceholder/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { memo } from 'react' -import { Box, Typography } from '@mui/material' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { Icons } from '@masknet/icons' - -const useStyles = makeStyles()((theme) => ({ - container: { - width: '100%', - height: '100%', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - }, - prompt: { - color: MaskColorVar.textLight, - fontSize: theme.typography.pxToRem(12), - lineHeight: theme.typography.pxToRem(16), - marginTop: theme.spacing(2.5), - }, -})) - -export interface EmptyPlaceholderProps extends React.PropsWithChildren<{}> {} - -export const EmptyPlaceholder = memo(({ children }) => { - const { classes } = useStyles() - return ( - - - {children} - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/FungibleTokenTable/index.tsx b/packages/mask/dashboard/pages/Wallets/components/FungibleTokenTable/index.tsx deleted file mode 100644 index a6788f766f45..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/FungibleTokenTable/index.tsx +++ /dev/null @@ -1,259 +0,0 @@ -import { Icons } from '@masknet/icons' -import { CrossIsolationMessages, DashboardRoutes, EMPTY_LIST, NetworkPluginID } from '@masknet/shared-base' -import { MaskColorVar, makeStyles } from '@masknet/theme' -import type { Web3Helper } from '@masknet/web3-helpers' -import { useNativeToken, useNetworkContext } from '@masknet/web3-hooks-base' -import { CurrencyType, isGreaterThanOrEqualTo, isLessThan, leftShift, minus, toZero } from '@masknet/web3-shared-base' -import { isNativeTokenAddress } from '@masknet/web3-shared-evm' -import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from '@mui/material' -import { memo, useCallback, useMemo, useState } from 'react' -import { useNavigate } from 'react-router-dom' -import { useContainer } from 'unstated-next' -import { LoadingPlaceholder } from '../../../../components/LoadingPlaceholder/index.js' -import { useDashboardI18N } from '../../../../locales/index.js' -import { Context } from '../../hooks/useContext.js' -import { EmptyPlaceholder } from '../EmptyPlaceholder/index.js' -import { FungibleTokenTableRow } from '../FungibleTokenTableRow/index.js' -import { TransferTab } from '../Transfer/types.js' - -const useStyles = makeStyles()((theme) => ({ - container: { - display: 'flex', - flexDirection: 'column', - }, - table: { - paddingLeft: theme.spacing(5), - paddingRight: theme.spacing(5), - [theme.breakpoints.down('lg')]: { - paddingLeft: theme.spacing(2), - paddingRight: theme.spacing(2), - }, - }, - header: { - color: MaskColorVar.normalText, - fontWeight: theme.typography.fontWeightRegular as any, - padding: '12px 0 12px', - backgroundColor: MaskColorVar.primaryBackground, - border: 'none', - }, - more: { - textAlign: 'center', - }, - moreBody: { - display: 'inline-flex', - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - padding: theme.spacing(1, 2), - gap: 2, - fontWeight: 600, - margin: theme.spacing(2, 'auto', 1), - cursor: 'pointer', - borderRadius: 20, - backgroundColor: theme.palette.maskColor.bg, - }, -})) - -export interface FungibleTokenTableProps { - selectedChainId?: Web3Helper.ChainIdAll -} - -export const FungibleTokenTable = memo(({ selectedChainId }) => { - const navigate = useNavigate() - const [isExpand, setIsExpand] = useState(false) - const { data: nativeToken } = useNativeToken<'all'>(NetworkPluginID.PLUGIN_EVM, { - chainId: selectedChainId, - }) - const { fungibleAssets } = useContainer(Context) - - const onSwap = useCallback((token: Web3Helper.FungibleAssetAll) => { - return CrossIsolationMessages.events.swapDialogEvent.sendToLocal({ - open: true, - traderProps: { - defaultInputCoin: { - name: token.name || '', - symbol: token.symbol || '', - address: token.address, - decimals: token.decimals, - }, - chainId: token.chainId, - }, - }) - }, []) - - const onSend = useCallback( - (token: Web3Helper.FungibleAssetAll) => - navigate(DashboardRoutes.WalletsTransfer, { state: { token, type: TransferTab.Token } }), - [], - ) - - const dataSource = useMemo(() => { - const results = - fungibleAssets.data?.filter((x) => !selectedChainId || x.chainId === selectedChainId) ?? EMPTY_LIST - - if (!selectedChainId) { - return results.sort((a, z) => { - const aUSD = toZero(a.value?.[CurrencyType.USD] ?? '0') - const zUSD = toZero(z.value?.[CurrencyType.USD] ?? '0') - - // token value - if (!aUSD.isEqualTo(zUSD)) return minus(zUSD, aUSD).isPositive() ? 1 : -1 - - return 0 - }) - } - - if (!results.length && nativeToken) { - return [ - { - ...nativeToken, - balance: '0', - }, - ] - } - - return results - }, [nativeToken, fungibleAssets.data, selectedChainId]) - - const handleSwitch = useCallback(() => { - setIsExpand((x) => !x) - }, []) - - const hasLowValueToken = useMemo(() => { - return !!dataSource.find((x) => !isNativeTokenAddress(x.address) && isLessThan(x.value?.usd ?? 0, 1)) - }, [dataSource]) - - const sortedData = useMemo(() => { - return dataSource.sort((first, second) => { - const firstValue = leftShift(first.balance, first.decimals) - const secondValue = leftShift(second.balance, second.decimals) - if (firstValue.isEqualTo(secondValue)) return 0 - return Number(firstValue.lt(secondValue)) - }) - }, [dataSource]) - - const visibleData = useMemo(() => { - if (isExpand) return sortedData - return sortedData.filter((asset) => { - return isNativeTokenAddress(asset.address) || isGreaterThanOrEqualTo(asset.value?.usd ?? 0, 1) - }) - }, [isExpand, sortedData]) - - return ( - <> - - - - ) -}) - -FungibleTokenTable.displayName = 'FungibleTokenTable' - -export interface MoreBarUIProps { - isEmpty: boolean - isExpand: boolean - isLoading: boolean - hasLowValueToken: boolean - onSwitch: () => void -} - -export const MoreBarUI = memo(({ isExpand, isEmpty, isLoading, hasLowValueToken, onSwitch }) => { - const t = useDashboardI18N() - const { classes } = useStyles() - - if (isEmpty || isLoading || !hasLowValueToken) return null - return ( - - - - {t.wallets_assets_more({ - symbol: '<', - context: isExpand ? 'expanded' : 'collapsed', - })} - - - - - ) -}) - -MoreBarUI.displayName = 'MoreBarUI' - -export interface TokenTableUIProps { - isEmpty: boolean - isExpand: boolean - isLoading: boolean - data: Web3Helper.FungibleAssetAll[] - onSwap(token: Web3Helper.FungibleAssetAll): void - onSend(token: Web3Helper.FungibleAssetAll): void -} - -export const TokenTableUI = memo(({ onSwap, onSend, isLoading, isEmpty, data }) => { - const t = useDashboardI18N() - const { classes } = useStyles() - const { pluginID: currentPluginId } = useNetworkContext() - - return ( - <> - - {isLoading ? ( - - ) : isEmpty ? ( - - ) : ( - - - - - {t.wallets_assets_asset()} - - - {t.wallets_assets_balance()} - - - {t.wallets_assets_price()} - - - {t.wallets_assets_value()} - - {currentPluginId === NetworkPluginID.PLUGIN_EVM && ( - - {t.wallets_assets_operation()} - - )} - - - - {data.length ? ( - - {data.map((asset) => ( - - ))} - - ) : null} -
- )} -
- - ) -}) - -TokenTableUI.displayName = 'TokenTableUI' diff --git a/packages/mask/dashboard/pages/Wallets/components/FungibleTokenTableRow/index.tsx b/packages/mask/dashboard/pages/Wallets/components/FungibleTokenTableRow/index.tsx deleted file mode 100644 index c684bde5ccaa..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/FungibleTokenTableRow/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { memo } from 'react' -import { Box, Button, TableCell, TableRow, Typography } from '@mui/material' -import { getMaskColor, makeStyles } from '@masknet/theme' -import { FormattedCurrency, ImageIcon, TokenIcon } from '@masknet/shared' -import { useNetworkContext, useNetworkDescriptor } from '@masknet/web3-hooks-base' -import { CurrencyType, formatBalance, formatCurrency, getTokenUSDValue } from '@masknet/web3-shared-base' -import { NetworkPluginID } from '@masknet/shared-base' -import { ChainId } from '@masknet/web3-shared-evm' -import type { Web3Helper } from '@masknet/web3-helpers' -import { useDashboardI18N } from '../../../../locales/index.js' -import { Context } from '../../hooks/useContext.js' -import { useContainer } from 'unstated-next' - -const useStyles = makeStyles()((theme) => ({ - icon: { - width: 36, - height: 36, - }, - symbol: { - marginLeft: 14, - fontSize: theme.typography.pxToRem(14), - maxWidth: '100px', - textOverflow: 'ellipsis', - textTransform: 'capitalize', - whiteSpace: 'nowrap', - overflow: 'hidden', - }, - row: { - '&:hover': { - backgroundColor: theme.palette.background.default, - }, - }, - cell: { - padding: theme.spacing(2), - border: 'none', - }, - button: { - color: theme.palette.mode === 'dark' ? getMaskColor(theme).white : getMaskColor(theme).primary, - '&:disabled': { - color: theme.palette.mode === 'dark' ? getMaskColor(theme).white : getMaskColor(theme).primary, - }, - }, - chainIcon: { - position: 'absolute', - right: -9, - bottom: 0, - height: 18, - width: 18, - border: `1px solid ${theme.palette.background.default}`, - borderRadius: '50%', - }, -})) - -export interface TokenTableRowProps { - asset: Web3Helper.FungibleAssetAll - onSwap(asset: Web3Helper.FungibleAssetAll): void - onSend(asset: Web3Helper.FungibleAssetAll): void -} - -export const FungibleTokenTableRow = memo(({ asset, onSend, onSwap }) => { - const t = useDashboardI18N() - const { classes } = useStyles() - - const { pluginID, setSelectedNetwork } = useContainer(Context) - const networkDescriptor = useNetworkDescriptor(pluginID, asset.chainId) - const { pluginID: currentPluginId } = useNetworkContext() - - return ( - - - - - - - - - - {asset.symbol} - - - - {formatBalance(asset.balance, asset.decimals, 6)} - - - - {asset.price?.[CurrencyType.USD] - ? formatCurrency(Number.parseFloat(asset.price[CurrencyType.USD] ?? '')) - : '-'} - - - - - {getTokenUSDValue(asset.value) < 0.01 ? ( - '<$0.01' - ) : ( - - )} - - - {currentPluginId === NetworkPluginID.PLUGIN_EVM && ( - - - - - )} - - ) -}) - -FungibleTokenTableRow.displayName = 'FungibleTokenTableRow' diff --git a/packages/mask/dashboard/pages/Wallets/components/History/index.tsx b/packages/mask/dashboard/pages/Wallets/components/History/index.tsx deleted file mode 100644 index 6aef2d4a627a..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/History/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { memo } from 'react' -import { ContentContainer } from '../../../../components/ContentContainer/index.js' -import { HistoryTable } from '../HistoryTable/index.js' -import type { Web3Helper } from '@masknet/web3-helpers' - -interface HistoryProps { - selectedChainId: Web3Helper.ChainIdAll -} - -export const History = memo(({ selectedChainId }) => { - return ( - - - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/HistoryTable/index.tsx b/packages/mask/dashboard/pages/Wallets/components/HistoryTable/index.tsx deleted file mode 100644 index f08e66fd3545..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/HistoryTable/index.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { type Dispatch, memo, type SetStateAction, useMemo, useState } from 'react' -import { Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from '@mui/material' -import { LoadingBase, makeStyles, MaskColorVar } from '@masknet/theme' -import type { Web3Helper } from '@masknet/web3-helpers' -import { useTransactions, useNetworkContext, useIterator } from '@masknet/web3-hooks-base' -import { ElementAnchor } from '@masknet/shared' -import { EMPTY_LIST } from '@masknet/shared-base' -import { Icons } from '@masknet/icons' -import type { Transaction } from '@masknet/web3-shared-base' -import { HistoryTableRow } from '../HistoryTableRow/index.js' -import { useDashboardI18N } from '../../../../locales/index.js' - -const useStyles = makeStyles()((theme) => ({ - container: { - display: 'flex', - flexDirection: 'column', - height: '100%', - }, - loading: { - justifyContent: 'center', - alignItems: 'center', - }, - header: { - color: MaskColorVar.normalText, - fontWeight: theme.typography.fontWeightRegular as any, - padding: '12px 0 12px', - border: 'none', - backgroundColor: MaskColorVar.primaryBackground, - }, - placeholder: { - display: 'flex', - flex: 1, - alignItems: 'center', - justifyContent: 'center', - flexDirection: 'column', - rowGap: 14, - }, - placeholderText: { - fontSize: 14, - lineHeight: '18px', - color: theme.palette.maskColor.second, - }, -})) - -interface HistoryTableProps { - selectedChainId: Web3Helper.ChainIdAll -} - -export const HistoryTable = memo(({ selectedChainId }) => { - const [page, setPage] = useState(0) - const { pluginID } = useNetworkContext() - const iterator = useTransactions(pluginID, { chainId: selectedChainId }) - const { - value = EMPTY_LIST, - next, - done, - loading, - } = useIterator>(iterator) - - const dataSource = useMemo(() => { - return value.filter((x) => x.chainId === selectedChainId) - }, [value, selectedChainId]) - - return ( - - ) -}) - -export interface HistoryTableUIProps { - page: number - next?: () => void - onPageChange: Dispatch> - done: boolean - isLoading: boolean - isEmpty: boolean - dataSource: Array> - selectedChainId: Web3Helper.ChainIdAll -} - -export const HistoryTableUI = memo(({ dataSource, next, isLoading, done, selectedChainId }) => { - const t = useDashboardI18N() - const { classes, cx } = useStyles() - - return ( - <> - {dataSource.length ? ( - - - - - - {t.wallets_history_types()} - - - {t.wallets_history_value()} - - - {t.wallets_history_receiver()} - - - - - - {dataSource.map((transaction) => ( - - ))} - -
-
- ) : isLoading || !done ? ( - - - - ) : !isLoading && dataSource.length === 0 ? ( - - - {t.wallet_history_no_data()} - - ) : null} - next?.()}> - {!done && dataSource?.length ? : null} - - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/HistoryTableRow/index.tsx b/packages/mask/dashboard/pages/Wallets/components/HistoryTableRow/index.tsx deleted file mode 100644 index b06d949c8a02..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/HistoryTableRow/index.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { memo } from 'react' -import { BigNumber } from 'bignumber.js' -import formatDateTime from 'date-fns/format' -import fromUnixTime from 'date-fns/fromUnixTime' -import { Icons } from '@masknet/icons' -import { useReverseAddress, useWeb3Others } from '@masknet/web3-hooks-base' -import type { Web3Helper } from '@masknet/web3-helpers' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { TokenType, TransactionStatusType, type Transaction } from '@masknet/web3-shared-base' -import { Box, Link, Stack, TableCell, TableRow, Tooltip, Typography } from '@mui/material' -import { DebankTransactionDirection, ZerionTransactionDirection } from '@masknet/web3-providers/types' -import { TransactionIcon } from '../TransactionIcon/index.js' - -const useStyles = makeStyles()((theme) => ({ - type: { - maxWidth: '240px', - textOverflow: 'ellipsis', - textTransform: 'capitalize', - whiteSpace: 'nowrap', - overflow: 'hidden', - }, - cell: { - padding: `${theme.spacing(1.25)} ${theme.spacing(2.5)}`, - border: 'none', - fontSize: theme.typography.pxToRem(14), - }, - link: { - display: 'inline-flex', - alignItems: 'center', - justifyContent: 'center', - height: 21, - color: MaskColorVar.textPrimary, - }, - linkIcon: { - // TODO: replace with theme color - color: theme.palette.mode === 'dark' ? '#F5F5F5' : '#07101B', - marginLeft: 10, - }, - pair: { - color: MaskColorVar.greenMain, - }, - send: { - color: MaskColorVar.redMain, - }, - hover: { - '&:hover': { - backgroundColor: theme.palette.background.default, - }, - }, - nftName: { - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - cursor: 'default', - }, -})) - -export interface HistoryTableRowProps { - transaction: Transaction - selectedChainId: Web3Helper.ChainIdAll -} - -export const HistoryTableRow = memo(({ transaction, selectedChainId }) => { - const { data: domain } = useReverseAddress(undefined, transaction.to) - const transactionType = (transaction.type ?? '').replaceAll('_', ' ') - - return ( - - ) -}) - -HistoryTableRow.displayName = 'HistoryTableRow' - -export interface HistoryTableRowUIProps extends HistoryTableRowProps { - selectedChainId: Web3Helper.ChainIdAll - formattedType: string - domain?: string | null -} - -export const HistoryTableRowUI = memo( - ({ transaction, selectedChainId, formattedType, domain }) => { - const { classes, cx } = useStyles() - const Others = useWeb3Others() - - return ( - - - - - - - {formattedType} - - - {formatDateTime(fromUnixTime(transaction.timestamp), 'yyyy-MM-dd HH:mm')} - - - - - - {transaction.assets.map((pair, index) => { - const direction = - pair.direction === DebankTransactionDirection.SEND || - pair.direction === ZerionTransactionDirection.OUT - return ( - - - {direction ? '-' : '+'} - - {new BigNumber(pair.amount).toFixed( - new BigNumber(pair.amount).toNumber() < 1 ? 6 : 2, - )} - - - - {pair.type === TokenType.NonFungible && ( - - - {pair.name} - - - )} - {pair.type === TokenType.Fungible && ( - - {pair.symbol} - - )} - - - ) - })} - - - - - {domain ? Others.formatDomainName?.(domain) : Others.formatAddress(transaction.to, 4)} - - - - - - - - ) - }, -) diff --git a/packages/mask/dashboard/pages/Wallets/components/ImportWallet/index.tsx b/packages/mask/dashboard/pages/Wallets/components/ImportWallet/index.tsx deleted file mode 100644 index 276c29df806b..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/ImportWallet/index.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { memo } from 'react' -import { TabContext, TabPanel } from '@mui/lab' -import { Button, styled, FilledInput, Tab } from '@mui/material' -import { makeStyles, ButtonGroupTabList, useTabs } from '@masknet/theme' -import { DesktopMnemonicConfirm } from '../../../../components/Mnemonic/index.js' -import { MaskAlert } from '../../../../components/MaskAlert/index.js' -import { useDashboardI18N } from '../../../../locales/index.js' - -const Container = styled('div')` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -` - -const ControlContainer = styled('div')( - ({ theme }) => ` - margin-top: ${theme.spacing(6)}; - display: grid; - justify-content: center; - grid-template-columns: repeat(2, 180px); - gap: 24px; - width: 584px; -`, -) - -const AlertContainer = styled('div')( - ({ theme }) => ` - width: 676px; - margin-top: ${theme.spacing(7)}; -`, -) - -const PrivateKeyInput = styled(FilledInput)( - ({ theme }) => ` - width: 582px; - height: 182px; - margin-top: ${theme.spacing(3)}; -`, -) - -const PasswordInput = styled(FilledInput)( - ({ theme }) => ` - width: 582px; - margin-top: ${theme.spacing(3)}; -`, -) - -const useStyles = makeStyles()((theme) => ({ - tabs: { width: 582, justifyContent: 'center' }, - panels: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: 0, - marginTop: theme.spacing(3), - width: 582, - }, -})) - -export const ImportWallet = memo(() => { - const { classes } = useStyles() - const t = useDashboardI18N() - const [currentTab, onChange, tabs] = useTabs('mnemonic', 'json', 'privateKey') - const tabPanelClasses = { root: classes.panels } - return ( - <> - - - - - - - - - {}} /> - - - TBD - - - - - - - - - - - - - - - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/ReceiveDialog/index.tsx b/packages/mask/dashboard/pages/Wallets/components/ReceiveDialog/index.tsx deleted file mode 100644 index 4ab9ad957dda..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/ReceiveDialog/index.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { memo } from 'react' -import { useCopyToClipboard } from 'react-use' -import { MaskColorVar, MaskDialog, makeStyles } from '@masknet/theme' -import { QRCode, useSnackbarCallback } from '@masknet/shared' -import { DialogContent, Typography, DialogActions, Button } from '@mui/material' -import { ChainResolver } from '@masknet/web3-providers' -import type { Web3Helper } from '@masknet/web3-helpers' -import { useChainContext, useReverseAddress, useWeb3Others } from '@masknet/web3-hooks-base' -import { WalletQRCodeContainer } from '../../../../components/WalletQRCodeContainer/index.js' -import { useDashboardI18N } from '../../../../locales/index.js' - -const useStyles = makeStyles()((theme) => ({ - paper: { - width: '100%', - }, - container: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - }, - addressTitle: { - marginTop: theme.spacing(1.5), - color: MaskColorVar.normalText, - }, - address: { - marginTop: theme.spacing(1.5), - fontWeight: 600, - }, -})) - -export interface ReceiveDialogProps { - open: boolean - address: string - onClose: () => void -} - -export const ReceiveDialog = memo(({ open, address, onClose }) => { - const Others = useWeb3Others() - - const { chainId } = useChainContext() - const { data: domain } = useReverseAddress(undefined, address) - - return ( - - ) -}) - -export interface ReceiveDialogUIProps extends ReceiveDialogProps { - chainId: Web3Helper.ChainIdAll - domain?: string -} - -export const ReceiveDialogUI = memo(({ open, chainId, address, domain, onClose }) => { - const t = useDashboardI18N() - const { classes } = useStyles() - const [, copyToClipboard] = useCopyToClipboard() - const copyAddress = useSnackbarCallback({ - executor: async (address: string) => copyToClipboard(address), - deps: [], - successText: t.wallets_address_copied(), - }) - // TODO: The text prop protocol maybe correct and requires confirmation - return ( - - - - {t.wallets_receive_tips({ chainName: ChainResolver.chainName(chainId as number) ?? '' })} - - - - - - {t.wallets_address()} - - - {domain ? ( - - {domain} - - ) : null} - - - {address} - - - - - - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/TransactionIcon/index.tsx b/packages/mask/dashboard/pages/Wallets/components/TransactionIcon/index.tsx deleted file mode 100644 index bc27d1e719a8..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/TransactionIcon/index.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { memo, useMemo } from 'react' -import { Box } from '@mui/material' -import { Icons } from '@masknet/icons' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { isSameAddress } from '@masknet/web3-shared-base' -import type { NetworkPluginID } from '@masknet/shared-base' -import { useRedPacketConstants } from '@masknet/web3-shared-evm' -import { useChainContext } from '@masknet/web3-hooks-base' -import { HistoryAPI } from '@masknet/web3-providers/types' - -const useStyles = makeStyles()(() => ({ - container: { - width: 36, - height: 36, - borderRadius: '50%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - // default backgroundColor - background: MaskColorVar.warning.alpha(0.1), - }, - success: { - background: MaskColorVar.success.alpha(0.1), - }, - warning: { - background: MaskColorVar.warning.alpha(0.1), - }, - error: { - background: MaskColorVar.redMain.alpha(0.1), - }, - icon: { - height: 20, - width: 20, - }, -})) - -export interface TransactionIconProps { - type?: string - transactionType?: string - address: string - failed: boolean -} - -export const TransactionIcon = memo(({ address, failed, type, transactionType }) => { - const { chainId } = useChainContext() - const { - HAPPY_RED_PACKET_ADDRESS_V1, - HAPPY_RED_PACKET_ADDRESS_V2, - HAPPY_RED_PACKET_ADDRESS_V3, - HAPPY_RED_PACKET_ADDRESS_V4, - } = useRedPacketConstants(chainId) - - const isRedPacket = - isSameAddress(HAPPY_RED_PACKET_ADDRESS_V1, address) || - isSameAddress(HAPPY_RED_PACKET_ADDRESS_V2, address) || - isSameAddress(HAPPY_RED_PACKET_ADDRESS_V3, address) || - isSameAddress(HAPPY_RED_PACKET_ADDRESS_V4, address) - - return ( - - ) -}) - -export interface TransactionIconUIProps { - isRedPacket: boolean - isFailed: boolean - type?: string - transactionType?: string -} - -export const TransactionIconUI = memo(({ isFailed, isRedPacket, type, transactionType }) => { - const { classes, cx } = useStyles() - const icon = useMemo(() => { - if (isFailed) return - if (isRedPacket) return - - switch (type) { - case HistoryAPI.TransactionType.SEND: - return - case HistoryAPI.TransactionType.TRANSFER: - return - case HistoryAPI.TransactionType.WITHDRAW: - case HistoryAPI.TransactionType.RECEIVE: - return - case HistoryAPI.TransactionType.CREATE_LUCKY_DROP: - case HistoryAPI.TransactionType.CREATE_RED_PACKET: - return - default: - return - } - }, [isFailed, isRedPacket, type]) - - const isNotFailed = !isFailed && !!transactionType - const isSuccess = - isNotFailed && - [ - HistoryAPI.TransactionType.RECEIVE, - HistoryAPI.TransactionType.CLAIM, - HistoryAPI.TransactionType.WITHDRAW, - ].includes(transactionType as HistoryAPI.TransactionType) - const isWarning = - isNotFailed && - [ - HistoryAPI.TransactionType.SEND, - HistoryAPI.TransactionType.FILL_POOL, - HistoryAPI.TransactionType.CREATE_RED_PACKET, - HistoryAPI.TransactionType.CREATE_LUCKY_DROP, - HistoryAPI.TransactionType.TRANSFER, - HistoryAPI.TransactionType.SWAP, - ].includes(transactionType as HistoryAPI.TransactionType) - - return ( - - {icon} - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/Transfer/NFTCard.tsx b/packages/mask/dashboard/pages/Wallets/components/Transfer/NFTCard.tsx deleted file mode 100644 index 5d94826a21e4..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/Transfer/NFTCard.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { memo, useMemo, useState } from 'react' -import { Checkbox, ImageListItem, ImageListItemBar, Box } from '@mui/material' -import { getMaskColor, makeStyles, MaskColorVar } from '@masknet/theme' -import { Icons } from '@masknet/icons' -import { AssetPreviewer } from '@masknet/shared' -import { NetworkPluginID } from '@masknet/shared-base' -import { useNonFungibleAsset } from '@masknet/web3-hooks-base' -import type { NonFungibleToken } from '@masknet/web3-shared-base' -import type { ChainId, SchemaType } from '@masknet/web3-shared-evm' - -const useStyles = makeStyles()({ - checkbox: { - position: 'absolute', - top: 0, - right: 0, - }, - disabled: { - filter: 'opacity(0.5)', - cursor: 'not-allowed', - }, - barTitle: { - padding: 0, - lineHeight: '16px', - }, - fallbackImage: { - minHeight: '0 !important', - maxWidth: 'none', - transform: 'translateY(10px)', - width: 64, - height: 64, - }, -}) - -export interface NFTCardProps { - token: NonFungibleToken - selectedTokenId: string - onSelect(tokenId: string): void -} - -export const NFTCard = memo(({ token, selectedTokenId, onSelect }) => { - const { classes } = useStyles() - const [checked, setChecked] = useState(!!selectedTokenId && selectedTokenId === token.tokenId) - const isDisabled = useMemo( - () => !!selectedTokenId && selectedTokenId !== token.tokenId, - [selectedTokenId, token.tokenId], - ) - - const { data: NFTDetailed } = useNonFungibleAsset<'all'>(NetworkPluginID.PLUGIN_EVM, token.address, token.tokenId, { - chainId: token.chainId, - }) - - const NFTNameBar = useMemo(() => { - return ( - (theme.palette.mode === 'dark' ? MaskColorVar.primaryBackground : '#F9F9FA'), - }} - classes={{ titleWrap: classes.barTitle }} - subtitle={{token.tokenId}} - position="below" - /> - ) - }, [token.tokenId]) - - return ( - (theme.palette.mode === 'dark' ? getMaskColor(theme).white : '#F9F9FA'), - }} - className={isDisabled ? classes.disabled : ''}> - - {NFTNameBar} - - {/* TODO: replace to mask checkbox component */} - } - checkedIcon={} - onChange={(e) => { - const value = e.target.checked - onSelect(value ? token.tokenId : '') - setChecked(value) - }} - /> - - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/Transfer/SelectNFTList.tsx b/packages/mask/dashboard/pages/Wallets/components/Transfer/SelectNFTList.tsx deleted file mode 100644 index 45e72f76aa14..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/Transfer/SelectNFTList.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { memo, useMemo } from 'react' -import { Box, ImageList, Typography, ImageListItem, Stack } from '@mui/material' -import { MaskColorVar, LoadingBase } from '@masknet/theme' -import type { NonFungibleToken } from '@masknet/web3-shared-base' -import type { ChainId, SchemaType } from '@masknet/web3-shared-evm' -import { useDashboardI18N } from '../../../../locales/index.js' -import { NFTCard } from './NFTCard.js' - -interface SelectNFTListProps { - list: Array> - selectedTokenId: string - loading: boolean - error?: boolean - onSelect(tokenId: string): void -} - -export const SelectNFTList = memo(({ list, onSelect, selectedTokenId, loading, error }) => { - const t = useDashboardI18N() - - const renderStatus = useMemo(() => { - if (loading) { - return ( - - - - - - ) - } - - return ( - - - - {t.wallets_collectible_load_end()} - - - - ) - }, [loading]) - - return ( - - theme.palette.mode === 'dark' ? MaskColorVar.lightBackground : MaskColorVar.normalBackground, - }}> - - {list.map((token) => ( - - ))} - {renderStatus} - - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/Transfer/TransferERC20.tsx b/packages/mask/dashboard/pages/Wallets/components/Transfer/TransferERC20.tsx deleted file mode 100644 index 3111f86d5716..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/Transfer/TransferERC20.tsx +++ /dev/null @@ -1,423 +0,0 @@ -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useAsync, useAsyncFn, useUpdateEffect } from 'react-use' -import { useLocation, useNavigate } from 'react-router-dom' -import { BigNumber } from 'bignumber.js' -import { useContainer } from 'unstated-next' -import { Tune as TuneIcon } from '@mui/icons-material' -import { Box, Button, IconButton, Link, Popover, Stack, Typography } from '@mui/material' -import { Icons } from '@masknet/icons' -import { MaskColorVar, MaskTextField, ShadowRootTooltip, makeStyles } from '@masknet/theme' -import { useGasLimit } from '@masknet/web3-hooks-evm' -import { - useFungibleTokenBalance, - useGasPrice, - useLookupAddress, - useNetworkDescriptor, - useWeb3Others, - useNativeToken, - useNativeTokenPrice, - useMaskTokenAddress, - useWallet, - useWeb3Connection, -} from '@masknet/web3-hooks-base' -import { FormattedAddress, SelectFungibleTokenModal, TokenAmountPanel } from '@masknet/shared' -import { DashboardRoutes, NetworkPluginID } from '@masknet/shared-base' -import { ChainResolver, DepositPaymaster, SmartPayBundler } from '@masknet/web3-providers' -import { - TokenType, - type FungibleToken, - isGreaterThan, - isZero, - multipliedBy, - rightShift, - isSameAddress, - minus, - toFixed, -} from '@masknet/web3-shared-base' -import { - SchemaType, - formatWeiToEther, - type ChainId, - isValidDomain, - isValidAddress, - NetworkType, - isNativeTokenAddress, - addGasMargin, - type EIP1559GasConfig, -} from '@masknet/web3-shared-evm' -import { useDashboardI18N } from '../../../../locales/index.js' -import { useGasConfig } from '../../hooks/useGasConfig.js' -import { Context } from '../../hooks/useContext.js' -import { TransferTab } from './types.js' - -export interface TransferERC20Props { - token: FungibleToken -} - -const GAS_LIMIT = 21000 - -const useStyles = makeStyles()((theme) => { - return { - tooltip: { - backgroundColor: theme.palette.maskColor.publicMain, - color: theme.palette.maskColor.white, - }, - arrow: { - color: theme.palette.maskColor.publicMain, - }, - } -}) - -export const TransferERC20 = memo(({ token }) => { - const t = useDashboardI18N() - const wallet = useWallet() - const anchorEl = useRef(null) - const navigate = useNavigate() - const { state } = useLocation() as { - state: { - token?: FungibleToken - } | null - } - const [amount, setAmount] = useState('') - const [address, setAddress] = useState('') - const [message, setMessage] = useState('') - const [popoverOpen, setPopoverOpen] = useState(false) - const [minPopoverWidth, setMinPopoverWidth] = useState(0) - - const { classes } = useStyles() - - const { data: defaultGasPrice = '0' } = useGasPrice(NetworkPluginID.PLUGIN_EVM) - - const [selectedToken, setSelectedToken] = useState(token) - - const { account, chainId, pluginID, isWalletConnectNetworkNotMatch } = useContainer(Context) - - const network = useNetworkDescriptor(pluginID, state?.token?.chainId) - const Others = useWeb3Others() - const Web3 = useWeb3Connection(pluginID, { - account, - chainId, - }) - - const is1559Supported = useMemo(() => Others?.chainResolver.isFeatureSupported(chainId, 'EIP1559'), [chainId]) - useEffect(() => { - token.chainId === chainId - ? setSelectedToken(token) - : setSelectedToken(ChainResolver.nativeCurrency(chainId as ChainId)) - }, [token, chainId]) - - // workaround: transferERC20 should support non-evm network - const isNativeToken = isNativeTokenAddress(selectedToken.address) - - const { data: nativeToken } = useNativeToken(pluginID, { chainId }) - const { data: nativeTokenPrice = 0 } = useNativeTokenPrice(pluginID, { chainId }) - - // balance - const { data: tokenBalance = '0', refetch: tokenBalanceRetry } = useFungibleTokenBalance( - pluginID, - selectedToken?.address ?? '', - { chainId }, - ) - - // #region resolve ENS domain - const { - value: registeredAddress = '', - error: resolveDomainError, - loading: resolveDomainLoading, - } = useLookupAddress(pluginID, address, chainId) - // #endregion - - // transfer amount - const transferAmount = rightShift(amount || '0', selectedToken.decimals).toFixed() - const { data: erc20GasLimit = 0 } = useGasLimit( - selectedToken.type === TokenType.Fungible - ? selectedToken.symbol === nativeToken?.symbol - ? SchemaType.Native - : SchemaType.ERC20 - : selectedToken.schema, - selectedToken.address, - transferAmount, - isValidAddress(address) ? address : registeredAddress, - ) - const gasLimit_ = isNativeToken ? GAS_LIMIT : erc20GasLimit - const { gasConfig, onCustomGasSetting, gasLimit, maxFee } = useGasConfig(gasLimit_, GAS_LIMIT) - - const gasPrice = gasConfig.gasPrice || defaultGasPrice - - const gasFee = useMemo(() => { - const price = is1559Supported && maxFee ? new BigNumber(maxFee) : gasPrice - return multipliedBy(gasLimit, price) - }, [gasLimit, gasPrice, maxFee, is1559Supported]) - const gasFeeInUsd = formatWeiToEther(gasFee).multipliedBy(nativeTokenPrice) - - // #region hack for smartPay, will be removed - const maskTokenAddress = useMaskTokenAddress() - - const { value: smartPayConfig } = useAsync(async () => { - const smartPayChainId = await SmartPayBundler.getSupportedChainId() - const depositPaymaster = new DepositPaymaster(smartPayChainId) - const ratio = await depositPaymaster.getRatio() - - return { - ratio, - smartPayChainId, - } - }, []) - - const actualBalance = useMemo(() => { - if ( - !wallet?.owner || - chainId !== smartPayConfig?.smartPayChainId || - !isSameAddress(selectedToken?.address, maskTokenAddress) - ) - return tokenBalance - - return toFixed( - minus( - tokenBalance, - - new BigNumber((gasConfig as EIP1559GasConfig).maxFeePerGas) - .multipliedBy(!isZero(gasLimit) ? addGasMargin(gasLimit) : '200000') - .integerValue() - .multipliedBy(smartPayConfig?.ratio ?? 1), - ), - 0, - ) - }, [gasConfig, wallet, selectedToken?.address, maskTokenAddress, smartPayConfig, chainId, tokenBalance, gasLimit]) - // #endregion - - const maxAmount = useMemo(() => { - const price = is1559Supported && maxFee ? new BigNumber(maxFee) : gasPrice - const gasFee = multipliedBy(gasLimit, price) - - let amount_ = new BigNumber(actualBalance || '0') - amount_ = selectedToken.schema === SchemaType.Native ? amount_.minus(gasFee) : amount_ - return BigNumber.max(0, amount_).toFixed() - }, [actualBalance, gasPrice, selectedToken?.type, amount, gasLimit, maxFee, is1559Supported]) - - const [{ loading: isTransferring }, transferCallback] = useAsyncFn(async () => { - if (!selectedToken.address) return - let recipient: string | undefined - - if (isValidAddress(address)) recipient = address - else if (Others.isValidDomain(address)) recipient = registeredAddress - if (!recipient) return - - const totalAmount = rightShift(amount, token.decimals).toFixed() - return Web3.transferFungibleToken(selectedToken.address, recipient, totalAmount, '') - }, [account, selectedToken.address, token?.decimals, amount, Web3, address, registeredAddress]) - - const onTransfer = useCallback(async () => { - const hash = transferCallback() - if (typeof hash === 'string') { - setMessage('') - setAddress('') - setAmount('') - tokenBalanceRetry() - } - }, [transferCallback]) - - // #region validation - const validationMessage = useMemo(() => { - if (!transferAmount || isZero(transferAmount)) return t.wallets_transfer_error_amount_absence() - if (isGreaterThan(rightShift(amount, selectedToken.decimals), maxAmount)) - return t.wallets_transfer_error_insufficient_balance({ symbol: selectedToken.symbol ?? '' }) - if (!address) return t.wallets_transfer_error_address_absence() - if (!(isValidAddress(address) || isValidDomain(address))) return t.wallets_transfer_error_invalid_address() - if (isValidDomain(address) && (resolveDomainError || !registeredAddress)) { - if (network?.type !== NetworkType.Ethereum) return t.wallet_transfer_error_no_ens_support() - return t.wallet_transfer_error_no_address_has_been_set_name() - } - return '' - }, [ - transferAmount, - maxAmount, - address, - tokenBalance, - selectedToken, - amount, - registeredAddress, - resolveDomainError, - network, - Others, - ]) - // #endregion - - const ensContent = useMemo(() => { - if (resolveDomainLoading) return - if (registeredAddress) { - return ( - - - - {address} - - - - - - - - ) - } - - if (address.includes('.eth')) { - if (network?.type !== NetworkType.Ethereum) { - return ( - - - {t.wallet_transfer_error_no_ens_support()} - - - ) - } - if (Others.isValidDomain(address) && resolveDomainError) { - return ( - - - {t.wallet_transfer_error_no_address_has_been_set_name()} - - - ) - } - } - - return - }, [ - registeredAddress, - address, - Others.isValidDomain, - MaskColorVar, - resolveDomainError, - network?.type, - resolveDomainLoading, - ]) - - useUpdateEffect(() => { - setPopoverOpen(!!ensContent && !!anchorEl.current) - }, [ensContent]) - - return ( - - - - { - if (!anchorEl.current) anchorEl.current = event.currentTarget - if (ensContent) setPopoverOpen(true) - setMinPopoverWidth(event.currentTarget.clientWidth) - }, - spellCheck: false, - }} - onChange={(e) => setAddress(e.currentTarget.value)} - label={t.wallets_transfer_to_address()} - /> - - setPopoverOpen(false)} - PaperProps={{ - style: { minWidth: `${minPopoverWidth}px`, borderRadius: 4 }, - }} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - open={popoverOpen}> - {ensContent} - - - - { - const picked = await SelectFungibleTokenModal.openAndWaitForClose({ - disableNativeToken: false, - chainId, - pluginID: NetworkPluginID.PLUGIN_EVM, - }) - if (!picked) return - setSelectedToken(picked as FungibleToken) - // Update the previous location state of the token. - navigate(DashboardRoutes.WalletsTransfer, { - state: { type: TransferTab.Token, token: picked }, - }) - }, - }, - }} - /> - - - - {t.gas_fee()} - - - - {t.transfer_cost({ - gasFee: formatWeiToEther(gasFee).toFixed(6), - symbol: nativeToken?.symbol ?? '', - usd: gasFeeInUsd.toFixed(2), - })} - - - - - - - {isNativeToken ? ( - - setMessage(e.currentTarget.value)} - label={t.wallets_transfer_memo()} - /> - - ) : null} - - {isWalletConnectNetworkNotMatch ? ( - -
- -
-
- ) : ( - - )} -
-
-
- ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/Transfer/TransferERC721.tsx b/packages/mask/dashboard/pages/Wallets/components/Transfer/TransferERC721.tsx deleted file mode 100644 index 60f5a6638bd1..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/Transfer/TransferERC721.tsx +++ /dev/null @@ -1,452 +0,0 @@ -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useAsync, useAsyncFn, useUpdateEffect } from 'react-use' -import { useNavigate, useLocation } from 'react-router-dom' -import { unionBy } from 'lodash-es' -import { z } from 'zod' -import { Controller, useForm } from 'react-hook-form' -import { Icons } from '@masknet/icons' -import { makeStyles, MaskColorVar, MaskTextField, ShadowRootTooltip } from '@masknet/theme' -import { Box, Button, IconButton, Link, Popover, Stack, Typography } from '@mui/material' -import { - isSameAddress, - type NonFungibleToken, - type NonFungibleTokenContract, - multipliedBy, -} from '@masknet/web3-shared-base' -import { NetworkPluginID, DashboardRoutes } from '@masknet/shared-base' -import { - SchemaType, - formatWeiToEther, - NetworkType, - type ChainId, - isValidDomain, - isValidAddress, - formatEthereumAddress, -} from '@masknet/web3-shared-evm' -import { FormattedAddress, SelectNonFungibleContractModal } from '@masknet/shared' -import { zodResolver } from '@hookform/resolvers/zod' -import { KeyboardArrowDown as KeyboardArrowDownIcon, Tune as TuneIcon } from '@mui/icons-material' -import { - useGasPrice, - useLookupAddress, - useNetworkDescriptor, - useNativeToken, - useNativeTokenPrice, - useWeb3Others, - useWeb3Connection, -} from '@masknet/web3-hooks-base' -import { useGasLimit, useNonFungibleOwnerTokens } from '@masknet/web3-hooks-evm' -import { SelectNFTList } from './SelectNFTList.js' -import { LoadingPlaceholder } from '../../../../components/LoadingPlaceholder/index.js' -import { useDashboardI18N } from '../../../../locales/index.js' -import { useGasConfig } from '../../hooks/index.js' -import { TransferTab } from './types.js' -import { Context } from '../../hooks/useContext.js' -import { useContainer } from 'unstated-next' - -const useStyles = makeStyles()((theme) => ({ - disabled: { - opacity: 1, - }, - tooltip: { - backgroundColor: theme.palette.maskColor.publicMain, - color: theme.palette.maskColor.white, - }, - arrow: { - color: theme.palette.maskColor.publicMain, - }, -})) - -type FormInputs = { - recipient: string - contract: string - tokenId: string -} - -const GAS_LIMIT = 30000 - -export const TransferERC721 = memo(() => { - const t = useDashboardI18N() - const { chainId, pluginID, isWalletConnectNetworkNotMatch, account } = useContainer(Context) - const anchorEl = useRef(null) - - const { state } = useLocation() as { - state: { - nonFungibleToken?: NonFungibleToken - type?: TransferTab - chainId?: ChainId - } | null - } - - const { classes } = useStyles() - const Others = useWeb3Others() - const [defaultToken, setDefaultToken] = useState | null>(null) - const navigate = useNavigate() - const [popoverOpen, setPopoverOpen] = useState(false) - const [recipientError, setRecipientError] = useState<{ - type: 'account' | 'contractAddress' - message: string - } | null>(null) - const [minPopoverWidth, setMinPopoverWidth] = useState(0) - const [contract, setContract] = useState>() - const network = useNetworkDescriptor() - - const { data: nativeToken } = useNativeToken(pluginID, { chainId }) - const { data: nativeTokenPrice = 0 } = useNativeTokenPrice(pluginID, { chainId }) - // form - const schema = z.object({ - recipient: z - .string() - .refine((address) => isValidAddress(address) || isValidDomain(address), t.wallets_incorrect_address()), - contract: z.string().min(1, t.wallets_collectible_contract_is_empty()), - tokenId: z.string().min(1, t.wallets_collectible_token_id_is_empty()), - }) - - const { - control, - handleSubmit, - setValue, - watch, - clearErrors, - formState: { errors, isSubmitting }, - } = useForm({ - resolver: zodResolver(schema), - defaultValues: { recipient: '', contract: '', tokenId: '' }, - }) - - const [contractAddress, setContractAddress] = useState(state?.nonFungibleToken?.address || '') - - useEffect(() => { - if ( - !state?.nonFungibleToken || - state?.nonFungibleToken.chainId !== chainId || - state?.type !== TransferTab.Collectibles - ) { - setContract(undefined) - setContractAddress('') - setValue('contract', '') - setValue('tokenId', '') - setDefaultToken(null) - return - } - - setContract(state.nonFungibleToken.contract) - setContractAddress(state.nonFungibleToken.address) - setValue('contract', state.nonFungibleToken.contract?.name ?? '') - setValue('tokenId', state.nonFungibleToken.tokenId) - setDefaultToken(state.nonFungibleToken) - }, [state, chainId]) - - const allFormFields = watch() - - // #region resolve ENS domain - const { - value: registeredAddress = '', - error: resolveDomainError, - loading: resolveDomainLoading, - } = useLookupAddress(NetworkPluginID.PLUGIN_EVM, allFormFields.recipient) - // #endregion - - // #region check contract address and account address - useAsync(async () => { - const recipient = allFormFields.recipient - setRecipientError(null) - if (!recipient && !registeredAddress) return - if (!isValidAddress(recipient) && !isValidAddress(registeredAddress)) return - clearErrors() - if (isSameAddress(recipient, account) || isSameAddress(registeredAddress, account)) { - setRecipientError({ - type: 'account', - message: t.wallets_transfer_error_same_address_with_current_account(), - }) - } - const result = await Web3.getCode(recipient) - if (result !== '0x') { - setRecipientError({ - type: 'contractAddress', - message: t.wallets_transfer_error_is_contract_address(), - }) - } - }, [allFormFields.recipient, clearErrors, registeredAddress]) - // #endregion - - const { data: erc721GasLimit } = useGasLimit( - SchemaType.ERC721, - contract?.address, - undefined, - isValidAddress(allFormFields.recipient) ? allFormFields.recipient : registeredAddress, - allFormFields.tokenId, - ) - - const gasLimit_ = erc721GasLimit ? erc721GasLimit : GAS_LIMIT - const { gasConfig, onCustomGasSetting, gasLimit } = useGasConfig(gasLimit_, GAS_LIMIT) - - const Web3 = useWeb3Connection(pluginID, { - account, - chainId, - }) - - const [{ loading: isTransferring }, transferCallback] = useAsyncFn(async () => { - if (!contractAddress) return - if (pluginID === NetworkPluginID.PLUGIN_EVM && !allFormFields.tokenId) return - return Web3.transferNonFungibleToken(contractAddress, allFormFields.tokenId ?? '', allFormFields.recipient, '1') - }, [account, allFormFields.tokenId, pluginID, contractAddress, allFormFields.recipient, Web3]) - - const handleSelectNFT = () => { - SelectNonFungibleContractModal.open({ - pluginID: NetworkPluginID.PLUGIN_EVM, - schemaType: SchemaType.ERC721, - chainId, - onSubmit: (contract) => { - setContractAddress(contract.address || '') - if (contract && defaultToken && !isSameAddress(contract.address, defaultToken.address)) { - setDefaultToken(null) - } - if (contract) { - setValue('contract', contract.name || contract.address || '', { - shouldValidate: true, - }) - setContract(contract as NonFungibleTokenContract) - setValue('tokenId', '') - } - }, - }) - } - - // gas price - const { data: defaultGasPrice = '0' } = useGasPrice() - const gasPrice = gasConfig.gasPrice || defaultGasPrice - const gasFee = useMemo(() => multipliedBy(gasLimit, gasPrice), [gasLimit, gasPrice]) - const gasFeeInUsd = formatWeiToEther(gasFee).multipliedBy(nativeTokenPrice) - - const { loading: loadingOwnerList, value: tokenDetailedOwnerList = [] } = useNonFungibleOwnerTokens( - contract?.address ?? '', - account, - chainId as ChainId, - ) - - const onTransfer = useCallback( - async (data: FormInputs) => { - let hash: string | undefined - if (isValidAddress(data.recipient)) { - hash = await transferCallback() - } else if (isValidDomain(data.recipient) && isValidAddress(registeredAddress)) { - hash = await transferCallback() - } - if (typeof hash === 'string') { - navigate(DashboardRoutes.WalletsHistory) - } - }, - [transferCallback, contract?.address, gasConfig, registeredAddress], - ) - - const ensContent = useMemo(() => { - if (resolveDomainLoading) return - if (registeredAddress) { - return ( - - - - {allFormFields.recipient} - - - - - - - - ) - } - - if (allFormFields.recipient.includes('.eth')) { - if (network?.type !== NetworkType.Ethereum) { - return ( - - - {t.wallet_transfer_error_no_ens_support()} - - - ) - } - if (isValidDomain(allFormFields.recipient) && resolveDomainError) { - return ( - - - {t.wallet_transfer_error_no_address_has_been_set_name()} - - - ) - } - } - return - }, [allFormFields.recipient, resolveDomainError, resolveDomainLoading, network, registeredAddress]) - - useUpdateEffect(() => { - setPopoverOpen(!!ensContent && !!anchorEl.current) - }, [ensContent]) - - const contractIcon = useMemo(() => { - if (!contract?.logoURL) return null - return ( - - - - ) - }, [contract]) - - return ( - -
- - - ( - setValue('recipient', e.currentTarget.value)} - helperText={errors.recipient?.message || recipientError?.message} - error={ - !!errors.recipient || - (!!recipientError && recipientError.type === 'contractAddress') - } - value={field.field.value} - InputProps={{ - onClick: (event) => { - if (!anchorEl.current) anchorEl.current = event.currentTarget - if (ensContent) setPopoverOpen(true) - setMinPopoverWidth(event.currentTarget.clientWidth) - }, - spellCheck: false, - }} - label={t.wallets_transfer_to_address()} - /> - )} - name="recipient" - /> - setPopoverOpen(false)} - PaperProps={{ - style: { minWidth: `${minPopoverWidth}px`, borderRadius: 4 }, - }} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - open={popoverOpen}> - {ensContent} - - - - ( - - , - }} - inputProps={{ - sx: { cursor: 'pointer' }, - }} - label={t.wallets_transfer_contract()} - value={field.field.value} - /> - - )} - name="contract" - /> - - {loadingOwnerList && tokenDetailedOwnerList.length === 0 ? ( - - - - ) : null} - - {tokenDetailedOwnerList.length > 0 && !loadingOwnerList && ( - ( - setValue('tokenId', value)} - list={ - defaultToken - ? unionBy([defaultToken, ...tokenDetailedOwnerList], 'tokenId') - : tokenDetailedOwnerList - } - selectedTokenId={field.field.value} - loading={loadingOwnerList} - /> - )} - name="tokenId" - /> - )} - - - - {t.gas_fee()} - - - - {t.transfer_cost({ - gasFee: formatWeiToEther(gasFee).toFixed(6), - symbol: nativeToken?.symbol ?? '', - usd: gasFeeInUsd.toFixed(2), - })} - - - - - - - - {isWalletConnectNetworkNotMatch ? ( - -
- -
-
- ) : ( - - )} -
-
-
-
- ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/Transfer/index.tsx b/packages/mask/dashboard/pages/Wallets/components/Transfer/index.tsx deleted file mode 100644 index 73b5bdc5067d..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/Transfer/index.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { memo, useEffect } from 'react' -import { ContentContainer } from '../../../../components/ContentContainer/index.js' -import { Box, Tab } from '@mui/material' -import { useTabs } from '@masknet/theme' -import { TabContext, TabList, TabPanel } from '@mui/lab' -import { TransferERC20 } from './TransferERC20.js' -import { useLocation } from 'react-router-dom' -import { useDashboardI18N } from '../../../../locales/index.js' -import { TransferERC721 } from './TransferERC721.js' -import { TransferTab } from './types.js' -import type { FungibleToken } from '@masknet/web3-shared-base' -import type { ChainId, SchemaType } from '@masknet/web3-shared-evm' -import { useWeb3Others } from '@masknet/web3-hooks-base' -import { useContainer } from 'unstated-next' -import { Context } from '../../hooks/useContext.js' - -const assetTabs = [TransferTab.Token, TransferTab.Collectibles] as const - -export * from './types.js' - -export const Transfer = memo(() => { - const t = useDashboardI18N() - const { state } = useLocation() as { - state: { - token?: FungibleToken - nonFungibleToken?: FungibleToken - type?: TransferTab - } | null - } - const { chainId } = useContainer(Context) - const Others = useWeb3Others() - const nativeToken = Others.createNativeToken(chainId) - const transferTabsLabel: Record = { - [TransferTab.Token]: t.wallets_assets_token(), - [TransferTab.Collectibles]: t.wallets_assets_collectibles(), - } - const [currentTab, onChange, , setTab] = useTabs(TransferTab.Token, TransferTab.Collectibles) - - useEffect(() => { - if (!state) return - if (!state.nonFungibleToken || state.type !== TransferTab.Collectibles) return - - setTab(TransferTab.Collectibles) - }, [state]) - - if (!nativeToken && !state?.token) return null - - return ( - - - - - {assetTabs.map((key) => ( - - ))} - - - - - - - - - - - ) -}) diff --git a/packages/mask/dashboard/pages/Wallets/components/Transfer/types.ts b/packages/mask/dashboard/pages/Wallets/components/Transfer/types.ts deleted file mode 100644 index 61e77868c144..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/Transfer/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum TransferTab { - Token = 'Token', - Collectibles = 'Collectibles', -} diff --git a/packages/mask/dashboard/pages/Wallets/components/WalletStateBar/index.tsx b/packages/mask/dashboard/pages/Wallets/components/WalletStateBar/index.tsx deleted file mode 100644 index 93fdf9957339..000000000000 --- a/packages/mask/dashboard/pages/Wallets/components/WalletStateBar/index.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { memo } from 'react' -import { Box, Button, Stack, Typography } from '@mui/material' -import { ProviderType } from '@masknet/web3-shared-evm' -import { makeStyles, MaskColorVar, LoadingBase } from '@masknet/theme' -import { FormattedAddress, WalletIcon, SelectProviderModal, WalletStatusModal } from '@masknet/shared' -import { - useNetworkDescriptor, - useProviderDescriptor, - useWallet, - useReverseAddress, - useRecentTransactions, - useChainContext, -} from '@masknet/web3-hooks-base' -import { Others } from '@masknet/web3-providers' -import { NetworkPluginID } from '@masknet/shared-base' -import { TransactionStatusType } from '@masknet/web3-shared-base' -import type { Web3Helper } from '@masknet/web3-helpers' -import { useDashboardI18N } from '../../../../locales/index.js' - -const useStyles = makeStyles()((theme) => ({ - bar: { - minWidth: 80, - lineHeight: '28px', - height: '28px', - cursor: 'pointer', - position: 'relative', - '&::after': { - borderRadius: 30, - pointerEvents: 'none', - content: '""', - inset: 0, - margin: 'auto', - position: 'absolute', - backgroundColor: 'var(--network-icon-color, transparent)', - opacity: 0.1, - zIndex: 0, - }, - '& > span': { - position: 'relative', - zIndex: 1, - }, - }, - dot: { - position: 'relative', - top: 0, - display: 'inline-block', - marginRight: theme.spacing(0.8), - lineHeight: '28px', - width: 10, - height: 10, - borderRadius: 5, - }, - domain: { - fontSize: 14, - marginLeft: 20, - background: theme.palette.mode === 'dark' ? 'rgba(73, 137, 255, 0.2)' : 'rgba(28, 104, 243, 0.1)', - padding: '2px 8px', - borderRadius: 4, - }, -})) - -export const WalletStateBar = memo(() => { - const t = useDashboardI18N() - - const { account } = useChainContext() - const wallet = useWallet() - const networkDescriptor = useNetworkDescriptor() - const providerDescriptor = useProviderDescriptor() - const pendingTransactions = useRecentTransactions(NetworkPluginID.PLUGIN_EVM, TransactionStatusType.NOT_DEPEND) - - const { data: domain } = useReverseAddress(NetworkPluginID.PLUGIN_EVM, account) - - if (!account) { - return - } - return ( - - ) -}) - -interface WalletStateBarUIProps { - isPending: boolean - network?: Web3Helper.NetworkDescriptorAll - provider?: Web3Helper.ProviderDescriptorAll - name?: string - address?: string - domain?: string | null - openConnectWalletDialog(): void -} - -export function WalletStateBarUI({ - isPending, - network, - provider, - name, - address, - domain, - openConnectWalletDialog, -}: WalletStateBarUIProps) { - const t = useDashboardI18N() - const { classes } = useStyles() - - if (!network || !provider) return null - - return ( - - - - - {network.name} - - - {isPending ? ( - - - - {t.wallet_transactions_pending()} - - - ) : null} - - - - - - {provider.type !== ProviderType.MaskWallet ? ( - - {domain ? Others.formatDomainName(domain) : provider.name} - - ) : ( - - {name} - {domain ? ( - {Others.formatDomainName(domain)} - ) : null} - - )} - - - - - - - ) -} diff --git a/packages/mask/dashboard/pages/Wallets/hooks/index.ts b/packages/mask/dashboard/pages/Wallets/hooks/index.ts deleted file mode 100644 index 4106243ea699..000000000000 --- a/packages/mask/dashboard/pages/Wallets/hooks/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './useGasConfig.js' -export * from './useIsMatched.js' diff --git a/packages/mask/dashboard/pages/Wallets/hooks/useContext.ts b/packages/mask/dashboard/pages/Wallets/hooks/useContext.ts deleted file mode 100644 index 8f4e1f9c8a4d..000000000000 --- a/packages/mask/dashboard/pages/Wallets/hooks/useContext.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { createContainer } from 'unstated-next' -import { NetworkPluginID, EMPTY_LIST } from '@masknet/shared-base' -import { - useFungibleAssets, - useChainContext, - useFungibleTokensFromTokenList, - useWeb3Others, -} from '@masknet/web3-hooks-base' -import type { Web3Helper } from '@masknet/web3-helpers' -import { isNativeTokenAddress, ProviderType } from '@masknet/web3-shared-evm' -import { isSameAddress } from '@masknet/web3-shared-base' -import { useMemo } from 'react' - -function useContext(initialState?: { - account?: string - chainId?: Web3Helper.ChainIdAll - setSelectedNetwork?: (x: Web3Helper.NetworkDescriptorAll | null) => void - pluginID?: NetworkPluginID - connectedChainId?: Web3Helper.ChainIdAll -}) { - const { account, chainId, providerType } = useChainContext({ - account: initialState?.account, - chainId: initialState?.chainId, - }) - const Others = useWeb3Others(initialState?.pluginID) - const fungibleAssets = useFungibleAssets<'all'>(initialState?.pluginID, undefined, { - account, - chainId, - }) - const { value: fungibleTokens = EMPTY_LIST, loading } = useFungibleTokensFromTokenList(initialState?.pluginID, { - chainId, - }) - - const assets = useMemo(() => { - if (!fungibleAssets?.data) return EMPTY_LIST - return fungibleAssets.data.map((x) => { - if (isNativeTokenAddress(x.address)) - return { ...x, logoURL: Others.chainResolver.nativeCurrency(x.chainId)?.logoURL || x.logoURL } - const token = fungibleTokens.find((y) => isSameAddress(x.address, y.address) && x.chainId === y.chainId) - if (!token?.logoURL) return x - return { ...x, logoURL: token.logoURL } - }) - }, [fungibleAssets.data, fungibleTokens, Others.chainResolver.nativeCurrency]) - return { - account, - chainId, - setSelectedNetwork: initialState?.setSelectedNetwork, - isWalletConnectNetworkNotMatch: - (providerType === ProviderType.WalletConnect || providerType === ProviderType.WalletConnectV2) && - initialState?.connectedChainId !== chainId, - pluginID: initialState?.pluginID ?? NetworkPluginID.PLUGIN_EVM, - fungibleAssets: { ...fungibleAssets, data: assets, isLoading: loading || fungibleAssets.isLoading }, - } -} - -export const Context = createContainer(useContext) -Context.Provider.displayName = 'FungibleAssetsProvider' diff --git a/packages/mask/dashboard/pages/Wallets/hooks/useGasConfig.ts b/packages/mask/dashboard/pages/Wallets/hooks/useGasConfig.ts deleted file mode 100644 index a005024843c6..000000000000 --- a/packages/mask/dashboard/pages/Wallets/hooks/useGasConfig.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { useEffect, useMemo, useState } from 'react' -import { toHex } from 'web3-utils' -import { BigNumber } from 'bignumber.js' -import { ChainResolver } from '@masknet/web3-providers' -import { type GasConfig } from '@masknet/web3-shared-evm' -import { useChainContext, useGasOptions, useGasPrice } from '@masknet/web3-hooks-base' -import { GasOptionType } from '@masknet/web3-shared-base' -import { NetworkPluginID } from '@masknet/shared-base' -import { GasSettingModal } from '@masknet/shared' - -interface GasConfigProps { - gasConfig: GasConfig - gasLimit: number - maxFee: BigNumber.Value - onCustomGasSetting: () => ReturnType -} - -export function useGasConfig(gasLimit: number, minGasLimit: number): GasConfigProps { - const { chainId } = useChainContext() - - const [gasLimit_, setGasLimit_] = useState(0) - const [customGasPrice, setCustomGasPrice] = useState(0) - const [gasOption, setGasOption] = useState(GasOptionType.NORMAL) - const [maxFee, setMaxFee] = useState(0) - const [priorityFee, setPriorityFee] = useState(0) - - const is1559Supported = useMemo(() => ChainResolver.isFeatureSupported(chainId, 'EIP1559'), [chainId]) - const { data: defaultGasPrice = '0' } = useGasPrice(NetworkPluginID.PLUGIN_EVM) - const gasPrice = customGasPrice || defaultGasPrice - const { data: gasOptions } = useGasOptions(NetworkPluginID.PLUGIN_EVM) - - useEffect(() => GasSettingModal.close(), []) - - useEffect(() => { - setGasLimit_(gasLimit) - }, [gasLimit]) - - useEffect(() => { - if (!gasOptions) return - - if (is1559Supported) { - const gasLevel = gasOptions.normal - setMaxFee((oldVal) => { - return !oldVal ? gasLevel?.suggestedMaxFeePerGas ?? '0' : oldVal - }) - setPriorityFee((oldVal) => { - return !oldVal ? gasLevel?.suggestedMaxPriorityFeePerGas ?? '0' : oldVal - }) - } else { - setCustomGasPrice((oldVal) => (!oldVal ? gasOptions.normal.suggestedMaxFeePerGas : oldVal)) - } - }, [is1559Supported, gasOptions?.normal]) - - useEffect(() => { - if (!gasOptions) return - if (is1559Supported) { - const gasLevel = gasOptions.normal - setMaxFee(gasLevel?.suggestedMaxFeePerGas ?? 0) - setPriorityFee(gasLevel?.suggestedMaxPriorityFeePerGas ?? 0) - } else { - setCustomGasPrice(gasOptions.normal.suggestedMaxFeePerGas) - } - }, [chainId, gasOptions?.normal]) - - const gasConfig: GasConfig = useMemo(() => { - return is1559Supported - ? { - gas: toHex(gasLimit_), - maxFeePerGas: toHex(new BigNumber(maxFee).integerValue().toFixed()), - maxPriorityFeePerGas: toHex(new BigNumber(priorityFee).integerValue().toFixed()), - } - : { gas: toHex(gasLimit_), gasPrice: toHex(new BigNumber(gasPrice).toString()) } - }, [is1559Supported, gasLimit_, maxFee, priorityFee, gasPrice, chainId]) - - return { - gasConfig, - gasLimit: gasLimit_, - maxFee, - onCustomGasSetting: async () => - GasSettingModal.openAndWaitForClose({ gasLimit: gasLimit_, gasOption, minGasLimit }), - } -} diff --git a/packages/mask/dashboard/pages/Wallets/hooks/useIsMatched.ts b/packages/mask/dashboard/pages/Wallets/hooks/useIsMatched.ts deleted file mode 100644 index 1556206c8a1c..000000000000 --- a/packages/mask/dashboard/pages/Wallets/hooks/useIsMatched.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { useMatch } from 'react-router-dom' - -export function useIsMatched(...args: Parameters) { - const match = useMatch(...args) - return match !== null -} diff --git a/packages/mask/dashboard/pages/Wallets/index.tsx b/packages/mask/dashboard/pages/Wallets/index.tsx deleted file mode 100644 index 9991026a7ea2..000000000000 --- a/packages/mask/dashboard/pages/Wallets/index.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { useEffect, useMemo, useState, useCallback } from 'react' -import { Route, Routes, useLocation, useNavigate } from 'react-router-dom' -import { getRegisteredWeb3Networks } from '@masknet/plugin-infra' -import { PluginTransakMessages } from '@masknet/plugin-transak' -import type { Web3Helper } from '@masknet/web3-helpers' -import { useChainContext, useNetworkDescriptor, useNetworkContext, useWallets } from '@masknet/web3-hooks-base' -import { DashboardRoutes, relativeRouteOf, CrossIsolationMessages, NetworkPluginID } from '@masknet/shared-base' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { ChainId, type SchemaType } from '@masknet/web3-shared-evm' -import { ChainResolver } from '@masknet/web3-providers' -import type { FungibleToken, NonFungibleToken } from '@masknet/web3-shared-base' -import { useDashboardI18N } from '../../locales/index.js' -import { PageFrame } from '../../components/PageFrame/index.js' -import { Assets } from './components/Assets/index.js' -import { Balance } from './components/Balance/index.js' -import { History } from './components/History/index.js' -import { ReceiveDialog } from './components/ReceiveDialog/index.js' -import { Transfer, TransferTab } from './components/Transfer/index.js' -import { WalletStateBar } from './components/WalletStateBar/index.js' -import { useIsMatched } from './hooks/index.js' -import { Context } from './hooks/useContext.js' -import { StartUp } from './StartUp.js' - -const r = relativeRouteOf(DashboardRoutes.Wallets) - -function Wallets() { - const t = useDashboardI18N() - const navigate = useNavigate() - const { account, chainId } = useChainContext() - const wallets = useWallets() - - const { pathname, state } = useLocation() as { - state: { - token?: FungibleToken - nonFungibleToken?: NonFungibleToken - type?: TransferTab - } | null - pathname: string - } - const isWalletPath = useIsMatched(DashboardRoutes.Wallets) - const isWalletTransferPath = useIsMatched(DashboardRoutes.WalletsTransfer) - const isWalletHistoryPath = useIsMatched(DashboardRoutes.WalletsHistory) - - const [receiveOpen, setReceiveOpen] = useState(false) - - const { pluginID } = useNetworkContext() - const networks = getRegisteredWeb3Networks(pluginID).filter((x) => x.isMainnet) - - // If show one network only, set it as default network - const defaultNetwork = networks.length !== 1 ? null : networks[0] - - const networkDescriptor = useNetworkDescriptor( - NetworkPluginID.PLUGIN_EVM, - (state?.type === TransferTab.Token ? state?.token?.chainId : state?.nonFungibleToken?.chainId) ?? - ChainId.Mainnet, - ) - const [selectedNetwork, setSelectedNetwork] = useState( - networkDescriptor ?? null, - ) - - const { openDialog: openBuyDialog } = useRemoteControlledDialog(PluginTransakMessages.buyTokenDialogUpdated) - - const openSwapDialog = useCallback(() => { - CrossIsolationMessages.events.swapDialogEvent.sendToLocal({ - open: true, - }) - }, []) - - useEffect(() => { - if (isWalletPath) return - setSelectedNetwork(networkDescriptor || defaultNetwork) - }, [isWalletPath, networkDescriptor, defaultNetwork]) - - useEffect(() => { - if (isWalletTransferPath || isWalletHistoryPath) { - setSelectedNetwork(networkDescriptor || defaultNetwork) - return - } - setSelectedNetwork(defaultNetwork) - }, [pathname, defaultNetwork]) - - const pateTitle = useMemo(() => { - if (!account && wallets.length === 0) return t.create_wallet_form_title() - if (isWalletPath) return t.wallets_assets() - if (isWalletTransferPath) return t.wallets_transfer() - if (isWalletHistoryPath) return t.wallets_history() - - return t.wallets() - }, [isWalletPath, isWalletHistoryPath, isWalletTransferPath, account, wallets.length]) - - return ( - }> - {!account ? ( - - ) : ( - - - navigate(DashboardRoutes.WalletsTransfer, { - state: { - type: TransferTab.Token, - token: ChainResolver.nativeCurrency( - (selectedNetwork?.chainId ?? chainId) as ChainId, - ), - }, - }) - } - onBuy={openBuyDialog} - onSwap={openSwapDialog} - onReceive={() => setReceiveOpen(true)} - networks={networks} - selectedNetwork={selectedNetwork} - onSelectNetwork={setSelectedNetwork} - showOperations={pluginID === NetworkPluginID.PLUGIN_EVM} - /> - - } /> - } /> - } - /> - - - )} - {account ? ( - setReceiveOpen(false)} /> - ) : null} - - ) -} - -export default function () { - return -} diff --git a/packages/mask/dashboard/pages/routes.tsx b/packages/mask/dashboard/pages/routes.tsx index ea16ba83f90c..8cc90a8f5569 100644 --- a/packages/mask/dashboard/pages/routes.tsx +++ b/packages/mask/dashboard/pages/routes.tsx @@ -1,23 +1,15 @@ -import React, { lazy, Suspense } from 'react' -import { Route, Routes, Navigate, HashRouter } from 'react-router-dom' -import { useCustomSnackbar } from '@masknet/theme' +import { lazy, Suspense } from 'react' +import { Route, Routes, HashRouter } from 'react-router-dom' import { DashboardRoutes } from '@masknet/shared-base' -import { useDashboardI18N } from '../locales/index.js' import { TermsGuard } from './TermsGuard.js' -import { DashboardFrame } from '../components/DashboardFrame/index.js' import { Modals } from '../modals/index.js' const SetupPersona = lazy(() => import(/* webpackPrefetch: true */ './SetupPersona/index.js')) -const Wallets = lazy(() => import(/* webpackPrefetch: true */ './Wallets/index.js')) const SignUp = lazy(() => import('./SignUp/index.js')) const PrivacyPolicy = lazy(() => import('./PrivacyPolicy/index.js')) - const CreateWallet = lazy(() => import('./CreateMaskWallet/index.js')) export function Pages() { - const t = useDashboardI18N() - const { showSnackbar } = useCustomSnackbar() - return ( @@ -26,9 +18,7 @@ export function Pages() { } /> } /> } /> - )} /> } /> - } /> @@ -36,7 +26,3 @@ export function Pages() { ) } - -function frame(x: React.ReactNode) { - return -} diff --git a/packages/mask/dashboard/type.ts b/packages/mask/dashboard/type.ts index e314f6ff448f..f1079880f255 100644 --- a/packages/mask/dashboard/type.ts +++ b/packages/mask/dashboard/type.ts @@ -1,3 +1,4 @@ +export { BackupAccountType as AccountType } from '@masknet/shared-base' export interface BackupFileInfo { downloadURL: string size: number @@ -6,11 +7,6 @@ export interface BackupFileInfo { abstract: string } -export enum AccountType { - Email = 'email', - Phone = 'phone', -} - export enum Scenario { backup = 'backup', create = 'create_binding', diff --git a/packages/mask/dashboard/utils/regexp.ts b/packages/mask/dashboard/utils/regexp.ts index 1655b0a07ef3..6dc401584d23 100644 --- a/packages/mask/dashboard/utils/regexp.ts +++ b/packages/mask/dashboard/utils/regexp.ts @@ -1,5 +1,5 @@ export const passwordRegexp = /^(?=.{8,20}$)(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^\dA-Za-z]).*/ -export const emailRegexp = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/ +export const emailRegexp = /^([\w!#$%&*+./=?^`{|}~’-]{1,64}@([\dA-Za-z-]{1,255}.[\dA-Za-z-]{2,}))$/ export const phoneRegexp = /^(\+?([ .-])?\d{1,2}([ .-])?)?(\(?\d{3}\)?|\d{3})([ .-])?(\d{3}([ .-])?\d{4})/ diff --git a/packages/mask/public/js/trusted-types.js b/packages/mask/public/js/trusted-types.js index 0f1ca529bf6d..6c6b0ea230d1 100644 --- a/packages/mask/public/js/trusted-types.js +++ b/packages/mask/public/js/trusted-types.js @@ -34,10 +34,6 @@ if (typeof trustedTypes === 'object' && location.protocol.includes('extension')) return html }, }) - - if (location.pathname !== '/popups.html') { - trustedTypes.createPolicy('ssr', {}) - } } undefined diff --git a/packages/mask/shared-ui/hooks/useUserContext.ts b/packages/mask/shared-ui/hooks/useUserContext.ts index 046488483602..b16b15b34668 100644 --- a/packages/mask/shared-ui/hooks/useUserContext.ts +++ b/packages/mask/shared-ui/hooks/useUserContext.ts @@ -1,5 +1,5 @@ import { PersistentStorages, type BackupConfig } from '@masknet/shared-base' -import { useCallback } from 'react' +import { useCallback, useMemo } from 'react' import { createContainer } from 'unstated-next' import { useSubscription } from 'use-subscription' @@ -11,17 +11,27 @@ function useUserContext() { await PersistentStorages.Settings.storage.backupConfig.setValue({ ...user, ...obj, - backupPassword: btoa(obj.backupPassword ?? ''), + backupPassword: obj.backupPassword ? btoa(obj.backupPassword) : user.backupPassword, }) }, [user], ) + const result = useMemo(() => { + try { + const backupPassword = user.backupPassword && atob(user.backupPassword) + return { + ...user, + backupPassword, + } + } catch { + // Maybe `backupPassword` is not base64-encoded. + return user + } + }, [user, updateUser]) + return { - user: { - ...user, - backupPassword: user.backupPassword && atob(user.backupPassword), - }, + user: result, updateUser, } } diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index 93c404f4a53e..0b06e90c35ae 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -26,6 +26,8 @@ "coming_soon": "Coming soon", "max": "Max", "data": "Data", + "redpacket_a_token": "a token", + "redpacket_an_nft": "an NFT", "available_balance": "Available Balance", "available_amount": "{{- amount}} available", "failed_to_transfer_token": "Failed to transfer token: {{- message}}", @@ -90,21 +92,7 @@ "application_settings_tab_plug_app-listed-placeholder": "Click the application icon to hide in the APP list.", "application_display_tab_plug_app-unlisted-placeholder": "Click the setting icon to list it on the App board.", "application_settings_tab_plug_app-unlisted-placeholder": "Click the application icon to list it on the App board.", - "applications_persona_title": "Persona", - "applications_persona_copy": "Copy Success", - "applications_persona_connect": "Connect {{-nickname}}", - "applications_persona_connect_hint": "Please connect {{-nickname}} before using dApps.", - "applications_persona_verify": "Verify {{-nickname}}", - "applications_persona_verify_hint": "Please verify current persona with social media account before using dApps.", - "applications_persona_verify_connect": "Connect and Verify {{-nickname}}", - "applications_persona_verify_connect_hint": "Please connect {{-nickname}} and send proof post before using dApps.", "persona_load_failed": "Load failed", - "enable_plugin_boundary": "Enable plugin", - "enable_plugin_boundary_description": "This function has been turned off in the App settings. Enable plug-ins to fully access.", - "close_check_security": "Close [Check Security]?", - "check_security_intro": "The [Check Security] dApp provides quick, reliable, and convenient Web3 security services.", - "check_security_close_warning": "If you decide to close [Check Security], you will no longer see security notifications when interacting with suspicious, blacklisted, or potentially fraudulent contracts and addresses.", - "check_security_close_advice": "We recommend new Web3 users to keep [Check Security] open. ", "additional_post_box__encrypted_post_pre": "Decrypt this post with #mask_io! $t(promote) {{encrypted}}", "additional_post_box__encrypted_post_pre_red_packet_sns_official_account": "$t(promote_red_packet) Follow @{{account}} for Web3 updates and insights. \n\n🧧🧧🧧 Try sending Lucky Drop to your friends with Mask.io. \n\n{{encrypted}}", "additional_post_box__encrypted_post_pre_red_packet": "$t(promote_red_packet) \n\n🧧🧧🧧 Try sending Lucky Drop to your friends with Mask.io. \n\n{{encrypted}}", @@ -273,7 +261,7 @@ "export": "Export", "confirm_swap_risk": "Confirm swap risk", "wallet_load_retry": "Failed to load {{symbol}}. Click to retry.", - "wallet_name": "Wallet Name", + "name": "Name", "wallet_rename": "Rename Wallet", "wallet_loading_nft_contract": "Loading NFT contract...", "wallet_search_contract_no_result": "No results or contract address does not meet the query criteria.", @@ -438,7 +426,7 @@ "plugin_web3_profile_recommend_feature_description": "Optimize your social profile by customizing the display of Web3 features.", "plugin_nft_avatar_recommend_feature_description": "Set your NFT as profile picture with exclusive aura.", "application_hint": "Socialize and show off your NFTs. People can bid, buy and view your valuable NFTs without leaving Twitter.", - "plugin_goPlusSecurity_description": "Provide you with fast, reliable and convenient security services", + "plugin_goPlusSecurity_description": "Provide you with fast, reliable and convenient security services.", "plugin_red_packet_create": "Create your Lucky Drop.", "plugin_red_packet_claimed": "Claimed", "plugin_red_packet_claim": "Claim", @@ -745,7 +733,7 @@ "popups_wallet_settings_show_private_key": "Show Private Key", "popups_wallet_settings_backup_wallet": "Backup Wallet", "popups_wallet_settings_enter_payment_password": "Enter Payment Password", - "popups_wallet_settings_export_private_key_title": "Select the wallet to export the private key", + "popups_wallet_settings_export_private_key_title": "Click on the down-arrow icon to see the private key.", "popups_wallet_settings_rename_tips": "Wallet name must between 3 to 18 characters.", "popups_wallet_settings_name_exists": "The wallet name already exists.", "popups_wallet_settings_remove_wallet": "Remove Wallet", @@ -804,6 +792,7 @@ "popups_wallet_name_mnemonic": "Mnemonic", "popups_wallet_name_json_file": "Json File", "popups_wallet_name_private_key": "Private Key", + "popups_wallet_name_keystore": "Keystore", "popups_wallet_name_mnemonic_placeholder": "Enter 12 or 24 mnemonic words.", "popups_wallet_name_origin_password": "Original Password", "popups_wallet_tab_assets": "Tokens", @@ -909,7 +898,7 @@ "popups_disconnect_persona": "Disconnect Social Account?", "popups_new_persona_disconnect_confirmation_description": "This twitter verification relationship with Persona will not be recorded in Next.ID.

Performing this action will impact these Persona-related features:
  • Send the encrypted message.
  • ‘Browser your Web3’ features.

  • ", "popups_persona_disconnect_confirmation_description": "This persona verification record will no longer show up in your verification profile page. Your\n Mask friends can no longer send encrypted message to you by this persona or check your Web3\n products.", - "popups_persona_disconnect_tips": "Do you want to remove the verified association between the Twitter account @{{identity}} and {{personaName}}?", + "popups_persona_disconnect_tips": "Do you want to remove the verified association between the Twitter account @{{- identity}} and {{- personaName}}?", "popups_wallet_disconnect_tips": "Are you sure to remove the connected wallet {{wallet}}?", "popups_persona": "Persona", "popups_twitter_id": "Twitter ID", @@ -958,11 +947,12 @@ "popups_backup_password_rules_tips": "Backup password must be 8-20 characters, including uppercase, lowercase, special characters and numbers.", "popups_backup_password_tips": "Please set up backup password to export private key.", "popups_backup_password_inconsistency": "The two entered passwords are inconsistent.", - "popups_backup_password_incorrect": "Incorrect password.", + "popups_backup_password_incorrect": "Incorrect backup password.", "popups_backup_password_invalid": "Please enter backup password to export persona private key.", "popups_backup_password_set_successfully": "Backup password set successfully", "popups_export_private_key_tips": "This export is only for exporting private key. We do not export any other data. If you need more data, please go to Settings: Global Backup ", "popups_export_json_file_tips": "This file has been encrypted and stored with your current password. Your current password is needed to decrypt this file when using it to import wallet.", + "popups_export_keystore_tips": "This JSON file is encrypted with your current payment password. The same password is required for decryption when importing this wallet.", "popups_log_out_tips": "After logging out, your associated social accounts will no longer decrypt past encrypted messages. If you need to reuse your account, you can recover your account with your identity, private key, local or cloud backup.", "popups_log_out_with_smart_pay_tips_one": "Please note: This Persona {{persona}} is the management account of above listed SmartPay wallet. You cannot use SmartPay wallet to interact with blockchain after logging out persona.", "popups_log_out_with_smart_pay_tips_other": "Please note: This Persona {{persona}} is the management account of above listed SmartPay wallets. You cannot use SmartPay wallet to interact with blockchain after logging out persona.", @@ -1077,13 +1067,10 @@ "plugin_mask_box_description": "Professional multi-chain decentralized platform for launching NFT mystery boxes.", "plugin_transak_name": "Fiat On-Ramp", "plugin_transak_description": "Fiat On-Ramp Aggregator on Twitter. Buy crypto in 60+ countries with Transak support.", - "plugin_valuables_name": "Valuables", - "plugin_valuables_description": "Buy & sell tweets autographed by their original creators.", "popups_wallet_unsupported_network": "Unsupported network type", "plugin_default_title": "Default", "plugin_provider_by": "Provided by", "plugin_enable": "Enable plugin", - "show_wallet_setting_intro": "Please select the wallet to activate the function after saving the settings.", "show_wallet_setting_button": "Go to Settings", "web3_profile_no_social_address_list": "Can’t find a valid user address data source.", "plugin_avatar_feature_general_user_name": "General user", @@ -1170,7 +1157,7 @@ "wallet_edit_contact_successfully": "Edit contact successfully.", "wallet_delete_contact": "Delete Contact", "wallet_delete_contact_successfully": "Delete contact successfully.", - "wallet_name_wallet": "Name your wallet", + "wallet_name": "Name your wallet", "wallet_account": "Wallet Account", "wallet_imported": "Imported", "scan_address_to_payment": "Scan QR code to receive payment", @@ -1209,13 +1196,6 @@ "about_collection": "Abount {{- name}}", "hide_collectible": "Hide {{-name}}", "hide_collectible_description": "Confirm to hide {{- name}}? You can redisplay it by re-adding this NFT at any time.", - "switch_logo_title": "Switch Twitter Logo", - "switch_logo_save_tip": "Share and recommend this feature after saving.", - "switch_logo_classics_logo": "Classics Logo", - "switch_logo_new_logo": "New Logo", - "switch_logo_share_text": "I just replaced Twitter X logo with the original blue bird one with Mask Network extension.", - "switch_logo_share_mask": "Download https://mask.io to try more powerful tools on Twitter.", - "switch_logo_description": "Switch between the classic Twitter logo and the new one.", "add_new_address_to_an_existing_group": "Add new address to an existing group", "or_create_a_new_wallet_group": "Or create a new wallet group" } diff --git a/packages/mask/shared-ui/locales/ja-JP.json b/packages/mask/shared-ui/locales/ja-JP.json index eb48174dc3a7..03fbb09b40e0 100644 --- a/packages/mask/shared-ui/locales/ja-JP.json +++ b/packages/mask/shared-ui/locales/ja-JP.json @@ -62,21 +62,7 @@ "application_settings_tab_plug_app-listed-placeholder": "アプリアイコンをクリックして、アプリ一覧で非表示にします。", "application_display_tab_plug_app-unlisted-placeholder": "設定のアイコンをクリックして、アプリボードに表示します。", "application_settings_tab_plug_app-unlisted-placeholder": "アプリのアイコンをクリックして、アプリボードに表示します。", - "applications_persona_title": "ペルソナ", - "applications_persona_copy": "コピー完了", - "applications_persona_connect": "{{nickname}} に接続", - "applications_persona_connect_hint": "dAppsを使用する前に、 {{nickname}} に接続してください。", - "applications_persona_verify": "{{nickname}} の認証", - "applications_persona_verify_hint": "dAppsを使用する前に、ソーシャルメディアアカウントで現在のペルソナを確認してください。", - "applications_persona_verify_connect": "{{nickname}} に接続して認証します", - "applications_persona_verify_connect_hint": "dAppsを利用する前に{{nickname}}を接続し、プルーフポストを送信してください。", "persona_load_failed": "読み込み失敗", - "enable_plugin_boundary": "プラグインを有効にする", - "enable_plugin_boundary_description": "特定のコンテンツを表示するには DApp を開く必要があります。", - "close_check_security": "[セキュリティチェック] を閉じますか?", - "check_security_intro": "「セキュリティチェック」dAppは、迅速、確実、便利にWeb3セキュリティサービスを提供します。", - "check_security_close_warning": "「セキュリティチェック」を閉じますと、不審なブラックリストに登録され、不正な契約書やアドレスとやりとりする際に、セキュリティに関する通知が表示されなくなります。", - "check_security_close_advice": "「セキュリティチェック」 を開いたままにしておくことをお勧めします。 ", "additional_post_box__encrypted_post_pre": "#mask_io で復号しましょう! {{encrypted}}", "additional_post_box__encrypted_post_pre_red_packet_sns_official_account": "こんにちは、#mask_io @{{account}} でラッキードロップを獲得しましょう。 \n\n$t(promote_red_packet)\n {{encrypted}}", "additional_post_box__encrypted_post_pre_red_packet": "こんにちは、#mask_io @{{account}} でラッキードロップを獲得しましょう。 \n\n$t(promote_red_packet)\n {{encrypted}}", diff --git a/packages/mask/shared-ui/locales/ko-KR.json b/packages/mask/shared-ui/locales/ko-KR.json index b7dc61c7e996..f8d5c82b7925 100644 --- a/packages/mask/shared-ui/locales/ko-KR.json +++ b/packages/mask/shared-ui/locales/ko-KR.json @@ -62,21 +62,7 @@ "application_settings_tab_plug_app-listed-placeholder": "애프리케이션 아이콘을 클릭하여 앱 리스트에 숨깁니다.", "application_display_tab_plug_app-unlisted-placeholder": "설정 아이콘을 클릭하여 앱 보드에 나열합니다.", "application_settings_tab_plug_app-unlisted-placeholder": "애프리케이션 아이콘을 클릭하여 앱 보드에 나열합니다.", - "applications_persona_title": "페르소나", - "applications_persona_copy": "복사 성공", - "applications_persona_connect": "{{nickname}} 에 연결하기", - "applications_persona_connect_hint": "dApp을 이용하기 전에 {{nickname}} 연결하세요.", - "applications_persona_verify": "{{nickname}} 검증", - "applications_persona_verify_hint": "dApp 이용하기 전에 소셜 미디어 계정으로 이용 중인 페르소나를 검증하세요.", - "applications_persona_verify_connect": "{{nickname}} 연결 및 검증", - "applications_persona_verify_connect_hint": "dApp을 이용하기 전에 {{nickname}} 연결하고 증명 게시글 보내세요.", "persona_load_failed": "로딩 실패", - "enable_plugin_boundary": "플러그인 활성화", - "enable_plugin_boundary_description": "특정 콘텐츠를 보려면 dApp을 열어야 합니다.", - "close_check_security": "[Check Security] 닫기", - "check_security_intro": "[Check Security] dApp은 빠르고, 신뢰할 수 있는 웹3 보안 서비스를 제공합니다.", - "check_security_close_warning": "[Check Security] 닫기로 결정한 경우 의심스럽거나 블랙리스트에 있거나 사기 가능성이 있는 컨트랙트 및 주소와 상호 작용할 때 보안 알림이 더 이상 표시되지 않습니다.", - "check_security_close_advice": "새로운 웹3 이용자에게 [Check Security] 열려 있는 상태를 유지하는 것을 권장합니다. ", "additional_post_box__encrypted_post_pre": "#mask_io로 이 게시물을 해독하기. {{encrypted}}", "additional_post_box__encrypted_post_pre_red_packet_sns_official_account": "#mask_io로 행운 드랍을 수령해 보세요. @{{account}} \n\n$t(promote_red_packet)\n {{encrypted}}", "additional_post_box__encrypted_post_pre_red_packet": "#mask_io {{encrypted}} 덧붙이고 레드 패킷을 수령하세요.", @@ -791,7 +777,6 @@ "plugin_default_title": "디폴트", "plugin_provider_by": "제공자", "plugin_enable": "플러그인 활성화", - "show_wallet_setting_intro": "설정 저장 후 이 기능을 활성화하는 데 사용할 월렛을 선택하세요.", "show_wallet_setting_button": "설정으로 가기", "web3_profile_no_social_address_list": "인증된 사용자 주소 데이터 소스를 찾을 수 없습니다.", "plugin_avatar_feature_general_user_name": "일반 사용자", diff --git a/packages/mask/shared-ui/locales/qya-AA.json b/packages/mask/shared-ui/locales/qya-AA.json index 25ddca3bf266..439063302241 100644 --- a/packages/mask/shared-ui/locales/qya-AA.json +++ b/packages/mask/shared-ui/locales/qya-AA.json @@ -90,21 +90,7 @@ "application_settings_tab_plug_app-listed-placeholder": "crwdns16228:0crwdne16228:0", "application_display_tab_plug_app-unlisted-placeholder": "crwdns17254:0crwdne17254:0", "application_settings_tab_plug_app-unlisted-placeholder": "crwdns16230:0crwdne16230:0", - "applications_persona_title": "crwdns18496:0crwdne18496:0", - "applications_persona_copy": "crwdns18498:0crwdne18498:0", - "applications_persona_connect": "crwdns18500:0{{-nickname}}crwdne18500:0", - "applications_persona_connect_hint": "crwdns18502:0{{-nickname}}crwdne18502:0", - "applications_persona_verify": "crwdns18504:0{{-nickname}}crwdne18504:0", - "applications_persona_verify_hint": "crwdns18506:0crwdne18506:0", - "applications_persona_verify_connect": "crwdns18508:0{{-nickname}}crwdne18508:0", - "applications_persona_verify_connect_hint": "crwdns18510:0{{-nickname}}crwdne18510:0", "persona_load_failed": "crwdns18600:0crwdne18600:0", - "enable_plugin_boundary": "crwdns18602:0crwdne18602:0", - "enable_plugin_boundary_description": "crwdns19131:0crwdne19131:0", - "close_check_security": "crwdns17716:0crwdne17716:0", - "check_security_intro": "crwdns17718:0crwdne17718:0", - "check_security_close_warning": "crwdns17720:0crwdne17720:0", - "check_security_close_advice": "crwdns17722:0crwdne17722:0", "additional_post_box__encrypted_post_pre": "crwdns3991:0$t(promote)crwdnd3991:0{{encrypted}}crwdne3991:0", "additional_post_box__encrypted_post_pre_red_packet_sns_official_account": "crwdns19497:0$t(promote_red_packet)crwdnd19497:0{{account}}crwdnd19497:0{{encrypted}}crwdne19497:0", "additional_post_box__encrypted_post_pre_red_packet": "crwdns3995:0$t(promote_red_packet)crwdnd3995:0{{encrypted}}crwdne3995:0", @@ -1077,7 +1063,6 @@ "plugin_default_title": "crwdns16634:0crwdne16634:0", "plugin_provider_by": "crwdns16636:0crwdne16636:0", "plugin_enable": "crwdns19817:0crwdne19817:0", - "show_wallet_setting_intro": "crwdns18672:0crwdne18672:0", "show_wallet_setting_button": "crwdns18674:0crwdne18674:0", "web3_profile_no_social_address_list": "crwdns18676:0crwdne18676:0", "plugin_avatar_feature_general_user_name": "crwdns19103:0crwdne19103:0", diff --git a/packages/mask/shared-ui/locales/zh-CN.json b/packages/mask/shared-ui/locales/zh-CN.json index 6aafbe7ce7c2..b0b88642c2d9 100644 --- a/packages/mask/shared-ui/locales/zh-CN.json +++ b/packages/mask/shared-ui/locales/zh-CN.json @@ -1,6 +1,6 @@ { "promote": ":performancing_arts::performancing_arts::performancing_arts: 是否早已厌倦了纯文字信息?您可以尝试发送加密信息给朋友。安装Mask.io来发送您第一个加密推文吧。", - "promote_red_packet": "🧧🧧🧧 尝试给你的好友发送代币或 NFTs 红包,分享此时的喜悦吧! 安装 Mask.io 发送你的第一个红包。", + "promote_red_packet": "我刚刚用 Mask Network 插件在 Twitter 上创建一个 {{token}} 红包。欢迎抽取和转发。", "promote_file_service": "📃📃📃 尝试在推特上使用永久的、去中心化的文件存储功能。安装 Mask.io 上传并分享你的第一份永久的去中心化存储文件,由主流去中心化存储方案提供技术支持。", "promote_savings": "Hi朋友们,我刚刚在 {{chain}} 上质押了 {{amount}} {{symbol}} 。关注@{{account}} 来查找更多的质押项目。", "promote_withdraw": "Hi朋友们,我刚刚在 {{chain}} 上质押了 {{- amount}} {{symbol}} 。关注@{{account}} 来查找更多的质押项目。", @@ -90,23 +90,9 @@ "application_settings_tab_plug_app-listed-placeholder": "点击应用图标将会在应用列表中隐藏。", "application_display_tab_plug_app-unlisted-placeholder": "点击设置图标进入设置页面,在应用面板上设置显示应用。", "application_settings_tab_plug_app-unlisted-placeholder": "点击应用图标将会在应用列表中显示。", - "applications_persona_title": "身份", - "applications_persona_copy": "复制成功", - "applications_persona_connect": "连接到 {{nickname}}", - "applications_persona_connect_hint": "请在使用 dApp之前连接到 {{nickname}} 。", - "applications_persona_verify": "验证 {{nickname}}", - "applications_persona_verify_hint": "进入应用前请使用当前身份验证当前社交媒体账号。", - "applications_persona_verify_connect": "连接到 {{nickname}} 并进行验证", - "applications_persona_verify_connect_hint": "请在使用 dApp之前连接到 {{nickname}} 并发送验证推文。", "persona_load_failed": "加载失败", - "enable_plugin_boundary": "开启插件", - "enable_plugin_boundary_description": "您需要打开 DAPP 才能查看特定内容。", - "close_check_security": "关闭 [检查安全] 应用?", - "check_security_intro": "[检查安全] 应用提供迅速、可靠和方便的 Web3 安全服务。", - "check_security_close_warning": "如果您决定关闭 [检查安全] 应用,关闭后与任何有潜在风险的合约或地址进行互动时将无法看到相关安全提示。", - "check_security_close_advice": "我们建议 Web3 新用户保持打开 [检查安全] 应用。 ", "additional_post_box__encrypted_post_pre": "安装 #mask_io 解密此贴文 ! {{encrypted}}", - "additional_post_box__encrypted_post_pre_red_packet_sns_official_account": "使用 #mask_io 来领取这个红包!@{{account}} \n\n$t(promote_red_packet)\n {{encrypted}}", + "additional_post_box__encrypted_post_pre_red_packet_sns_official_account": "$t(promote_red_packet) 关注@{{account}},获取更多 Web3 应用的最新资讯和洞见。 \n\n🧧🧧🧧 尝试用 Mask.io 给你的好友发送红包。\n\n {{encrypted}}", "additional_post_box__encrypted_post_pre_red_packet": "使用 #mask_io 来认领这个红包!@{{encrypted}}", "additional_post_box__encrypted_post_pre_file_service_sns_official_account": "$t(promote_file_service)\n {{encrypted}}", "additional_post_box__encrypted_post_pre_file_service": "$t(promote_file_service) {{encrypted}}", @@ -1045,7 +1031,6 @@ "plugin_default_title": "默认", "plugin_provider_by": "提供方:", "plugin_enable": "开启插件", - "show_wallet_setting_intro": "请选择钱包保存设置后激活功能。", "show_wallet_setting_button": "前往设置", "web3_profile_no_social_address_list": "找不到有效的用户地址数据源。", "plugin_avatar_feature_general_user_name": "普通用户", diff --git a/packages/mask/src/components/CompositionDialog/Composition.tsx b/packages/mask/src/components/CompositionDialog/Composition.tsx index dff257ffe2ee..bdc114cb76fa 100644 --- a/packages/mask/src/components/CompositionDialog/Composition.tsx +++ b/packages/mask/src/components/CompositionDialog/Composition.tsx @@ -68,7 +68,7 @@ export function Composition({ type = 'timeline', requireClipboardPermission }: P const [reason, setReason] = useState<'timeline' | 'popup' | 'reply'>('timeline') const [initialMetas, setInitialMetas] = useState>(EMPTY_OBJECT) - // #region Open + const [open, setOpen] = useState(false) const [isOpenFromApplicationBoard, setIsOpenFromApplicationBoard] = useState(false) @@ -109,6 +109,7 @@ export function Composition({ type = 'timeline', requireClipboardPermission }: P if (options?.startupPlugin) UI.current?.startPlugin(options.startupPlugin, options.startupPluginProps) }) }, [type]) + useEffect(() => { if (!open) return @@ -116,16 +117,12 @@ export function Composition({ type = 'timeline', requireClipboardPermission }: P Telemetry.captureEvent(EventType.Interact, EventID.EntryMaskComposeVisibleAll) return MaskMessages.events.replaceComposition.on((message) => { - const ui = UI.current - if (!ui) return + if (!UI.current) return UI.current.setMessage(message) }) }, [open]) - // #endregion - // #region submit const onSubmit_ = useSubmit(onClose, reason) - // #endregion const UI = useRef(null) const networkSupport = activatedSiteAdaptorUI!.injection.newPostComposition?.supportedOutputTypes diff --git a/packages/mask/src/components/CompositionDialog/CompositionUI.tsx b/packages/mask/src/components/CompositionDialog/CompositionUI.tsx index fe0157ec44f3..f7bd624e8937 100644 --- a/packages/mask/src/components/CompositionDialog/CompositionUI.tsx +++ b/packages/mask/src/components/CompositionDialog/CompositionUI.tsx @@ -12,6 +12,13 @@ import { LoadingButton } from '@mui/lab' import { Button, DialogActions, Typography, alpha } from '@mui/material' import type { EncryptTargetPublic } from '@masknet/encryption' import { Icons } from '@masknet/icons' +import { + TypedMessageEditor, + type TypedMessageEditorRef, + CharLimitIndicator, + PluginEntryRender, + type PluginEntryRenderRef, +} from '@masknet/shared' import { CompositionContext, type CompositionType } from '@masknet/plugin-infra/content-script' import { EncryptionTargetType, type ProfileInformation } from '@masknet/shared-base' import { makeStyles } from '@masknet/theme' @@ -22,13 +29,6 @@ import { useI18N } from '../../utils/index.js' import { SelectRecipientsUI } from '../shared/SelectRecipients/SelectRecipients.js' import { EncryptionMethodSelector, EncryptionMethodType } from './EncryptionMethodSelector.js' import { EncryptionTargetSelector } from './EncryptionTargetSelector.js' -import { - TypedMessageEditor, - type TypedMessageEditorRef, - CharLimitIndicator, - PluginEntryRender, - type PluginEntryRenderRef, -} from '@masknet/shared' import type { EncryptTargetE2EFromProfileIdentifier } from '../../../background/services/crypto/encryption.js' const useStyles = makeStyles()((theme) => ({ @@ -186,7 +186,7 @@ export const CompositionDialogUI = forwardRef( attachMetadata: (meta, data) => Editor.current?.attachMetadata(meta, data), dropMetadata: (meta) => Editor.current?.dropMetadata(meta), }), - [props.type], + [props.type, Editor.current], ) const submitAvailable = currentPostSize > 0 && currentPostSize < (props.maxLength ?? Number.POSITIVE_INFINITY) diff --git a/packages/mask/src/components/CompositionDialog/useSubmit.ts b/packages/mask/src/components/CompositionDialog/useSubmit.ts index 9f1fcde6fd74..333338b6512b 100644 --- a/packages/mask/src/components/CompositionDialog/useSubmit.ts +++ b/packages/mask/src/components/CompositionDialog/useSubmit.ts @@ -95,13 +95,14 @@ function decorateEncryptedText(encrypted: string, t: I18NFunction, meta?: Meta): // Note: since this is in the composition stage, we can assume plugins don't insert old version of meta. if (meta?.has(`${PluginID.RedPacket}:1`) || meta?.has(`${PluginID.RedPacket}_nft:1`)) { + const token = meta?.has(`${PluginID.RedPacket}:1`) ? t('redpacket_a_token') : t('redpacket_an_nft') return hasOfficialAccount ? t('additional_post_box__encrypted_post_pre_red_packet_sns_official_account', { encrypted, account: officialAccount, - token: meta?.has(`${PluginID.RedPacket}:1`) ? 'a token' : 'an NFT', + token, }) - : t('additional_post_box__encrypted_post_pre_red_packet', { encrypted }) + : t('additional_post_box__encrypted_post_pre_red_packet', { encrypted, token }) } else if (meta?.has(`${PluginID.FileService}:3`)) { return hasOfficialAccount ? t('additional_post_box__encrypted_post_pre_file_service_sns_official_account', { diff --git a/packages/mask/src/components/GuideStep/index.tsx b/packages/mask/src/components/GuideStep/index.tsx index d0289fd912a1..9f51b551dd18 100644 --- a/packages/mask/src/components/GuideStep/index.tsx +++ b/packages/mask/src/components/GuideStep/index.tsx @@ -1,15 +1,7 @@ import { cloneElement, useRef, useState, type ReactElement, useLayoutEffect } from 'react' import { makeStyles, usePortalShadowRoot } from '@masknet/theme' import { Box, Modal, styled, Typography } from '@mui/material' -import { - CrossIsolationMessages, - EnhanceableSite, - SwitchLogoDialogStatus, - sayHelloShowed, - switchLogoOpenedState, - userGuideFinished, - userGuideStatus, -} from '@masknet/shared-base' +import { sayHelloShowed, userGuideFinished, userGuideStatus } from '@masknet/shared-base' import { useValueRef } from '@masknet/shared-base-ui' import { activatedSiteAdaptorUI } from '../../site-adaptor-infra/index.js' import { useI18N } from '../../utils/index.js' @@ -120,7 +112,6 @@ export default function GuideStep({ total, step, tip, children, arrow = true, on const currentStep = useValueRef(userGuideStatus[networkIdentifier]) const finished = useValueRef(userGuideFinished[networkIdentifier]) const isCurrentStep = +currentStep === step - const state = useValueRef(switchLogoOpenedState) const box1Ref = useRef(null) const box2Ref = useRef(null) @@ -131,7 +122,6 @@ export default function GuideStep({ total, step, tip, children, arrow = true, on const onSkip = () => { sayHelloShowed[networkIdentifier].value = true userGuideFinished[networkIdentifier].value = true - CrossIsolationMessages.events.switchLogoDialogUpdated.sendToLocal({ open: true }) } const onNext = () => { @@ -146,19 +136,8 @@ export default function GuideStep({ total, step, tip, children, arrow = true, on const onTry = () => { userGuideFinished[networkIdentifier].value = true onComplete?.() - CrossIsolationMessages.events.switchLogoDialogUpdated.sendToLocal({ open: true }) } - useLayoutEffect(() => { - if ( - !finished || - state === SwitchLogoDialogStatus.PreviouslyOpened || - networkIdentifier !== EnhanceableSite.Twitter - ) - return - CrossIsolationMessages.events.switchLogoDialogUpdated.sendToLocal({ open: true }) - }, [finished, state, networkIdentifier]) - useLayoutEffect(() => { let stopped = false requestAnimationFrame(function fn() { diff --git a/packages/mask/src/components/InjectedComponents/DisabledPluginSuggestion.tsx b/packages/mask/src/components/InjectedComponents/DisabledPluginSuggestion.tsx index a22334f7ee1d..5c041f3500d1 100644 --- a/packages/mask/src/components/InjectedComponents/DisabledPluginSuggestion.tsx +++ b/packages/mask/src/components/InjectedComponents/DisabledPluginSuggestion.tsx @@ -1,3 +1,7 @@ +import { type ReactNode, useCallback } from 'react' +import { useAsync } from 'react-use' +import type { Option } from 'ts-results-es' +import { useSubscription } from 'use-subscription' import { Icons } from '@masknet/icons' import { type Plugin, @@ -11,10 +15,6 @@ import { BooleanPreference, EMPTY_LIST } from '@masknet/shared-base' import { makeStyles, MaskLightTheme } from '@masknet/theme' import { extractTextFromTypedMessage } from '@masknet/typed-message' import { Box, type BoxProps, Button, Skeleton, Typography, useTheme } from '@mui/material' -import { type ReactNode, useCallback } from 'react' -import { useAsync } from 'react-use' -import type { Option } from 'ts-results-es' -import { useSubscription } from 'use-subscription' import Services from '#services' import { useI18N } from '../../utils/index.js' @@ -42,12 +42,12 @@ export function useDisabledPluginSuggestionFromPost(postContent: Option, export function useDisabledPluginSuggestionFromMeta(meta: undefined | ReadonlyMap) { const disabled = useDisabledPlugins().filter((x) => x.contribution?.metadataKeys) + if (!meta) return EMPTY_LIST - const keys = [...meta.keys()] const matches = disabled.filter((x) => { const contributes = x.contribution!.metadataKeys! - return keys.some((key) => contributes.has(key)) + return [...meta.keys()].some((key) => contributes.has(key)) }) return matches } @@ -58,6 +58,7 @@ export function PossiblePluginSuggestionPostInspector() { const matches = useDisabledPluginSuggestionFromPost(message, metaLinks) return } + export function PossiblePluginSuggestionUI(props: { plugins: Plugin.Shared.Definition[] }) { const { plugins } = props const _plugins = useActivatedPluginsSiteAdaptor('any') diff --git a/packages/mask/src/components/InjectedComponents/PermissionBoundary.tsx b/packages/mask/src/components/InjectedComponents/PermissionBoundary.tsx index 069bead43319..4054a4590c60 100644 --- a/packages/mask/src/components/InjectedComponents/PermissionBoundary.tsx +++ b/packages/mask/src/components/InjectedComponents/PermissionBoundary.tsx @@ -1,7 +1,3 @@ -import type { PluginWrapperComponent, Plugin, PluginWrapperMethods } from '@masknet/plugin-infra/content-script' -import { MaskPostExtraPluginWrapper, useSharedI18N } from '@masknet/shared' -import { EMPTY_LIST } from '@masknet/shared-base' -import { Typography, useTheme } from '@mui/material' import { forwardRef, memo, @@ -13,8 +9,13 @@ import { useState, } from 'react' import type { AsyncState } from 'react-use/lib/useAsyncFn.js' +import type { PluginWrapperComponent, Plugin, PluginWrapperMethods } from '@masknet/plugin-infra/content-script' +import { MaskPostExtraPluginWrapper, useSharedI18N } from '@masknet/shared' +import { EMPTY_LIST } from '@masknet/shared-base' +import { Typography, useTheme } from '@mui/material' import { useCheckPermissions, useGrantPermissions } from '../DataSource/usePluginHostPermission.js' import { PossiblePluginSuggestionUISingle } from './DisabledPluginSuggestion.js' + interface PermissionBoundaryProps extends PropsWithChildren<{}> { permissions: string[] fallback?: @@ -52,6 +53,7 @@ export const MaskPostExtraPluginWrapperWithPermission: PluginWrapperComponent refItem, [refItem]) + return ( ({ button: { // TODO: is it correct? (what about twitter?) - padding: isMobileFacebook ? 0 : '7px', + padding: isMobileFacebook ? 0 : 'var(--icon-padding, 10px)', }, text: { color: theme.palette.grey[300], @@ -39,15 +39,23 @@ const useStyles = makeStyles()((theme) => ({ const ICON_MAP: Record = { minds: , - default: , + default: ( + + ), } -const EntryIconButton = memo((props: PostDialogHintUIProps) => { +const EntryIconButton = memo(function EntryIconButton(props: PostDialogHintUIProps) { const { t } = useI18N() const { tooltip, disableGuideTip } = props const { classes, cx } = useStyles(undefined, { props }) - const getEntry = () => ( + const Entry = ( { ) return disableGuideTip ? ( - getEntry() + Entry ) : ( - {getEntry()} + {Entry} ) }) diff --git a/packages/mask/src/components/InjectedComponents/PostInspector.tsx b/packages/mask/src/components/InjectedComponents/PostInspector.tsx index 1191215050f8..d2a71a49360f 100644 --- a/packages/mask/src/components/InjectedComponents/PostInspector.tsx +++ b/packages/mask/src/components/InjectedComponents/PostInspector.tsx @@ -1,14 +1,14 @@ -import { DecryptPost } from './DecryptedPost/DecryptedPost.js' -import { useCurrentIdentity } from '../DataSource/useActivatedUI.js' +import { useSubscription } from 'use-subscription' import { usePostInfoDetails, createInjectHooksRenderer, useActivatedPluginsSiteAdaptor, } from '@masknet/plugin-infra/content-script' +import { DecryptPost } from './DecryptedPost/DecryptedPost.js' +import { useCurrentIdentity } from '../DataSource/useActivatedUI.js' import { PossiblePluginSuggestionPostInspector } from './DisabledPluginSuggestion.js' -import { useSubscription } from 'use-subscription' -import { PersistentStorages } from '../../../shared/index.js' import { MaskPostExtraPluginWrapperWithPermission } from './PermissionBoundary.js' +import { PersistentStorages } from '../../../shared/index.js' const PluginHooksRenderer = createInjectHooksRenderer( useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, diff --git a/packages/mask/src/components/InjectedComponents/ProfileCard/AvatarDecoration.tsx b/packages/mask/src/components/InjectedComponents/ProfileCard/AvatarDecoration.tsx index 4ae0e220e4c4..a506743e1ca8 100644 --- a/packages/mask/src/components/InjectedComponents/ProfileCard/AvatarDecoration.tsx +++ b/packages/mask/src/components/InjectedComponents/ProfileCard/AvatarDecoration.tsx @@ -1,5 +1,5 @@ -import { Twitter } from '@masknet/web3-providers' import { useAsync } from 'react-use' +import { Twitter } from '@masknet/web3-providers' import { RSS3_KEY_SITE, NFTAvatarMiniClip, NFTBadgeTimeline } from '@masknet/plugin-avatar' interface Props { @@ -16,15 +16,13 @@ export function AvatarDecoration({ clipPathId, userId, className, size }: Props) if (!userId || !user) return null - const avatarId = Twitter.getAvatarId(user.legacy?.profile_image_url_https) - return user.has_nft_avatar ? ( ) : ( { return { @@ -113,6 +114,7 @@ export function ProfileCardTitle({ return NextIDProof.queryProfilesByTwitterId(userId) }, }) + const tipsDisabled = useIsMinimalMode(PluginID.Tips) return (
    @@ -137,9 +139,9 @@ export function ProfileCardTitle({ ) : null} {itsMe ? ( - ) : ( + ) : !tipsDisabled ? ( - )} + ) : null}
    diff --git a/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx b/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx index a77b7e08238e..608da0844157 100644 --- a/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx +++ b/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx @@ -4,6 +4,9 @@ import { first } from 'lodash-es' import { TabContext } from '@mui/lab' import { Link, Button, Stack, Tab, ThemeProvider, Typography } from '@mui/material' import { Icons } from '@masknet/icons' +import { useQuery } from '@tanstack/react-query' +import { Telemetry } from '@masknet/web3-telemetry' +import { EventType, EventID } from '@masknet/web3-telemetry/types' import { useActivatedPluginsSiteAdaptor, useIsMinimalMode, @@ -50,13 +53,11 @@ import { useGrantPermissions, usePluginHostPermissionCheck } from '../DataSource import { SearchResultInspector } from './SearchResultInspector.js' import { usePersonasFromDB } from '../DataSource/usePersonasFromDB.js' import Services from '#services' -import { useQuery } from '@tanstack/react-query' -import { Telemetry } from '@masknet/web3-telemetry' -import { EventType, EventID } from '@masknet/web3-telemetry/types' const useStyles = makeStyles()((theme) => ({ root: { width: Sniffings.is_facebook_page ? 876 : 'auto', + color: theme.palette.maskColor.main, }, container: { background: @@ -201,7 +202,7 @@ function Content(props: ProfileTabContentProps) { const [currentTab, onChange] = useTabs(first(tabs)?.id ?? PluginID.Collectible, ...tabs.map((tab) => tab.id)) - const isWeb3ProfileDisable = useIsMinimalMode(PluginID.Web3Profile) + const isWeb3ProfileDisabled = useIsMinimalMode(PluginID.Web3Profile) const isOnTwitter = Sniffings.is_twitter_page const doesOwnerHaveNoAddress = @@ -212,7 +213,7 @@ function Content(props: ProfileTabContentProps) { const showNextID = isOnTwitter && // enabled the plugin - (isWeb3ProfileDisable || + (isWeb3ProfileDisabled || myPersonaNotVerifiedYet || // the owner persona and site verified on next ID but not verify the wallet doesOwnerHaveNoAddress || @@ -358,7 +359,6 @@ function Content(props: ProfileTabContentProps) { if (((isOwnerIdentity && loadPersonaStatusError) || loadSocialAccounts) && socialAccounts.length === 0) { const handleClick = () => { if (loadPersonaStatusError) retryLoadPersonaStatus() - if (loadSocialAccounts) retrySocialAccounts() } return ( diff --git a/packages/mask/src/components/InjectedComponents/SearchResultInspector.tsx b/packages/mask/src/components/InjectedComponents/SearchResultInspector.tsx index 6bf5095e0708..594fa42bf2ac 100644 --- a/packages/mask/src/components/InjectedComponents/SearchResultInspector.tsx +++ b/packages/mask/src/components/InjectedComponents/SearchResultInspector.tsx @@ -9,24 +9,18 @@ import { getSearchResultTabContent, getSearchResultTabs, useActivatedPluginsSiteAdaptor, + useIsMinimalMode, usePluginI18NField, } from '@masknet/plugin-infra/content-script' -import { - EMPTY_LIST, - PluginID, - type SocialIdentity, - ProfileTabs, - decentralizedSearchSettings, -} from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' +import { EMPTY_LIST, PluginID, type SocialIdentity, type ProfileTabs } from '@masknet/shared-base' import { makeStyles, MaskTabList, useTabs } from '@masknet/theme' +import { DSearch } from '@masknet/web3-providers' import type { Web3Helper } from '@masknet/web3-helpers' import { ScopedDomainsContainer } from '@masknet/web3-hooks-base' -import { DSearch } from '@masknet/web3-providers' import { type SearchResult, SearchResultType } from '@masknet/web3-shared-base' -import { useSearchedKeyword } from '../DataSource/useSearchedKeyword.js' import { Telemetry } from '@masknet/web3-telemetry' import { EventID, EventType } from '@masknet/web3-telemetry/types' +import { useSearchedKeyword } from '../DataSource/useSearchedKeyword.js' const useStyles = makeStyles<{ isProfilePage?: boolean; searchType?: SearchResultType }>()( (theme, { isProfilePage, searchType }) => ({ @@ -59,9 +53,9 @@ export interface SearchResultInspectorProps { export function SearchResultInspector(props: SearchResultInspectorProps) { const translate = usePluginI18NField() + const isMinimalMode = useIsMinimalMode(PluginID.Handle) - const dSearchEnabled = useValueRef(decentralizedSearchSettings) - const { profileTabType } = props + const { identity, profileTabType, isProfilePage } = props const keyword_ = useSearchedKeyword() const keyword = props.keyword || keyword_ const activatedPlugins = useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode() @@ -88,7 +82,7 @@ export function SearchResultInspector(props: SearchResultInspectorProps) { const currentResult = props.currentSearchResult ?? resultList.value?.[0] - const { classes } = useStyles({ isProfilePage: props.isProfilePage, searchType: currentResult?.type }) + const { classes } = useStyles({ isProfilePage, searchType: currentResult?.type }) const contentComponent = useMemo(() => { if (!currentResult || !resultList.value?.length) return null @@ -100,11 +94,11 @@ export function SearchResultInspector(props: SearchResultInspectorProps) { ) - }, [currentResult, resultList.value, props.isProfilePage, props.identity, profileTabType]) + }, [currentResult, resultList.value, isProfilePage, identity, profileTabType]) const tabs = useMemo(() => { if (!currentResult) return EMPTY_LIST @@ -123,7 +117,7 @@ export function SearchResultInspector(props: SearchResultInspectorProps) { return }, [currentTab, resultList.value]) - if (!dSearchEnabled && profileTabType === ProfileTabs.WEB3) return null + if (isMinimalMode && !isProfilePage) return null if (!keyword && !currentResult) return null if (!contentComponent) return null diff --git a/packages/mask/src/components/InjectedComponents/SwitchLogo/index.ts b/packages/mask/src/components/InjectedComponents/SwitchLogo/index.ts deleted file mode 100644 index c109f0937f15..000000000000 --- a/packages/mask/src/components/InjectedComponents/SwitchLogo/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './SwitchLogoDialog.js' diff --git a/packages/mask/src/components/InjectedComponents/ToolboxUnstyled.tsx b/packages/mask/src/components/InjectedComponents/ToolboxUnstyled.tsx index 9312d758f97a..6f9f8c58f504 100644 --- a/packages/mask/src/components/InjectedComponents/ToolboxUnstyled.tsx +++ b/packages/mask/src/components/InjectedComponents/ToolboxUnstyled.tsx @@ -31,7 +31,6 @@ import { makeStyles } from '@masknet/theme' import { useI18N } from '../../utils/index.js' import GuideStep from '../GuideStep/index.js' import { useOpenApplicationBoardDialog } from '../shared/openApplicationBoardDialog.js' -import { SwitchLogoDialog } from './SwitchLogo/SwitchLogoDialog.js' const useStyles = makeStyles()((theme) => ({ title: { @@ -75,32 +74,29 @@ function ToolboxHintForApplication(props: ToolboxHintProps) { const openApplicationBoardDialog = useOpenApplicationBoardDialog() return ( - <> - - - - - - - {mini ? null : ( - - {t('mask_network')} -
    - } - /> - )} - - - - - + + + + + + + {mini ? null : ( + + {t('mask_network')} +
    + } + /> + )} + + + ) } @@ -117,21 +113,20 @@ function ToolboxHintForWallet(props: ToolboxHintProps) { mini, } = props const { classes } = useStyles() - const { openWallet, walletTitle, chainColor, shouldDisplayChainIndicator, account } = useToolbox() + const { onClickToolbox, title, chainColor, shouldDisplayChainIndicator, account, provider } = useToolbox() const theme = useTheme() - const providerDescriptor = useProviderDescriptor() return ( - + - {!!account && providerDescriptor && providerDescriptor.type !== ProviderType.MaskWallet ? ( + {account && provider && provider.type !== ProviderType.MaskWallet ? ( ) : ( @@ -147,7 +142,7 @@ function ToolboxHintForWallet(props: ToolboxHintProps) { justifyContent: 'space-between', alignItems: 'center', }}> - {walletTitle} + {title} {shouldDisplayChainIndicator ? ( { - return account ? WalletStatusModal.open() : SelectProviderModal.open() - }, [account]) + const onClickToolbox = useCallback(() => { + return account && provider ? WalletStatusModal.open() : SelectProviderModal.open() + }, [account, provider]) - const walletTitle = renderButtonText() - - const shouldDisplayChainIndicator = account && chainIdValid && !chainIdMainnet return { - openWallet, - walletTitle, - shouldDisplayChainIndicator, - chainColor, account, + chainColor, + provider, + onClickToolbox, + title: getToolboxTitle(), + shouldDisplayChainIndicator: account && chainIdValid && !chainIdMainnet, } } diff --git a/packages/mask/src/components/shared/PluginEnableBoundary.tsx b/packages/mask/src/components/shared/PluginEnableBoundary.tsx deleted file mode 100644 index b312b5c89c43..000000000000 --- a/packages/mask/src/components/shared/PluginEnableBoundary.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { memo } from 'react' -import { useAsyncFn } from 'react-use' -import { Icons } from '@masknet/icons' -import type { PluginID } from '@masknet/shared-base' -import { useIsMinimalMode } from '@masknet/plugin-infra/content-script' -import { makeStyles, ActionButton } from '@masknet/theme' -import { Stack, Typography } from '@mui/material' -import Services from '#services' -import { useI18N } from '../../utils/index.js' - -const useStyles = makeStyles()((theme) => ({ - root: { - display: 'inline-flex', - justifyContent: 'center', - borderRadius: 20, - width: 254, - height: 40, - }, -})) - -interface PluginEnableBoundaryProps extends withClasses<'root'> { - pluginID: PluginID - children: React.ReactNode -} - -export const PluginEnableBoundary = memo((props) => { - const { t } = useI18N() - const { children, pluginID } = props - const { classes } = useStyles(undefined, { props }) - - const disabled = useIsMinimalMode(pluginID) - - const [{ loading }, onEnablePlugin] = useAsyncFn(async () => { - await Services.Settings.setPluginMinimalModeEnabled(pluginID, false) - }, [pluginID]) - - if (disabled) { - return ( - - - - {t('enable_plugin_boundary_description')} - - - } - className={classes.root} - color="primary" - onClick={onEnablePlugin} - sx={{ mt: 6 }}> - {t('enable_plugin_boundary')} - - - ) - } - return <>{children} -}) diff --git a/packages/mask/src/components/shared/SelectRecipients/SelectRecipients.tsx b/packages/mask/src/components/shared/SelectRecipients/SelectRecipients.tsx index 7d9d88a7f891..efc5d57fcc8d 100644 --- a/packages/mask/src/components/shared/SelectRecipients/SelectRecipients.tsx +++ b/packages/mask/src/components/shared/SelectRecipients/SelectRecipients.tsx @@ -27,7 +27,7 @@ export function SelectRecipientsUI(props: SelectRecipientsUIProps) { const currentIdentity = useCurrentIdentity() const type = resolveNextIDPlatform(valueToSearch) const _value = resolveValueToSearch(valueToSearch) - const { isFetching: searchLoading, data: NextIDResults } = usePersonasFromNextID( + const { isInitialLoading: searchLoading, data: NextIDResults } = usePersonasFromNextID( _value, type ?? NextIDPlatform.NextID, false, diff --git a/packages/mask/src/components/shared/SelectRecipients/useContacts.ts b/packages/mask/src/components/shared/SelectRecipients/useContacts.ts index 3406828d0beb..7a821e739c84 100644 --- a/packages/mask/src/components/shared/SelectRecipients/useContacts.ts +++ b/packages/mask/src/components/shared/SelectRecipients/useContacts.ts @@ -20,6 +20,8 @@ export function useContacts(network: string): AsyncStateRetry x.profile).filter(isProfileIdentifier) - return (await Services.Identity.queryProfilesInformation(identifiers)).filter((x) => x.linkedPersona) + return (await Services.Identity.queryProfilesInformation(identifiers)).filter( + (x) => x.linkedPersona && x.linkedPersona !== currentPersona?.identifier, + ) }, [network, currentPersona]) } diff --git a/packages/mask/src/components/shared/openApplicationBoardDialog.tsx b/packages/mask/src/components/shared/openApplicationBoardDialog.tsx index e318bf747b18..aa83c1182bc1 100644 --- a/packages/mask/src/components/shared/openApplicationBoardDialog.tsx +++ b/packages/mask/src/components/shared/openApplicationBoardDialog.tsx @@ -22,8 +22,6 @@ export function useOpenApplicationBoardDialog(quickMode?: boolean, focusPluginID currentSite: activatedSiteAdaptorUI!.networkIdentifier, queryOwnedPersonaInformation: Services.Identity.queryOwnedPersonaInformation, setPluginMinimalModeEnabled: Services.Settings.setPluginMinimalModeEnabled, - getDecentralizedSearchSettings: Services.Settings.getDecentralizedSearchSettings, - setDecentralizedSearchSettings: Services.Settings.setDecentralizedSearchSettings, personaPerSiteConnectStatusLoading, applicationCurrentStatus, quickMode, diff --git a/packages/mask/src/extension/dashboard/index.ts b/packages/mask/src/extension/dashboard/index.ts index 04d4f5fdc835..3f45876951cf 100644 --- a/packages/mask/src/extension/dashboard/index.ts +++ b/packages/mask/src/extension/dashboard/index.ts @@ -1,3 +1,2 @@ -import { activateSiteAdaptorUI } from '../../setup.ui.js' -await activateSiteAdaptorUI() +import '../../setup.ui.js' await import(/* webpackMode: 'eager' */ './load-dashboard.js') diff --git a/packages/mask/src/extension/debug-page/DatabaseOps.tsx b/packages/mask/src/extension/debug-page/DatabaseOps.tsx deleted file mode 100644 index b0db93819960..000000000000 --- a/packages/mask/src/extension/debug-page/DatabaseOps.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { openDB, wrap } from 'idb/with-async-ittr' -import { timeout } from '@masknet/kit' -import { __DEBUG__ONLY__enableCryptoKeySerialization, serializer } from '@masknet/shared-base' -import type { BackupFormat, Instance, ObjectStore } from './types.js' -import { useI18N } from '../../utils/index.js' -import { env } from '@masknet/flags' - -__DEBUG__ONLY__enableCryptoKeySerialization() - -async function onBackup() { - const payload = await backupAll() - if (payload === undefined) { - return - } - const timestamp = ((value: Date) => { - const values = [ - value.getUTCFullYear(), - value.getUTCMonth() + 1, - value.getUTCDate(), - value.getUTCHours(), - value.getUTCMinutes(), - value.getUTCSeconds(), - ] - return values.map((value) => value.toString().padStart(2, '0')).join('') - })(new Date()) - download(`masknetwork-dump-${timestamp}.json`, payload) -} -async function onRestore() { - const file = await select() - if (file === undefined) { - return - } - // cspell:disable-next-line - const parsed = (await serializer.deserialization(await file.text())) as BackupFormat - await restoreAll(parsed) -} -async function onClear() { - const databases = await indexedDB.databases?.() - if (databases === undefined) { - return - } - await Promise.all( - databases.map(async ({ name }) => { - if (!name) return - await timeout(wrap(indexedDB.deleteDatabase(name)), 500, `Timeout to delete database: ${name}.`) - }), - ) -} -export function DatabaseOps() { - const { t } = useI18N() - return ( -
    -

    - -

    -

    - -

    -

    - -

    -
    - ) -} - -function select() { - return new Promise((resolve) => { - const element = document.createElement('input') - element.type = 'file' - element.addEventListener('change', () => { - resolve(element.files?.[0]) - }) - element.click() - }) -} - -function download(name: string, part: BlobPart) { - const element = document.createElement('a') - element.href = URL.createObjectURL(new Blob([part])) - element.download = name - element.click() -} - -async function restoreAll(parsed: BackupFormat) { - for (const { name, version, stores } of parsed.instances) { - const db = await openDB(name, version, { - upgrade(db) { - for (const name of db.objectStoreNames) { - db.deleteObjectStore(name) - } - for (const [storeName, { autoIncrement, keyPath, indexes }] of Object.entries(stores)) { - const store = db.createObjectStore(storeName, { autoIncrement, keyPath }) - for (const { name, keyPath, multiEntry, unique } of indexes) { - store.createIndex(name, keyPath, { multiEntry, unique }) - } - } - }, - }) - for (const [storeName, { records, keyPath }] of stores.entries()) { - await db.clear(storeName) - for (const [key, value] of records) { - try { - if (keyPath) { - await db.add(storeName, value) - } else { - await db.add(storeName, value, key) - } - } catch (error) { - console.error('Recover error when', key, value, parsed) - // Error from IndexedDB transaction is not recoverable - throw error - } - } - } - } -} - -async function backupAll() { - const databases = await indexedDB.databases?.() - if (databases === undefined) { - return - } - const instances: BackupFormat['instances'] = [] - for (const { name, version } of databases) { - if (!name || !version) continue - const db = await timeout(openDB(name, version), 500, `Timeout to open database ${name}_${version}.`) - if (db === undefined) { - continue - } - const stores: Instance['stores'] = new Map() - for (const name of db.objectStoreNames) { - const store = db.transaction(name).store - const indexes: ObjectStore['indexes'] = [] - for (const indexName of store.indexNames) { - const index = store.index(indexName) - indexes.push({ - name: index.name, - unique: index.unique, - multiEntry: index.multiEntry, - keyPath: index.keyPath, - }) - } - const records: ObjectStore['records'] = new Map() - for await (const cursor of store) { - records.set(cursor.key, cursor.value) - } - stores.set(name, { - keyPath: store.keyPath, - autoIncrement: store.autoIncrement, - indexes, - records, - }) - } - instances.push({ name, version, stores }) - } - const payload: BackupFormat = { - buildInfo: { - 'user-agent': navigator.userAgent, - version: env.VERSION, - 'build-date': env.BUILD_DATE, - 'commit-hash': env.COMMIT_HASH, - 'commit-date': env.COMMIT_DATE, - 'branch-name': env.BRANCH_NAME, - dirty: env.DIRTY, - }, - instances, - } - return JSON.stringify(serializer.serialization(payload), undefined, 2) -} diff --git a/packages/mask/src/extension/debug-page/DebugInfo.tsx b/packages/mask/src/extension/debug-page/DebugInfo.tsx deleted file mode 100644 index f4fe95348132..000000000000 --- a/packages/mask/src/extension/debug-page/DebugInfo.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useI18N } from '../../utils/index.js' -import { useBuildInfoMarkdown, openWindow, useBuildInfo } from '@masknet/shared-base-ui' - -export function DebugInfo() { - const { t } = useI18N() - const info = useBuildInfo() - const markdown = useBuildInfoMarkdown() - - const url = new URLSearchParams() - url.set('title', '[Bug] ') - url.set('body', markdown) - const link = 'https://github.com/DimensionDev/Maskbook/issues/new?' + url.toString() - - return ( - <> -
    {JSON.stringify(info, undefined, 4)}
    - - - ) -} diff --git a/packages/mask/src/extension/debug-page/Entry.tsx b/packages/mask/src/extension/debug-page/Entry.tsx deleted file mode 100644 index ecd1551e6ee5..000000000000 --- a/packages/mask/src/extension/debug-page/Entry.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { DebugInfo } from './DebugInfo.js' -import { DatabaseOps } from './DatabaseOps.js' - -export function Entry() { - return ( - <> - - - - ) -} diff --git a/packages/mask/src/extension/debug-page/README.md b/packages/mask/src/extension/debug-page/README.md deleted file mode 100644 index 6a4d01b84780..000000000000 --- a/packages/mask/src/extension/debug-page/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Debug page - -## Feature Set - -- [x] Backup Database -- [x] Restore Database -- [x] Clear Database -- [ ] Self-tests - - [ ] Test R/W Gundb - - [ ] Test R/W Gitcoin - - [ ] Test R/W ETH Network -- [ ] Check Database integrity -- [ ] Refresh Remote Feature Control diff --git a/packages/mask/src/extension/debug-page/index.tsx b/packages/mask/src/extension/debug-page/index.tsx deleted file mode 100644 index 9490b2c74eac..000000000000 --- a/packages/mask/src/extension/debug-page/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { createRoot } from 'react-dom/client' - -import { Entry } from './Entry.js' - -const container = document.createElement('main') - -document.body.appendChild(container) -createRoot(container).render() diff --git a/packages/mask/src/extension/debug-page/types.ts b/packages/mask/src/extension/debug-page/types.ts deleted file mode 100644 index 047f8e2c2eb0..000000000000 --- a/packages/mask/src/extension/debug-page/types.ts +++ /dev/null @@ -1,24 +0,0 @@ -export interface BackupFormat { - buildInfo: Record - instances: Instance[] -} - -export interface Instance { - name: string - version: number - stores: Map -} - -export interface ObjectStore { - indexes: Index[] - keyPath: IDBObjectStoreParameters['keyPath'] - autoIncrement: IDBObjectStoreParameters['autoIncrement'] - records: Map -} - -export interface Index { - name: IDBIndex['name'] - unique: IDBIndex['unique'] - multiEntry: IDBIndex['multiEntry'] - keyPath: IDBIndex['keyPath'] -} diff --git a/packages/mask/src/extension/popups/SSR-client.ts b/packages/mask/src/extension/popups/SSR-client.ts deleted file mode 100644 index 502bf66735dc..000000000000 --- a/packages/mask/src/extension/popups/SSR-client.ts +++ /dev/null @@ -1,43 +0,0 @@ -// This file should not import any file!! -/// - -// If the current page is "", navigate to "#/personas", therefore we can avoid a Router skip when hydrating. -if (location.hash === '') location.assign('#/personas') - -let trustedHTML: (x: string) => string | TrustedHTML -{ - if (typeof trustedTypes === 'object') { - const policy = trustedTypes.createPolicy('ssr', { - createHTML: String, - }) - trustedHTML = (x) => policy.createHTML(x) - } else { - trustedHTML = (x) => x - } -} - -if ( - location.hash === '#/personas' || - (location.hash.includes('#/personas') && location.hash.includes('tab=Connected+Wallets')) -) { - console.time('[SSR] Request') - browser.runtime.sendMessage({ type: 'popups-ssr' }).then(({ html, css }) => { - // React go first, but is that possible? - if (document.querySelector('#root')) return - - // Push SSR code - document.head.insertAdjacentHTML('beforeend', trustedHTML(css)) - // this is safe because it comes from SSR - // eslint-disable-next-line @masknet/browser-no-set-html - document.body.innerHTML = trustedHTML('
    ' + html + '
    ') as string - console.timeEnd('[SSR] Request') - - console.time('[SSR] Hydrate') - }) -} - -await import(/* webpackMode: 'eager' */ '../../setup.ui.js') -await import(/* webpackMode: 'eager' */ '../../site-adaptors/browser-action/index.js') -await import(/* webpackMode: 'eager' */ './normal-client.js') - -export {} diff --git a/packages/mask/src/extension/popups/SSR-server.tsx b/packages/mask/src/extension/popups/SSR-server.tsx deleted file mode 100644 index 7bf26755ea5d..000000000000 --- a/packages/mask/src/extension/popups/SSR-server.tsx +++ /dev/null @@ -1,78 +0,0 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - -import { initReactI18next } from 'react-i18next' -import { renderToString } from 'react-dom/server' -import { StaticRouter } from 'react-router-dom/server.js' -import { i18NextInstance, updateLanguage, PopupRoutes, EMPTY_LIST } from '@masknet/shared-base' -import { once, noop } from 'lodash-es' -import { DisableShadowRootContext, MaskThemeProvider } from '@masknet/theme' -import { CacheProvider } from '@emotion/react' -import createCache from '@emotion/cache' -import createEmotionServer from '@emotion/server/create-instance' -import { addShareBaseI18N } from '@masknet/shared-base-ui' -import { addMaskI18N } from '../../../shared-ui/locales/languages.js' -import type { PopupSSR_Props } from '../../../background/tasks/Cancellable/PopupSSR/type.js' -import { PersonaHomeUI } from './pages/Personas/Home/UI.js' -import { usePopupTheme } from '../../utils/theme/usePopupTheme.js' -import { PopupLayout } from './components/PopupLayout/index.js' -import { WalletLinkContext } from './components/Navigator/index.js' - -const init = once(() => - i18NextInstance.init().then(() => { - addMaskI18N(i18NextInstance) - addShareBaseI18N(i18NextInstance) - initReactI18next.init(i18NextInstance) - }), -) -export async function render(props: PopupSSR_Props) { - if (Object.keys(props).length === 0) throw new Error('PopupSSR: props is empty') - - await init() - updateLanguage(props.language) - // https://github.com/emotion-js/emotion/issues/2933 - const cache = (createCache.default || createCache)({ key: 'css' }) - const server = (createEmotionServer.default || createEmotionServer)(cache) - - const html = renderToString( - - - , - ) - .replaceAll('href="/', 'href="#/') - .replaceAll('href="#/dashboard', 'href="/dashboard') - const css = server.constructStyleTagsFromChunks(server.extractCriticalToChunks(html)) - return { html, css } -} - -const walletLink = () => '#' -function PopupSSR(props: PopupSSR_Props) { - return ( - // MaskUIRoot - - - 'light'}> - - - - - - - - - ) -} diff --git a/packages/mask/src/extension/popups/UI.tsx b/packages/mask/src/extension/popups/UI.tsx index 5806c4ffbf76..b30b360b0d50 100644 --- a/packages/mask/src/extension/popups/UI.tsx +++ b/packages/mask/src/extension/popups/UI.tsx @@ -69,6 +69,12 @@ const personaInitialState = { const PopupRoutes = memo(function PopupRoutes() { const location = useLocation() const mainLocation = location.state?.mainLocation as Location | undefined + + useEffect(() => { + const spinner = document.getElementById('app-spinner') + if (spinner) spinner.remove() + }, []) + return ( ({ background: theme.palette.maskColor.secondaryBottom, padding: theme.spacing(2), boxShadow: theme.palette.maskColor.bottomBg, + backdropFilter: 'blur(8px)', left: 0, right: 0, display: 'flex', diff --git a/packages/mask/src/extension/popups/components/BottomDrawer/index.tsx b/packages/mask/src/extension/popups/components/BottomDrawer/index.tsx index 0609fc52fa2b..1f6185c9c19c 100644 --- a/packages/mask/src/extension/popups/components/BottomDrawer/index.tsx +++ b/packages/mask/src/extension/popups/components/BottomDrawer/index.tsx @@ -1,12 +1,22 @@ import { memo, useRef, type ReactNode, useEffect } from 'react' import { Icons } from '@masknet/icons' import { TextOverflowTooltip, makeStyles } from '@masknet/theme' -import { Box, Drawer, Typography } from '@mui/material' +import { Box, Drawer, Typography, backdropClasses } from '@mui/material' const useStyles = makeStyles()((theme) => ({ - root: { + paper: { padding: theme.spacing(2.25), borderRadius: '24px 24px 0 0', + background: theme.palette.maskColor.bottom, + }, + root: { + [`& .${backdropClasses.root}`]: { + background: + theme.palette.mode === 'dark' + ? 'rgba(255, 255, 255, 0.10)' + : 'linear-gradient(0deg, rgba(0, 0, 0, 0.40) 0%, rgba(0, 0, 0, 0.40) 100%), rgba(28, 104, 243, 0.20)', + backdropFilter: 'blur(5px)', + }, }, header: { display: 'flex', @@ -47,7 +57,11 @@ export const BottomDrawer = memo(function BottomDrawer({ open if (open) everOpenRef.current = true }, [open]) return ( - + {title} diff --git a/packages/mask/src/extension/popups/components/InitialPlaceholder/index.tsx b/packages/mask/src/extension/popups/components/InitialPlaceholder/index.tsx deleted file mode 100644 index 68cacfa8f026..000000000000 --- a/packages/mask/src/extension/popups/components/InitialPlaceholder/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { memo } from 'react' -import { Box, Button, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { useEnterDashboard } from '../../hooks/index.js' -import { Icons } from '@masknet/icons' -import { useI18N } from '../../../../utils/index.js' - -const useStyles = makeStyles()({ - container: { - flex: 1, - display: 'flex', - flexDirection: 'column', - }, - placeholder: { - marginTop: 87, - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - }, - button: { - fontWeight: 600, - paddingTop: 10, - paddingBottom: 10, - fontSize: 14, - lineHeight: 1.5, - borderRadius: 20, - marginTop: 85, - marginLeft: 16, - marginRight: 16, - }, - header: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - background: - 'linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #FFFFFF 100%), linear-gradient(90deg, rgba(98, 126, 234, 0.2) 0%, rgba(59, 153, 252, 0.2) 100%)', - height: 187, - }, - title: { - fontWeight: 700, - fontSize: 24, - lineHeight: 1.2, - color: '#07101B', - }, - description: { - color: '#ACB4C1', - textAlign: 'center', - lineHeight: '18px', - fontWeight: 700, - marginTop: 12, - }, -}) - -export const InitialPlaceholder = memo(() => { - const { t } = useI18N() - const { classes } = useStyles() - const onEnter = useEnterDashboard() - - return ( - - - - - - {t('welcome_to_mask')} - - {t('welcome_description_congrats')}
    - {t('welcome_description_content')}
    - {t('welcome_description_content_second')} -
    -
    - -
    - ) -}) diff --git a/packages/mask/src/extension/popups/components/MnemonicDisplay/index.tsx b/packages/mask/src/extension/popups/components/MnemonicDisplay/index.tsx index 3b350e258c73..3e139f5d54b1 100644 --- a/packages/mask/src/extension/popups/components/MnemonicDisplay/index.tsx +++ b/packages/mask/src/extension/popups/components/MnemonicDisplay/index.tsx @@ -1,11 +1,11 @@ +import { memo } from 'react' +import { Trans } from 'react-i18next' import { Icons } from '@masknet/icons' import { EMPTY_LIST } from '@masknet/shared-base' import { makeStyles } from '@masknet/theme' import { Box, Button, Typography, alpha } from '@mui/material' import { useToggle } from '@react-hookz/web' -import { memo } from 'react' import { useI18N } from '../../../../utils/i18n-next-ui.js' -import { Trans } from 'react-i18next' const useStyles = makeStyles()((theme) => ({ root: { diff --git a/packages/mask/src/extension/popups/components/Navigator/index.tsx b/packages/mask/src/extension/popups/components/Navigator/index.tsx index 597e33380884..62b251d4e383 100644 --- a/packages/mask/src/extension/popups/components/Navigator/index.tsx +++ b/packages/mask/src/extension/popups/components/Navigator/index.tsx @@ -1,17 +1,11 @@ /// -// ! This file is used during SSR. DO NOT import new files that does not work in SSR -import urlcat from 'urlcat' -import { createContext, memo, use, useContext, useMemo, useRef } from 'react' +import { memo } from 'react' import { NavLink, type LinkProps } from 'react-router-dom' import { BottomNavigationAction, Box, type BoxProps } from '@mui/material' import { Icons } from '@masknet/icons' import { makeStyles } from '@masknet/theme' import { PopupRoutes } from '@masknet/shared-base' -import { useMessages, useWallet } from '@masknet/web3-hooks-base' -import { useHasPassword } from '../../hooks/index.js' -import { useWalletLockStatus } from '../../pages/Wallet/hooks/useWalletLockStatus.js' -import { HydrateFinished } from '../../../../utils/createNormalReactRoot.js' -import { useCurrentPersona } from '../../../../components/DataSource/useCurrentPersona.js' +import { useMessages } from '@masknet/web3-hooks-base' const useStyle = makeStyles()((theme) => ({ container: { @@ -42,7 +36,7 @@ const BottomNavLink = memo(function BottomNavLink({ children, to }) { const { classes } = useStyle() return ( - (isActive && to !== '#' ? classes.selected : undefined)}> + (isActive ? classes.selected : undefined)}> {children} ) @@ -50,11 +44,7 @@ const BottomNavLink = memo(function BottomNavLink({ children, to }) { export const Navigator = memo(function Navigator({ className, ...rest }: BoxProps) { const { classes, cx } = useStyle() - const walletLink = useRef(use(WalletLinkContext)).current() - const currentPersona = useCurrentPersona() - - useContext(HydrateFinished)() const messages = useMessages() return ( @@ -67,7 +57,7 @@ export const Navigator = memo(function Navigator({ className, ...rest }: BoxProp className={classes.action} /> - + ) }) - -export const WalletLinkContext = createContext(function useWalletLink() { - const wallet = useWallet() - const messages = useMessages() - const { isLocked, loading: lockStatusLoading } = useWalletLockStatus() - const { hasPassword, loading: hasPasswordLoading } = useHasPassword() - const walletPageLoading = lockStatusLoading || hasPasswordLoading - const walletLink = useMemo(() => { - if (walletPageLoading) return '#' - if (!wallet) return PopupRoutes.Wallet - if (!hasPassword) return PopupRoutes.SetPaymentPassword - if (isLocked) - return urlcat(PopupRoutes.Unlock, { from: messages.length ? PopupRoutes.ContractInteraction : undefined }) - if (messages.length) return PopupRoutes.ContractInteraction - return PopupRoutes.Wallet - }, [wallet, walletPageLoading, isLocked, hasPassword, messages]) - return walletLink -}) diff --git a/packages/mask/src/extension/popups/components/NormalHeader/index.tsx b/packages/mask/src/extension/popups/components/NormalHeader/index.tsx index 322ca1345ea1..4d3a863a7f69 100644 --- a/packages/mask/src/extension/popups/components/NormalHeader/index.tsx +++ b/packages/mask/src/extension/popups/components/NormalHeader/index.tsx @@ -1,5 +1,3 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - import { memo, useCallback, useContext, type ReactNode } from 'react' import { useNavigate, useOutletContext } from 'react-router-dom' import { Box, Typography } from '@mui/material' diff --git a/packages/mask/src/extension/popups/components/PasswordField/index.tsx b/packages/mask/src/extension/popups/components/PasswordField/index.tsx index c798dd12cb57..87803e517bb4 100644 --- a/packages/mask/src/extension/popups/components/PasswordField/index.tsx +++ b/packages/mask/src/extension/popups/components/PasswordField/index.tsx @@ -4,10 +4,7 @@ import { StyledInput } from '../StyledInput/index.js' import { Icons } from '@masknet/icons' export const PasswordField = memo( - forwardRef<{}, TextFieldProps & { show?: boolean; onClear?: () => void }>(function PasswordField( - { show = true, onClear, ...rest }, - ref, - ) { + forwardRef<{}, TextFieldProps & { show?: boolean }>(function PasswordField({ show = true, ...rest }, ref) { const theme = useTheme() const [showPassword, setShowPassword] = useState(false) return ( @@ -17,27 +14,20 @@ export const PasswordField = memo( ref={ref} InputProps={{ ...rest.InputProps, - endAdornment: - rest.error && onClear ? ( - - - + endAdornment: ( + + {show ? ( + setShowPassword(!showPassword)} + onMouseDown={(event) => event.preventDefault()} + edge="end" + size="small"> + {showPassword ? : } - - ) : ( - - {show ? ( - setShowPassword(!showPassword)} - onMouseDown={(event) => event.preventDefault()} - edge="end" - size="small"> - {showPassword ? : } - - ) : undefined} - - ), + ) : undefined} + + ), }} /> ) diff --git a/packages/mask/src/extension/popups/components/PopupLayout/index.tsx b/packages/mask/src/extension/popups/components/PopupLayout/index.tsx index f23b67e1e537..8b101b4cd4a0 100644 --- a/packages/mask/src/extension/popups/components/PopupLayout/index.tsx +++ b/packages/mask/src/extension/popups/components/PopupLayout/index.tsx @@ -1,5 +1,3 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - import { memo, useMemo, type PropsWithChildren, lazy, Suspense } from 'react' import { matchPath, Outlet, useLocation } from 'react-router-dom' import { PopupRoutes } from '@masknet/shared-base' @@ -55,7 +53,6 @@ const useStyles = makeStyles()((theme) => ({ const PATTERNS = [ PopupRoutes.Personas, PopupRoutes.Wallet, - PopupRoutes.Unlock, PopupRoutes.SetPaymentPassword, PopupRoutes.Friends, PopupRoutes.Settings, diff --git a/packages/mask/src/extension/popups/components/TokenPicker/TokenItem.tsx b/packages/mask/src/extension/popups/components/TokenPicker/TokenItem.tsx index 16ffe18fc3f3..ee8ba6fa32d3 100644 --- a/packages/mask/src/extension/popups/components/TokenPicker/TokenItem.tsx +++ b/packages/mask/src/extension/popups/components/TokenPicker/TokenItem.tsx @@ -145,6 +145,7 @@ export const TokenItem = memo(function TokenItem({ (function UnlockERC20 const { data: token } = useFungibleToken( NetworkPluginID.PLUGIN_EVM, transaction.formattedTransaction?.tokenInAddress, + undefined, + { chainId }, ) const { data: balance = '0' } = useFungibleTokenBalance( @@ -244,7 +246,7 @@ export const UnlockERC20Token = memo(function UnlockERC20 ) : null} {t('popups_wallet_unlock_erc20_requested_by')} {transaction.formattedTransaction.popup?.spender ? ( - + {t('contract')}: {transaction.formattedTransaction.popup?.spender}{' '} diff --git a/packages/mask/src/extension/popups/context.ts b/packages/mask/src/extension/popups/context.ts index 74b870f70467..52df61989623 100644 --- a/packages/mask/src/extension/popups/context.ts +++ b/packages/mask/src/extension/popups/context.ts @@ -1,5 +1,3 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - import { createContext } from 'react' export interface PageTitleState { diff --git a/packages/mask/src/extension/popups/entry.ts b/packages/mask/src/extension/popups/entry.ts new file mode 100644 index 000000000000..0a190d58d3ac --- /dev/null +++ b/packages/mask/src/extension/popups/entry.ts @@ -0,0 +1,5 @@ +/// + +import '../../setup.ui.js' +await import(/* webpackMode:'eager' */ '../../site-adaptors/browser-action/index.js') +await import(/* webpackMode: 'eager' */ './render.js') diff --git a/packages/mask/src/extension/popups/hooks/index.ts b/packages/mask/src/extension/popups/hooks/index.ts index 1ed25729bc97..def4b7b2c567 100644 --- a/packages/mask/src/extension/popups/hooks/index.ts +++ b/packages/mask/src/extension/popups/hooks/index.ts @@ -1,5 +1,4 @@ export * from './useContactsContext.js' -export * from './useEnterDashboard.js' export * from './useFriendProfiles.js' export * from './useFriends.js' export * from './useFriendsFromSearch.js' diff --git a/packages/mask/src/extension/popups/hooks/useEnterDashboard.ts b/packages/mask/src/extension/popups/hooks/useEnterDashboard.ts deleted file mode 100644 index e48b950dbb09..000000000000 --- a/packages/mask/src/extension/popups/hooks/useEnterDashboard.ts +++ /dev/null @@ -1,19 +0,0 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - -import { useCallback } from 'react' - -export const useEnterDashboard = () => { - return useCallback((event: React.MouseEvent) => { - if (event.shiftKey) { - browser.tabs.create({ - active: true, - url: browser.runtime.getURL('/debug.html'), - }) - } else { - browser.tabs.create({ - active: true, - url: browser.runtime.getURL('/dashboard.html'), - }) - } - }, []) -} diff --git a/packages/mask/src/extension/popups/hooks/useFriendProfiles.ts b/packages/mask/src/extension/popups/hooks/useFriendProfiles.ts index 3257293de62a..087e50d980d9 100644 --- a/packages/mask/src/extension/popups/hooks/useFriendProfiles.ts +++ b/packages/mask/src/extension/popups/hooks/useFriendProfiles.ts @@ -1,17 +1,24 @@ -import { EMPTY_LIST, NextIDPlatform, type BindingProof } from '@masknet/shared-base' +import { + EMPTY_LIST, + NextIDPlatform, + type BindingProof, + type ProfileIdentifier, + type EnhanceableSite, +} from '@masknet/shared-base' import { NextIDProof } from '@masknet/web3-providers' import { useQuery } from '@tanstack/react-query' import { useCurrentPersona } from '../../../components/DataSource/useCurrentPersona.js' import { PlatformSort, UnsupportedPlatforms } from '../pages/Friends/common.js' +import { useMemo } from 'react' export const profilesFilter = (x: BindingProof) => { return (x.platform === NextIDPlatform.ENS && x.name.endsWith('.eth')) || !UnsupportedPlatforms.includes(x.platform) } -export function useFriendProfiles(seen: boolean, nextId?: string, twitterId?: string) { +export function useFriendProfiles(seen: boolean, nextId?: string, profile?: ProfileIdentifier) { const currentPersona = useCurrentPersona() - const { data: profiles } = useQuery( + const { data: profiles = EMPTY_LIST } = useQuery( ['profiles', currentPersona?.identifier.publicKeyAsHex, nextId], async () => { if (!nextId) return EMPTY_LIST @@ -25,23 +32,26 @@ export function useFriendProfiles(seen: boolean, nextId?: string, twitterId?: st enabled: seen && !!nextId, }, ) - if (!profiles) return EMPTY_LIST - if (profiles.length === 0) { - if (twitterId) { - return [ - { - platform: NextIDPlatform.Twitter, - identity: twitterId, - is_valid: true, - last_checked_at: '', - name: twitterId, - created_at: '', - }, - ] - } else { - return EMPTY_LIST + return useMemo(() => { + if (profiles.length === 0) { + if (profile?.userId) { + return [ + { + platform: profile?.network as + | EnhanceableSite.Twitter + | EnhanceableSite.Facebook + | EnhanceableSite.Instagram, + identity: profile.userId, + is_valid: true, + last_checked_at: '', + name: profile.userId, + created_at: '', + }, + ] + } else { + return EMPTY_LIST + } } - } - const filtered = profiles.filter(profilesFilter).sort((a, b) => PlatformSort[a.platform] - PlatformSort[b.platform]) - return filtered + return profiles.filter(profilesFilter).sort((a, b) => PlatformSort[a.platform] - PlatformSort[b.platform]) + }, [profiles]) } diff --git a/packages/mask/src/extension/popups/hooks/useFriends.ts b/packages/mask/src/extension/popups/hooks/useFriends.ts index e9b7bf07b01a..db1958939e18 100644 --- a/packages/mask/src/extension/popups/hooks/useFriends.ts +++ b/packages/mask/src/extension/popups/hooks/useFriends.ts @@ -1,17 +1,12 @@ -import { isProfileIdentifier } from '@masknet/shared' -import { EMPTY_LIST, type BindingProof, type ECKeyIdentifier, type ProfileIdentifier } from '@masknet/shared-base' -import { useInfiniteQuery, useQuery } from '@tanstack/react-query' -import { first } from 'lodash-es' import { useCallback } from 'react' -import { useCurrentPersona } from '../../../components/DataSource/useCurrentPersona.js' +import { useInfiniteQuery, useQuery } from '@tanstack/react-query' +import { isProfileIdentifier } from '@masknet/shared' +import { EMPTY_LIST, type ECKeyIdentifier, type ProfileIdentifier } from '@masknet/shared-base' import Services from '#services' +import { useCurrentPersona } from '../../../components/DataSource/useCurrentPersona.js' +import { type RelationRecord } from '../../../../background/database/persona/type.js' -export type FriendsInformation = Friend & { - profiles: BindingProof[] - id: string -} - -export type Friend = { +export interface Friend { persona: ECKeyIdentifier profile?: ProfileIdentifier avatar?: string @@ -19,20 +14,28 @@ export type Friend = { export function useFriendsPaged() { const currentPersona = useCurrentPersona() + const { data: records = EMPTY_LIST, isLoading: recordsLoading, refetch: refetchRecords, - } = useQuery(['relation-records', currentPersona?.identifier.rawPublicKey], async () => { - return Services.Identity.queryRelationPaged( - currentPersona?.identifier, - { - network: 'all', - pageOffset: 0, - }, - 3000, - ) - }) + status: fetchRelationStatus, + } = useQuery( + ['relation-records', currentPersona?.identifier.rawPublicKey], + async () => { + return Services.Identity.queryRelationPaged( + currentPersona?.identifier, + { + network: 'all', + pageOffset: 0, + }, + 3000, + ) + }, + { + enabled: !!currentPersona, + }, + ) const { data, hasNextPage, @@ -40,6 +43,7 @@ export function useFriendsPaged() { isLoading, isFetchingNextPage, refetch: refetchFriends, + status, } = useInfiniteQuery({ queryKey: ['friends', currentPersona?.identifier.rawPublicKey], enabled: !recordsLoading, @@ -52,12 +56,11 @@ export function useFriendsPaged() { if (friends.length === 10) break const x = records[i] if (isProfileIdentifier(x.profile)) { - const res = first(await Services.Identity.queryProfilesInformation([x.profile])) - if (res?.linkedPersona !== undefined && res?.linkedPersona !== currentPersona?.identifier) + const profile = await Services.Identity.queryProfileInformation(x.profile) + if (profile?.linkedPersona && profile.linkedPersona !== currentPersona?.identifier) friends.push({ - persona: res.linkedPersona, + persona: profile.linkedPersona, profile: x.profile, - avatar: res.avatar, }) } else { if (x.profile !== currentPersona?.identifier) friends.push({ persona: x.profile }) @@ -74,6 +77,7 @@ export function useFriendsPaged() { refetchFriends() refetchRecords() }, [refetchFriends, refetchRecords]) + return { data, isLoading: isLoading || recordsLoading, @@ -81,5 +85,31 @@ export function useFriendsPaged() { fetchNextPage, isFetchingNextPage, refetch, + status, + fetchRelationStatus, + records, } } + +export function useFriendFromList(searchedRecords: RelationRecord[]) { + const currentPersona = useCurrentPersona() + return useQuery(['search-local', searchedRecords], async () => { + return ( + await Promise.all( + searchedRecords.map>(async (x) => { + if (!isProfileIdentifier(x.profile)) return + const profile = await Services.Identity.queryProfileInformation(x.profile) + if ( + profile?.linkedPersona !== undefined && + profile?.linkedPersona.publicKeyAsHex !== currentPersona?.identifier.publicKeyAsHex + ) + return { + persona: profile.linkedPersona, + profile: x.profile, + } + return + }), + ) + ).filter((x): x is Friend => typeof x !== 'undefined' && Object.hasOwn(x, 'persona')) + }) +} diff --git a/packages/mask/src/extension/popups/hooks/useFriendsFromSearch.tsx b/packages/mask/src/extension/popups/hooks/useFriendsFromSearch.tsx index 75256a199f4c..2dfe579d4e37 100644 --- a/packages/mask/src/extension/popups/hooks/useFriendsFromSearch.tsx +++ b/packages/mask/src/extension/popups/hooks/useFriendsFromSearch.tsx @@ -1,43 +1,73 @@ -import { ECKeyIdentifier, EMPTY_LIST, type NextIDPersonaBindings } from '@masknet/shared-base' -import { uniqBy } from 'lodash-es' import { useMemo } from 'react' -import type { Friend } from './useFriends.js' +import { uniqBy } from 'lodash-es' +import { ECKeyIdentifier, EMPTY_LIST, type NextIDPersonaBindings } from '@masknet/shared-base' import { useCurrentLinkedPersona } from '@masknet/shared' +import type { Friend } from './useFriends.js' import { profilesFilter } from './useFriendProfiles.js' -import { PlatformSort } from '../pages/Friends/common.js' +import { PlatformSort, type FriendNetwork, type Profile } from '../pages/Friends/common.js' -export type NextIDPersonaBindingsWithIdentifier = NextIDPersonaBindings & { linkedPersona: ECKeyIdentifier } & { +export type NextIDPersonaBindingsWithIdentifier = Omit & { proofs: Profile[] } & { + linkedPersona: ECKeyIdentifier +} & { isLocal?: boolean } export function useFriendsFromSearch( + localSearchedResult: Friend[], searchResult?: NextIDPersonaBindings[], localList?: Friend[], searchValue?: string, ): NextIDPersonaBindingsWithIdentifier[] { const currentIdentifier = useCurrentLinkedPersona() return useMemo(() => { - if (!searchResult?.length) return EMPTY_LIST + if (!searchResult?.length && !localSearchedResult?.length) return EMPTY_LIST + const localProfiles: NextIDPersonaBindingsWithIdentifier[] = + localSearchedResult + ?.filter((x) => x.persona.publicKeyAsHex !== currentIdentifier?.identifier.publicKeyAsHex && x.profile) + .map((item) => { + const profile = item.profile! + return { + proofs: [ + { + platform: profile.network as FriendNetwork, + identity: profile.userId, + is_valid: true, + last_checked_at: '', + name: profile.userId, + created_at: '', + }, + ], + linkedPersona: item.persona, + activated_at: '', + persona: item.persona.publicKeyAsHex, + isLocal: true, + } + }) ?? EMPTY_LIST const profiles: NextIDPersonaBindingsWithIdentifier[] = searchResult - .filter((x) => x.persona !== currentIdentifier?.identifier.publicKeyAsHex) - .map((item) => { - const filtered = item.proofs.filter(profilesFilter) - const identifier = ECKeyIdentifier.fromHexPublicKeyK256(item.persona).expect( - `${item.persona} should be a valid hex public key in k256`, - ) - filtered.sort((a, b) => PlatformSort[a.platform] - PlatformSort[b.platform]) - const searchItem = filtered.findIndex((x) => x.identity === searchValue || x.name === searchValue) - if (searchItem !== -1) filtered.unshift(filtered.splice(searchItem, 1)[0]) - return { - proofs: uniqBy(filtered, ({ identity }) => identity), - linkedPersona: identifier, - activated_at: item.activated_at, - persona: item.persona, - isLocal: localList - ? localList.some((x) => x.persona.publicKeyAsHex === identifier.publicKeyAsHex) - : false, - } - }) - return uniqBy(profiles, ({ linkedPersona }) => linkedPersona.publicKeyAsHex) - }, [searchResult, localList, currentIdentifier]) + ? searchResult + .filter((x) => x.persona !== currentIdentifier?.identifier.publicKeyAsHex) + .map((item) => { + const filtered = item.proofs.filter(profilesFilter) + const identifier = ECKeyIdentifier.fromHexPublicKeyK256(item.persona).expect( + `${item.persona} should be a valid hex public key in k256`, + ) + filtered.sort((a, b) => PlatformSort[a.platform] - PlatformSort[b.platform]) + const searchItem = filtered.findIndex((x) => x.identity === searchValue || x.name === searchValue) + if (searchItem !== -1) filtered.unshift(filtered.splice(searchItem, 1)[0]) + return { + proofs: uniqBy(filtered, ({ identity }) => identity), + linkedPersona: identifier, + activated_at: item.activated_at, + persona: item.persona, + isLocal: localList + ? localList.some((x) => x.persona.publicKeyAsHex === identifier.publicKeyAsHex) + : false, + } + }) + : EMPTY_LIST + return uniqBy( + localProfiles ? localProfiles.concat(profiles) : profiles, + ({ linkedPersona }) => linkedPersona.publicKeyAsHex, + ) + }, [searchResult, localList, currentIdentifier, localSearchedResult]) } diff --git a/packages/mask/src/extension/popups/hooks/useGasOptionsMenu.tsx b/packages/mask/src/extension/popups/hooks/useGasOptionsMenu.tsx index a0e873496370..38d580a3a535 100644 --- a/packages/mask/src/extension/popups/hooks/useGasOptionsMenu.tsx +++ b/packages/mask/src/extension/popups/hooks/useGasOptionsMenu.tsx @@ -33,6 +33,9 @@ const useStyles = makeStyles()((theme) => ({ paddingBottom: 4, border: 'unset', }, + '&.Mui-focusVisible': { + backgroundColor: 'transparent', + }, }, optionName: { fontSize: 14, diff --git a/packages/mask/src/extension/popups/hooks/useHasPassword.ts b/packages/mask/src/extension/popups/hooks/useHasPassword.ts index aa794ef0b6a1..4186d053abab 100644 --- a/packages/mask/src/extension/popups/hooks/useHasPassword.ts +++ b/packages/mask/src/extension/popups/hooks/useHasPassword.ts @@ -1,14 +1,14 @@ -import { useAsyncRetry } from 'react-use' import { useEffect } from 'react' import Services from '#services' import { CrossIsolationMessages } from '@masknet/shared-base' +import { useQuery } from '@tanstack/react-query' export function useHasPassword() { - const { value: hasPassword, loading, retry } = useAsyncRetry(Services.Wallet.hasPassword, []) + const { data: hasPassword, isLoading, refetch } = useQuery(['@@has-password'], Services.Wallet.hasPassword) useEffect(() => { - return CrossIsolationMessages.events.passwordStatusUpdated.on(retry) - }, [retry]) + return CrossIsolationMessages.events.passwordStatusUpdated.on(() => refetch()) + }, [refetch]) - return { hasPassword, loading } + return { hasPassword, loading: isLoading } } diff --git a/packages/mask/src/extension/popups/hooks/useSearchValue.ts b/packages/mask/src/extension/popups/hooks/useSearchValue.ts index 9cd530601fa1..267ab4d04c8f 100644 --- a/packages/mask/src/extension/popups/hooks/useSearchValue.ts +++ b/packages/mask/src/extension/popups/hooks/useSearchValue.ts @@ -11,7 +11,7 @@ export function useSearchValue(value: string, type?: NextIDPlatform): AsyncState if (value.endsWith('.eth')) return (await ENS.lookup(value))?.toLowerCase() - if (value.endsWith('.lens')) return (await Lens.getProfileByHandle(value)).ownedBy?.toLowerCase() + if (value.endsWith('.lens')) return (await Lens.getProfileByHandle(value)).ownedBy.address?.toLowerCase() return value.toLowerCase() }, [value]) diff --git a/packages/mask/src/extension/popups/modals/AddContactModal/index.tsx b/packages/mask/src/extension/popups/modals/AddContactModal/index.tsx index 1a3f64f5374d..4c79bcb7c339 100644 --- a/packages/mask/src/extension/popups/modals/AddContactModal/index.tsx +++ b/packages/mask/src/extension/popups/modals/AddContactModal/index.tsx @@ -97,7 +97,7 @@ function AddContactDrawer({ onConfirm, address, name, setName, setAddress, ...re { diff --git a/packages/mask/src/extension/popups/modals/ChangeBackupPasswordModal/index.tsx b/packages/mask/src/extension/popups/modals/ChangeBackupPasswordModal/index.tsx index aa306b2f9064..270afafc47ff 100644 --- a/packages/mask/src/extension/popups/modals/ChangeBackupPasswordModal/index.tsx +++ b/packages/mask/src/extension/popups/modals/ChangeBackupPasswordModal/index.tsx @@ -27,7 +27,6 @@ export const ChangeBackupPasswordModal = memo(function Cha const { control, - resetField, handleSubmit, formState: { errors, isValid, isSubmitting, isDirty }, } = useForm({ @@ -44,9 +43,9 @@ export const ChangeBackupPasswordModal = memo(function Cha z .object({ oldPassword: z - .string(t('popups_settings_backup_password_invalid')) - .min(8, t('popups_settings_backup_password_invalid')) - .max(20, t('popups_settings_backup_password_invalid')) + .string() + .min(8) + .max(20) .refine( (oldPassword) => oldPassword === user.backupPassword, t('popups_backup_password_incorrect'), @@ -57,25 +56,21 @@ export const ChangeBackupPasswordModal = memo(function Cha ), newPassword: z .string(t('popups_settings_backup_password_invalid')) - .min(8, t('popups_settings_backup_password_invalid')) - .max(20, t('popups_settings_backup_password_invalid')) + .min(8) + .max(20) .refine( (newPassword) => MATCH_PASSWORD_RE.test(newPassword), t('popups_settings_backup_password_invalid'), ), repeatPassword: z - .string(t('popups_settings_backup_password_invalid')) - .min(8, t('popups_settings_backup_password_invalid')) - .max(20, t('popups_settings_backup_password_invalid')) + .string() + .min(8) + .max(20) .refine( (repeatPassword) => MATCH_PASSWORD_RE.test(repeatPassword), t('popups_settings_backup_password_invalid'), ), }) - .refine((data) => data.oldPassword === user.backupPassword, { - message: t('popups_backup_password_incorrect'), - path: ['oldPassword'], - }) .refine((data) => data.newPassword !== data.oldPassword, { message: t('popups_settings_new_backup_password_error_tips'), path: ['newPassword'], @@ -129,9 +124,16 @@ export const ChangeBackupPasswordModal = memo(function Cha {...field} placeholder={t('password')} autoFocus - onClear={() => resetField('oldPassword', { defaultValue: '' })} - error={!!errors.oldPassword?.message} - helperText={errors.oldPassword?.message} + error={ + errors.oldPassword?.type !== 'too_small' && errors.oldPassword?.type !== 'too_big' + ? !!errors.oldPassword?.message + : false + } + helperText={ + errors.oldPassword?.type !== 'too_small' && errors.oldPassword?.type !== 'too_big' + ? errors.oldPassword?.message + : '' + } /> ) }} @@ -144,9 +146,16 @@ export const ChangeBackupPasswordModal = memo(function Cha resetField('newPassword', { defaultValue: '' })} - error={!!errors.newPassword?.message} - helperText={errors.newPassword?.message} + error={ + errors.newPassword?.type !== 'too_small' && errors.newPassword?.type !== 'too_big' + ? !!errors.newPassword?.message + : false + } + helperText={ + errors.newPassword?.type !== 'too_small' && errors.newPassword?.type !== 'too_big' + ? errors.newPassword?.message + : '' + } /> )} /> @@ -157,9 +166,16 @@ export const ChangeBackupPasswordModal = memo(function Cha resetField('repeatPassword', { defaultValue: '' })} - error={!!errors.repeatPassword?.message} - helperText={errors.repeatPassword?.message} + error={ + errors.repeatPassword?.type !== 'too_small' && errors.repeatPassword?.type !== 'too_big' + ? !!errors.repeatPassword?.message + : false + } + helperText={ + errors.repeatPassword?.type !== 'too_small' && errors.repeatPassword?.type !== 'too_big' + ? errors.repeatPassword?.message + : '' + } /> )} /> diff --git a/packages/mask/src/extension/popups/modals/EditContactModal/index.tsx b/packages/mask/src/extension/popups/modals/EditContactModal/index.tsx index e843c547d2eb..4af97f445a3c 100644 --- a/packages/mask/src/extension/popups/modals/EditContactModal/index.tsx +++ b/packages/mask/src/extension/popups/modals/EditContactModal/index.tsx @@ -134,7 +134,7 @@ function EditContactDrawer({ onConfirm, address, name, setName, type, ...rest }: inputProps={{ style: { textAlign: 'center' } }} classes={{ root: classes.inputRoot }} spellCheck={false} - placeholder={t('wallet_name_wallet')} + placeholder={t('name')} className={classes.input} value={name} onChange={(ev) => { diff --git a/packages/mask/src/extension/popups/modals/SelectAppearanceModal/index.tsx b/packages/mask/src/extension/popups/modals/SelectAppearanceModal/index.tsx index c18de4c4433f..63188b1632bc 100644 --- a/packages/mask/src/extension/popups/modals/SelectAppearanceModal/index.tsx +++ b/packages/mask/src/extension/popups/modals/SelectAppearanceModal/index.tsx @@ -11,6 +11,7 @@ import { useAppearance } from '../../../../../shared-ui/index.js' const useStyles = makeStyles()((theme) => ({ item: { padding: theme.spacing(1.5), + borderRadius: 8, }, icon: { minWidth: 24, diff --git a/packages/mask/src/extension/popups/modals/SelectLanguageModal/index.tsx b/packages/mask/src/extension/popups/modals/SelectLanguageModal/index.tsx index 3239a839b5c5..a3f05a9b3329 100644 --- a/packages/mask/src/extension/popups/modals/SelectLanguageModal/index.tsx +++ b/packages/mask/src/extension/popups/modals/SelectLanguageModal/index.tsx @@ -1,34 +1,17 @@ import { memo, useCallback, useMemo } from 'react' import { ActionModal, type ActionModalBaseProps } from '../../components/index.js' import { useI18N } from '../../../../utils/i18n-next-ui.js' -import { List, ListItemButton, ListItemIcon, ListItemText, Radio } from '@mui/material' +import { List, ListItemButton, ListItemText, Radio } from '@mui/material' import { getEnumAsArray } from '@masknet/kit' import { LanguageOptions } from '@masknet/public-api' -import { Icons } from '@masknet/icons' import { makeStyles } from '@masknet/theme' import Services from '#services' import { useLanguage } from '../../../../../shared-ui/index.js' -const LANGUAGE_OPTIONS_ICON_MAP = { - [LanguageOptions.__auto__]: null, - [LanguageOptions.enUS]: , - [LanguageOptions.zhCN]: , - [LanguageOptions.zhTW]: , - [LanguageOptions.jaJP]: , - [LanguageOptions.koKR]: , -} - const useStyles = makeStyles()((theme) => ({ item: { padding: theme.spacing(1.5), - }, - icon: { - minWidth: 24, - height: 12, - '& > *': { - width: '16px !important', - height: '12px !important', - }, + borderRadius: 8, }, text: { fontWeight: 700, @@ -55,7 +38,7 @@ export const SelectLanguageModal = memo(function SelectLan [LanguageOptions.zhCN]: '简体中文', [LanguageOptions.zhTW]: '繁体中文', [LanguageOptions.jaJP]: '日本語', - [LanguageOptions.koKR]: '한국인', + [LanguageOptions.koKR]: '한국어', }), [t], ) @@ -64,13 +47,11 @@ export const SelectLanguageModal = memo(function SelectLan {getEnumAsArray(LanguageOptions).map((x) => { - const icon = LANGUAGE_OPTIONS_ICON_MAP[x.value] return ( handleLanguageChange(x.value)}> - {icon ? {icon} : null} diff --git a/packages/mask/src/extension/popups/modals/SetBackupPasswordModal/index.tsx b/packages/mask/src/extension/popups/modals/SetBackupPasswordModal/index.tsx index df46c1cd3a50..80bc4fd5299b 100644 --- a/packages/mask/src/extension/popups/modals/SetBackupPasswordModal/index.tsx +++ b/packages/mask/src/extension/popups/modals/SetBackupPasswordModal/index.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useState } from 'react' +import { memo, useCallback, useMemo, useState } from 'react' import { useAsyncFn } from 'react-use' import { useNavigate, useSearchParams } from 'react-router-dom' import { Box, Typography, useTheme } from '@mui/material' @@ -28,7 +28,6 @@ export const SetBackupPasswordModal = memo(function SetBac const validPassword = useCallback(() => { if (newPassword.length < 8 || newPassword.length > 20) { - setPasswordValidError(t('popups_settings_backup_password_invalid')) return } else if (!MATCH_PASSWORD_RE.test(newPassword)) { setPasswordValidError(t('popups_settings_backup_password_invalid')) @@ -57,16 +56,22 @@ export const SetBackupPasswordModal = memo(function SetBac navigate(-1) }, [newPassword, passwordMatched, passwordValidError, updateUser, params, to]) + const disabled = useMemo(() => { + if (!newPassword.length || !repeatPassword.length) return true + if (newPassword.length < 8 || newPassword.length > 20) return true + + if (repeatPassword.length < 8 || repeatPassword.length > 20) return true + + if (!!passwordValidError || !passwordMatched) return true + + return false + }, [newPassword, repeatPassword, passwordMatched, passwordValidError]) + return ( + {t('confirm')} }> @@ -79,7 +84,6 @@ export const SetBackupPasswordModal = memo(function SetBac value={newPassword} error={!!passwordValidError} helperText={passwordValidError} - onClear={() => setNewPassword('')} /> (function SetBac onBlur={validRepeatPassword} error={!passwordMatched} helperText={!passwordMatched ? t('popups_backup_password_inconsistency') : ''} - onClear={() => setRepeatPassword('')} /> diff --git a/packages/mask/src/extension/popups/modals/SwitchPersonaModal/index.tsx b/packages/mask/src/extension/popups/modals/SwitchPersonaModal/index.tsx index 623252a92d9e..49b91cbcec10 100644 --- a/packages/mask/src/extension/popups/modals/SwitchPersonaModal/index.tsx +++ b/packages/mask/src/extension/popups/modals/SwitchPersonaModal/index.tsx @@ -5,7 +5,7 @@ import { useI18N } from '../../../../utils/i18n-next-ui.js' import { ActionButton, makeStyles } from '@masknet/theme' import { PersonaContext } from '@masknet/shared' import { PersonaItem } from './PersonaItem.js' -import { DashboardRoutes, type PersonaInformation } from '@masknet/shared-base' +import { DashboardRoutes, PopupRoutes, type PersonaInformation } from '@masknet/shared-base' import Services from '#services' import { useNavigate } from 'react-router-dom' import { Icons } from '@masknet/icons' @@ -65,7 +65,7 @@ export const SwitchPersonaModal = memo(function SwitchPers size="small" variant="outlined" startIcon={} - onClick={() => handleOpenDashboard(DashboardRoutes.Settings)}> + onClick={() => navigate(PopupRoutes.Settings)}> {t('backup')} (function Ver setPassword(e.target.value) setPasswordValid(MATCH_PASSWORD_RE.test(e.target.value)) }} - onClear={() => { - setPasswordMatched(true) - setPassword('') - }} value={password} error={!passwordMatched} helperText={ diff --git a/packages/mask/src/extension/popups/modals/WalletRemoveModal/index.tsx b/packages/mask/src/extension/popups/modals/WalletRemoveModal/index.tsx index 718986a9af92..e1f9affc4a13 100644 --- a/packages/mask/src/extension/popups/modals/WalletRemoveModal/index.tsx +++ b/packages/mask/src/extension/popups/modals/WalletRemoveModal/index.tsx @@ -46,9 +46,10 @@ function WalletRemoveDrawer({ wallet, error, password, setPassword, setError, .. if (!remainWallets.includes(nextWallet)) nextWallet = remainWallets[0] await Web3.removeWallet?.(wallet.address, password, { providerType: ProviderType.MaskWallet }) + await Web3.connect({ providerType: ProviderType.MaskWallet, - account: nextWallet.address, + account: nextWallet?.address ?? '', }) rest.onClose?.() diff --git a/packages/mask/src/extension/popups/pages/Friends/AccountRender/index.tsx b/packages/mask/src/extension/popups/pages/Friends/AccountRender/index.tsx index bd394a0d9e0b..c98c096b740a 100644 --- a/packages/mask/src/extension/popups/pages/Friends/AccountRender/index.tsx +++ b/packages/mask/src/extension/popups/pages/Friends/AccountRender/index.tsx @@ -1,19 +1,21 @@ import { safeUnreachable } from '@masknet/kit' -import { EnhanceableSite, NextIDPlatform, type BindingProof } from '@masknet/shared-base' +import { EnhanceableSite, NextIDPlatform } from '@masknet/shared-base' import { memo } from 'react' import { Account } from '../ContactCard/Account/index.js' import { SocialAccount } from '../ContactCard/SocialAccount/index.js' import { Account as DetailAccount } from '../Detail/Account/index.js' import { SocialAccount as DetailSocialAccount } from '../Detail/SocialAccount/index.js' +import type { Profile } from '../common.js' interface AccountRenderProps { - profile: BindingProof + profile: Profile detail?: boolean } export const AccountRender = memo(function AccountRender({ profile, detail }) { switch (profile.platform) { case NextIDPlatform.Twitter: + case EnhanceableSite.Twitter: return detail ? ( (function AccountRender({ p case NextIDPlatform.RSS3: case NextIDPlatform.NextID: return null + case EnhanceableSite.Facebook: + return detail ? ( + + ) : ( + + ) + case EnhanceableSite.Instagram: + return detail ? ( + + ) : ( + + ) default: safeUnreachable(profile.platform) return null diff --git a/packages/mask/src/extension/popups/pages/Friends/ContactCard/ConnectedAccounts/index.tsx b/packages/mask/src/extension/popups/pages/Friends/ContactCard/ConnectedAccounts/index.tsx index e363902c4127..d8413e6842cc 100644 --- a/packages/mask/src/extension/popups/pages/Friends/ContactCard/ConnectedAccounts/index.tsx +++ b/packages/mask/src/extension/popups/pages/Friends/ContactCard/ConnectedAccounts/index.tsx @@ -1,9 +1,10 @@ import { memo } from 'react' import { makeStyles } from '@masknet/theme' import { Box, Typography, useTheme, ButtonBase } from '@mui/material' -import { type BindingProof, PopupRoutes } from '@masknet/shared-base' +import { PopupRoutes } from '@masknet/shared-base' import { useNavigate } from 'react-router-dom' import { AccountRender } from '../../AccountRender/index.js' +import type { Profile } from '../../common.js' const useStyles = makeStyles()((theme) => ({ connectedAccounts: { @@ -28,7 +29,7 @@ const useStyles = makeStyles()((theme) => ({ interface ConnectedAccountsProps { avatar?: string - profiles?: BindingProof[] + profiles?: Profile[] nextId?: string publicKey?: string isLocal?: boolean diff --git a/packages/mask/src/extension/popups/pages/Friends/ContactCard/index.tsx b/packages/mask/src/extension/popups/pages/Friends/ContactCard/index.tsx index f97bfb65ffee..ef86ff974611 100644 --- a/packages/mask/src/extension/popups/pages/Friends/ContactCard/index.tsx +++ b/packages/mask/src/extension/popups/pages/Friends/ContactCard/index.tsx @@ -3,22 +3,22 @@ import { useNavigate } from 'react-router-dom' import { useEverSeen } from '@masknet/shared-base-ui' import { useMutation, useQueryClient, type InfiniteData } from '@tanstack/react-query' import { Icons } from '@masknet/icons' -import { makeStyles, usePopupCustomSnackbar } from '@masknet/theme' +import { ActionButton, makeStyles, usePopupCustomSnackbar } from '@masknet/theme' import { Box, Typography, Link, useTheme, ButtonBase as Button, Avatar } from '@mui/material' import { formatPersonaFingerprint, - type BindingProof, PopupRoutes, ProfileIdentifier, ECKeyIdentifier, + NextIDPlatform, } from '@masknet/shared-base' import { CopyButton, PersonaContext } from '@masknet/shared' -import { NextIDPlatform } from '@masknet/shared-base' import { attachNextIDToProfile } from '../../../../../utils/utils.js' import { ConnectedAccounts } from './ConnectedAccounts/index.js' import { useI18N } from '../../../../../utils/i18n-next-ui.js' import Services from '#services' import { type Friend, useFriendProfiles } from '../../../hooks/index.js' +import { type Profile } from '../common.js' const useStyles = makeStyles()((theme) => ({ card: { @@ -54,23 +54,11 @@ const useStyles = makeStyles()((theme) => ({ fontSize: 12, color: theme.palette.maskColor.second, }, - addButton: { - display: 'flex', - padding: '8px 12px', - justifyContent: 'center', - alignItems: 'center', - borderRadius: '99px', - background: theme.palette.maskColor.main, - color: theme.palette.maskColor.white, - fontSize: '12px', - lineHeight: '16px', - fontWeight: 700, - }, })) interface ContactCardProps { avatar?: string - proofProfiles?: BindingProof[] + proofProfiles?: Profile[] nextId?: string publicKey?: string isLocal?: boolean @@ -95,7 +83,7 @@ export const ContactCard = memo(function ContactCard({ const [seen, ref] = useEverSeen() const { currentPersona } = PersonaContext.useContainer() const { t } = useI18N() - const profiles = useFriendProfiles(seen, nextId, profile?.userId) + const profiles = useFriendProfiles(seen, nextId, profile) const rawPublicKey = currentPersona?.identifier.rawPublicKey const queryClient = useQueryClient() @@ -213,7 +201,7 @@ export const ContactCard = memo(function ContactCard({ avatar, publicKey, nextId, - profiles, + profiles: proofProfiles ? proofProfiles : profiles, isLocal, }, }) @@ -223,9 +211,13 @@ export const ContactCard = memo(function ContactCard({ ) : ( - + )} ({ accounts: { @@ -21,7 +21,7 @@ const useStyles = makeStyles()((theme) => ({ })) interface ConnectedAccountsProps { - profiles: BindingProof[] + profiles: Profile[] } export const ConnectedAccounts = memo(function ConnectedAccounts({ profiles }) { diff --git a/packages/mask/src/extension/popups/pages/Friends/Detail/UI.tsx b/packages/mask/src/extension/popups/pages/Friends/Detail/UI.tsx index c4573989e441..4bba6c875a70 100644 --- a/packages/mask/src/extension/popups/pages/Friends/Detail/UI.tsx +++ b/packages/mask/src/extension/popups/pages/Friends/Detail/UI.tsx @@ -1,15 +1,14 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - import { makeStyles } from '@masknet/theme' import { memo, useCallback } from 'react' import { Box, Avatar, Typography, Link } from '@mui/material' import { Icons } from '@masknet/icons' import { useNavigate } from 'react-router-dom' -import { formatPersonaFingerprint, type BindingProof } from '@masknet/shared-base' +import { formatPersonaFingerprint } from '@masknet/shared-base' import { useTheme } from '@mui/system' import { CopyButton, EmptyStatus } from '@masknet/shared' import { ConnectedAccounts } from './ConnectAccounts/index.js' import { useI18N } from '../../../../../utils/i18n-next-ui.js' +import type { Profile } from '../common.js' const useStyles = makeStyles()((theme) => ({ container: { @@ -75,7 +74,7 @@ const useStyles = makeStyles()((theme) => ({ export interface FriendsDetailUIProps { avatar?: string - profiles: BindingProof[] + profiles: Profile[] nextId: string publicKey?: string isLocal?: boolean diff --git a/packages/mask/src/extension/popups/pages/Friends/Home/UI.tsx b/packages/mask/src/extension/popups/pages/Friends/Home/UI.tsx index 937eeab3fd9d..f6546de960a6 100644 --- a/packages/mask/src/extension/popups/pages/Friends/Home/UI.tsx +++ b/packages/mask/src/extension/popups/pages/Friends/Home/UI.tsx @@ -1,5 +1,3 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - import { makeStyles, LoadingBase } from '@masknet/theme' import { memo } from 'react' import { Box, Typography } from '@mui/material' @@ -63,7 +61,7 @@ export const FriendsHomeUI = memo(function FriendsHomeUI({ - {loading && !(searchValue ? searchResult.length : friends.length) ? ( + {loading ? (
    {t('loading')} diff --git a/packages/mask/src/extension/popups/pages/Friends/Home/index.tsx b/packages/mask/src/extension/popups/pages/Friends/Home/index.tsx index c935097a3f67..ad5d048b9f2f 100644 --- a/packages/mask/src/extension/popups/pages/Friends/Home/index.tsx +++ b/packages/mask/src/extension/popups/pages/Friends/Home/index.tsx @@ -1,23 +1,44 @@ import { memo, useState, useMemo } from 'react' -import { FriendsHomeUI } from './UI.js' -import { useFriendsPaged, useTitle, useSearchValue, useFriendsFromSearch } from '../../../hooks/index.js' -import { EMPTY_LIST } from '@masknet/shared-base' -import { useI18N } from '../../../../../utils/i18n-next-ui.js' +import { EMPTY_LIST, NextIDPlatform } from '@masknet/shared-base' import { resolveNextIDPlatform } from '@masknet/shared' import { useInfiniteQuery } from '@tanstack/react-query' -import { NextIDProof } from '@masknet/web3-providers' +import { NextIDProof, Fuse } from '@masknet/web3-providers' +import { FriendsHomeUI } from './UI.js' +import { + useFriendsPaged, + useTitle, + useSearchValue, + useFriendsFromSearch, + useFriendFromList, +} from '../../../hooks/index.js' +import { useI18N } from '../../../../../utils/i18n-next-ui.js' const FriendsHome = memo(function FriendsHome() { const { t } = useI18N() useTitle(t('popups_encrypted_friends')) - const { data, fetchNextPage, isLoading, refetch } = useFriendsPaged() + const { data, fetchNextPage, isLoading, refetch, status, fetchRelationStatus, records } = useFriendsPaged() const friends = useMemo(() => data?.pages.flatMap((x) => x.friends) ?? EMPTY_LIST, [data]) const [searchValue, setSearchValue] = useState('') const type = resolveNextIDPlatform(searchValue) const { loading: resolveLoading, value: keyword = '' } = useSearchValue(searchValue, type) + const fuse = useMemo(() => { + return Fuse.create(records, { + keys: ['profile.userId'], + isCaseSensitive: false, + ignoreLocation: true, + threshold: 0, + }) + }, [records]) + const searchedRecords = useMemo(() => { + if (!keyword || type !== NextIDPlatform.Twitter) return EMPTY_LIST + return fuse.search(keyword).map((item) => item.item) + }, [fuse, keyword, type]) + const { isLoading: isSearchRecordLoading, data: localSearchedList = EMPTY_LIST } = + useFriendFromList(searchedRecords) const { isLoading: searchLoading, + isInitialLoading, data: searchResultArray, fetchNextPage: fetchNextSearchPage, } = useInfiniteQuery( @@ -35,12 +56,17 @@ const FriendsHome = memo(function FriendsHome() { }, ) const searchResult = useMemo(() => searchResultArray?.pages.flat() ?? EMPTY_LIST, [searchResultArray]) - const searchedList = useFriendsFromSearch(searchResult, friends, keyword) - + const searchedList = useFriendsFromSearch(localSearchedList, searchResult, friends, keyword) return ( & { + platform: NextIDPlatform | EnhanceableSite.Twitter | EnhanceableSite.Facebook | EnhanceableSite.Instagram +} + +export type FriendNetwork = EnhanceableSite.Twitter | EnhanceableSite.Facebook | EnhanceableSite.Instagram export type SupportedPlatforms = | NextIDPlatform.Ethereum diff --git a/packages/mask/src/extension/popups/pages/Personas/ExportPrivateKey/index.tsx b/packages/mask/src/extension/popups/pages/Personas/ExportPrivateKey/index.tsx index 10111e0428ec..6a78a5fa629a 100644 --- a/packages/mask/src/extension/popups/pages/Personas/ExportPrivateKey/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/ExportPrivateKey/index.tsx @@ -9,7 +9,7 @@ import { useAsync, useCopyToClipboard } from 'react-use' import { Trans } from 'react-i18next' import { BottomController } from '../../../components/BottomController/index.js' import { useNavigate } from 'react-router-dom' -import { DashboardRoutes, PopupRoutes } from '@masknet/shared-base' +import { PopupRoutes } from '@masknet/shared-base' import { ActionButton, usePopupCustomSnackbar } from '@masknet/theme' const ExportPrivateKey = memo(function ExportPrivateKey() { @@ -56,12 +56,7 @@ const ExportPrivateKey = memo(function ExportPrivateKey() { a: ( { - browser.tabs.create({ - active: true, - url: browser.runtime.getURL( - `/dashboard.html#${DashboardRoutes.Settings}?mode=true`, - ), - }) + navigate(PopupRoutes.Settings) }} /> ), diff --git a/packages/mask/src/extension/popups/pages/Personas/Home/UI.tsx b/packages/mask/src/extension/popups/pages/Personas/Home/UI.tsx index 0bb17225e40d..54d06a148898 100644 --- a/packages/mask/src/extension/popups/pages/Personas/Home/UI.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/Home/UI.tsx @@ -1,5 +1,3 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - import urlcat from 'urlcat' import { Icons } from '@masknet/icons' import { @@ -133,6 +131,12 @@ const useStyles = makeStyles()((theme) => ({ paddingLeft: 16, paddingRight: 16, }, + groupedButton: { + // Increasing priority instead of using !important. + '&&': { + color: theme.palette.maskColor.second, + }, + }, panel: { padding: theme.spacing(2), background: theme.palette.maskColor.bottom, @@ -213,7 +217,7 @@ export const PersonaHomeUI = memo( value === PopupHomeTabType.ConnectedWallets && hasPaymentPassword ) { - navigate(urlcat(PopupRoutes.Unlock, { from: PopupRoutes.Personas, goBack: true, popup: true })) + navigate(urlcat(PopupRoutes.Wallet, { from: PopupRoutes.Personas, goBack: true, popup: true })) return } onChange(event, value) @@ -262,7 +266,7 @@ export const PersonaHomeUI = memo( + classes={{ root: classes.tabs, grouped: classes.groupedButton }}> diff --git a/packages/mask/src/extension/popups/pages/Personas/Logout/index.tsx b/packages/mask/src/extension/popups/pages/Personas/Logout/index.tsx index f9716d10c59c..de4c72ee7df3 100644 --- a/packages/mask/src/extension/popups/pages/Personas/Logout/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/Logout/index.tsx @@ -199,10 +199,6 @@ export const LogoutUI = memo( if (paymentPasswordError) setPaymentPasswordError('') setPaymentPassword(e.target.value) }} - onClear={() => { - setPaymentPassword('') - setPaymentPasswordError('') - }} /> ) } else if (backupPassword) { @@ -215,10 +211,6 @@ export const LogoutUI = memo( setPassword(e.target.value) }} error={error} - onClear={() => { - setPassword('') - setError(false) - }} helperText={error ? t('popups_password_do_not_match') : ''} /> ) @@ -237,10 +229,6 @@ export const LogoutUI = memo( setPassword(e.target.value) }} error={error} - onClear={() => { - setPassword('') - setError(false) - }} helperText={error ? t('popups_password_do_not_match') : ''} /> ) diff --git a/packages/mask/src/extension/popups/pages/Personas/components/PersonaHeader/UI.tsx b/packages/mask/src/extension/popups/pages/Personas/components/PersonaHeader/UI.tsx index 1fe461e0eabd..2297cecec559 100644 --- a/packages/mask/src/extension/popups/pages/Personas/components/PersonaHeader/UI.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/components/PersonaHeader/UI.tsx @@ -1,4 +1,3 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR import { memo } from 'react' import { makeStyles } from '@masknet/theme' import { Avatar, Box, Link, Typography } from '@mui/material' diff --git a/packages/mask/src/extension/popups/pages/Settings/index.tsx b/packages/mask/src/extension/popups/pages/Settings/index.tsx index ca0040d18976..c8a6bf24f6f5 100644 --- a/packages/mask/src/extension/popups/pages/Settings/index.tsx +++ b/packages/mask/src/extension/popups/pages/Settings/index.tsx @@ -1,24 +1,24 @@ +import Services from '#services' +import { env } from '@masknet/flags' +import { Icons } from '@masknet/icons' +import { Appearance, LanguageOptions } from '@masknet/public-api' +import { DashboardRoutes, PopupModalRoutes, Sniffings } from '@masknet/shared-base' +import { openWindow } from '@masknet/shared-base-ui' +import { makeStyles } from '@masknet/theme' +import { Box, List, ListItem, ListItemText, Typography, useTheme } from '@mui/material' import { memo, useCallback, useMemo } from 'react' import { Trans } from 'react-i18next' -import { Box, List, ListItem, ListItemText, Typography, useTheme } from '@mui/material' +import { UserContext, useAppearance, useLanguage } from '../../../../../shared-ui/index.js' import { useI18N } from '../../../../utils/i18n-next-ui.js' -import { useTitle } from '../../hooks/useTitle.js' -import { makeStyles } from '@masknet/theme' -import { Icons } from '@masknet/icons' import { NormalHeader, useModalNavigate } from '../../components/index.js' -import { env } from '@masknet/flags' -import { UserContext, useAppearance, useLanguage } from '../../../../../shared-ui/index.js' -import { Appearance, LanguageOptions } from '@masknet/public-api' -import { openWindow } from '@masknet/shared-base-ui' -import { DashboardRoutes, PopupModalRoutes, Sniffings } from '@masknet/shared-base' import { useSupportedSites } from '../../hooks/useSupportedSites.js' -import Services from '#services' +import { useTitle } from '../../hooks/useTitle.js' const useStyles = makeStyles()((theme) => ({ container: { padding: theme.spacing(2), flex: 1, - maxHeight: 474, + paddingBottom: 80, overflow: 'auto', display: 'flex', flexDirection: 'column', @@ -64,13 +64,16 @@ const useStyles = makeStyles()((theme) => ({ cursor: 'pointer', padding: theme.spacing(1.5, 0), borderBottom: `1px solid ${theme.palette.maskColor.line}`, - '&:first-child': { + '&:first-of-type': { paddingTop: 0, }, - '&:last-child': { + '&:last-of-type': { paddingBottom: 0, borderBottom: 'none', }, + '&:hover > span': { + color: theme.palette.maskColor.main, + }, }, listItemText: { margin: 0, @@ -87,10 +90,14 @@ const useStyles = makeStyles()((theme) => ({ lineHeight: '16px', fontWeight: 700, }, + arrow: { + color: theme.palette.maskColor.second, + }, })) const FEEDBACK_MAIL = 'Support@mask.io' -const FAQ_LINK = 'realmasknetwork.notion.site' +const FAQ_LINK = + 'realmasknetwork.notion.site/realmasknetwork/Mask-Network-2-0-Setting-Up-Features-The-Broader-Ecosystem-e4b3e24182e045a58bdb5549c0daea82' const HOME_LINK = 'Mask.io' const Settings = memo(function Settings() { @@ -170,7 +177,7 @@ const Settings = memo(function Settings() { return ( <> - + @@ -192,7 +199,7 @@ const Settings = memo(function Settings() { primary={t('popups_settings_language')} secondary={LANGUAGE_OPTIONS_MAP[lang]} /> - + - + } /> - + @@ -239,20 +246,21 @@ const Settings = memo(function Settings() { - + { + if (!user.backupPassword) { + modalNavigate(PopupModalRoutes.SetBackupPassword) + } else { + handleOpenDashboard(DashboardRoutes.CloudBackup) + } + }}> { - if (!user.backupPassword) { - modalNavigate(PopupModalRoutes.SetBackupPassword) - } else { - handleOpenDashboard(DashboardRoutes.CloudBackup) - } - }} /> - + - + - + handleOpenDashboard(DashboardRoutes.RecoveryPersona)}> - + - + @@ -319,7 +329,7 @@ const Settings = memo(function Settings() { primary={t('popups_settings_feedback')} secondary={FEEDBACK_MAIL} /> - + - + - + @@ -353,13 +363,17 @@ const Settings = memo(function Settings() { - + + openWindow('https://github.com/DimensionDev/Maskbook', '_blank', { referrer: false }) + }> - + - + - + diff --git a/packages/mask/src/extension/popups/pages/Swap/SwapBox/index.tsx b/packages/mask/src/extension/popups/pages/Swap/SwapBox/index.tsx index 528c0c1bc67e..ad6567cd3f21 100644 --- a/packages/mask/src/extension/popups/pages/Swap/SwapBox/index.tsx +++ b/packages/mask/src/extension/popups/pages/Swap/SwapBox/index.tsx @@ -2,8 +2,7 @@ import { useMemo } from 'react' import { useLocation } from 'react-router-dom' import { NetworkPluginID } from '@masknet/shared-base' import { useChainContext, useFungibleToken } from '@masknet/web3-hooks-base' -import type { FungibleToken } from '@masknet/web3-shared-base' -import { createERC20Token, type ChainId, type SchemaType } from '@masknet/web3-shared-evm' +import { createERC20Token } from '@masknet/web3-shared-evm' import { Trader } from '@masknet/plugin-trader' export function SwapBox() { @@ -26,10 +25,5 @@ export function SwapBox() { }, [chainId, address, name, symbol, decimals]) const { data: coin } = useFungibleToken(NetworkPluginID.PLUGIN_EVM, address ?? '', fallbackToken, { chainId }) - return ( - } - chainId={chainId} - /> - ) + return } diff --git a/packages/mask/src/extension/popups/pages/Swap/index.tsx b/packages/mask/src/extension/popups/pages/Swap/index.tsx index 65a7b573cae8..f672e5755976 100644 --- a/packages/mask/src/extension/popups/pages/Swap/index.tsx +++ b/packages/mask/src/extension/popups/pages/Swap/index.tsx @@ -1,17 +1,17 @@ -import { useEffect, useMemo } from 'react' +import { SiteAdaptorContextRef } from '@masknet/plugin-infra/dom' import { AllProviderTradeContext } from '@masknet/plugin-trader' import { Appearance } from '@masknet/public-api' -import { SharedContextProvider } from '@masknet/shared' +import { SharedContextProvider, SwapPageModals } from '@masknet/shared' +import { openWindow } from '@masknet/shared-base-ui' import { applyMaskColorVars, makeStyles } from '@masknet/theme' import { ChainContextProvider, DefaultWeb3ContextProvider } from '@masknet/web3-hooks-base' import { Typography } from '@mui/material' +import { useEffect, useMemo } from 'react' +import { TwitterAdaptor } from '../../../../../shared/site-adaptors/implementations/twitter.com.js' import { useI18N } from '../../../../utils/index.js' import { NetworkSelector } from '../../components/NetworkSelector/index.js' import { useTokenParams } from '../../hooks/index.js' import { SwapBox } from './SwapBox/index.js' -import { SiteAdaptorContextRef } from '@masknet/plugin-infra/dom' -import { TwitterAdaptor } from '../../../../../shared/site-adaptors/implementations/twitter.com.js' -import { openWindow } from '@masknet/shared-base-ui' const useStyles = makeStyles()((theme) => { return { @@ -116,6 +116,7 @@ export default function SwapPage() {
    +
    ) } diff --git a/packages/mask/src/extension/popups/pages/Wallet/CollectibleDetail/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/CollectibleDetail/index.tsx index 39479d79f88c..2096263a820d 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/CollectibleDetail/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/CollectibleDetail/index.tsx @@ -7,7 +7,6 @@ import { useAccount, useNonFungibleAsset, useWeb3State } from '@masknet/web3-hoo import { TokenType, formatBalance } from '@masknet/web3-shared-base' import { SchemaType, formatTrait, isLensCollect, isLensFollower, isLensProfileAddress } from '@masknet/web3-shared-evm' import { Button, Skeleton, Typography } from '@mui/material' -import formatDateTime from 'date-fns/format' import { memo, useContext, useEffect, useMemo } from 'react' import { useLocation, useNavigate } from 'react-router-dom' import urlcat from 'urlcat' @@ -133,14 +132,6 @@ const useStyles = makeStyles()((theme) => ({ }, })) -/** - * timestamp in seconds or milliseconds - */ -const formatTimestamp = (timestamp: string) => { - const value = Number.parseInt(timestamp, 10) * (timestamp.length === 10 ? 1000 : 1) - return formatDateTime(new Date(value), 'yyyy-MM-dd HH:mm') -} - export const CollectibleDetail = memo(function CollectibleDetail() { const { classes, cx } = useStyles() const { t } = useI18N() diff --git a/packages/mask/src/extension/popups/pages/Wallet/ConnectWallet/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/ConnectWallet/index.tsx index 2e2db7e2d21e..cae01459beaa 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/ConnectWallet/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/ConnectWallet/index.tsx @@ -10,10 +10,10 @@ import { useWeb3UI, useWallets } from '@masknet/web3-hooks-base' import { getRegisteredWeb3Networks, getRegisteredWeb3Providers } from '@masknet/plugin-infra' import { Web3 } from '@masknet/web3-providers' import type { Web3Helper } from '@masknet/web3-helpers' +import Services from '#services' import { useTitle, PopupContext } from '../../../hooks/index.js' import { useI18N } from '../../../../../utils/index.js' import { useWalletLockStatus } from '../hooks/useWalletLockStatus.js' -import Services from '#services' const useStyles = makeStyles()((theme) => ({ box: { @@ -76,7 +76,7 @@ const ConnectWalletPage = memo(() => { }) if (isLocked && !lockStatusLoading) { - navigate(urlcat(PopupRoutes.Unlock, { from: PopupRoutes.SelectWallet, goBack: true, popup: true })) + navigate(urlcat(PopupRoutes.Wallet, { from: PopupRoutes.SelectWallet, goBack: true, popup: true })) return } diff --git a/packages/mask/src/extension/popups/pages/Wallet/CreateWallet/Derive.tsx b/packages/mask/src/extension/popups/pages/Wallet/CreateWallet/Derive.tsx index 52d0b4535c8a..4194dfa1bcc6 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/CreateWallet/Derive.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/CreateWallet/Derive.tsx @@ -1,9 +1,10 @@ import Services from '#services' import { Icons } from '@masknet/icons' import { defer, timeout } from '@masknet/kit' -import { EMPTY_LIST, NetworkPluginID, type Wallet } from '@masknet/shared-base' +import { EMPTY_LIST, NetworkPluginID } from '@masknet/shared-base' +import { queryClient } from '@masknet/shared-base-ui' import { ActionButton, makeStyles } from '@masknet/theme' -import { useWallet, useWallets, useWeb3State } from '@masknet/web3-hooks-base' +import { useWallet, useWeb3State } from '@masknet/web3-hooks-base' import { Providers, Web3 } from '@masknet/web3-providers' import { generateNewWalletName, isSameAddress } from '@masknet/web3-shared-base' import { ProviderType, formatEthereumAddress } from '@masknet/web3-shared-evm' @@ -84,18 +85,15 @@ const DeriveWallet = memo(function DeriveWallet() { const walletGroup = useWalletGroup() const wallets = walletGroup?.groups[mnemonicId] ?? EMPTY_LIST - const allWallets = useWallets() const currentWallet = useWallet() const { NameService } = useWeb3State(NetworkPluginID.PLUGIN_EVM) - const allWalletsRef = useRef([]) const [isDeriving, setIsDeriving] = DeriveStateContext.useContainer() const isDerivingRef = useRef(isDeriving) useEffect(() => { - allWalletsRef.current = allWallets isDerivingRef.current = isDeriving }) const [{ loading: creating }, create] = useAsyncFn(async () => { @@ -104,7 +102,9 @@ const DeriveWallet = memo(function DeriveWallet() { try { const nextWallet = await Services.Wallet.generateNextDerivationAddress() const ens = await NameService?.safeReverse?.(nextWallet, true) - const name = ens || generateNewWalletName(allWalletsRef.current) + const allWallets = Providers[ProviderType.MaskWallet].subscription.wallets.getCurrentValue() + queryClient.invalidateQueries(['@@mask-wallets']) + const name = ens || generateNewWalletName(allWallets) const address = await Services.Wallet.deriveWallet(name, mnemonicId) await pollResult(address) await Web3.connect({ diff --git a/packages/mask/src/extension/popups/pages/Wallet/CreateWallet/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/CreateWallet/index.tsx index eec27bad3fe2..55d2ef2615f3 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/CreateWallet/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/CreateWallet/index.tsx @@ -1,11 +1,11 @@ +import { memo } from 'react' +import { useNavigate } from 'react-router-dom' +import { first, sortBy } from 'lodash-es' import { Icons } from '@masknet/icons' import { PopupRoutes } from '@masknet/shared-base' import { makeStyles } from '@masknet/theme' import { formatEthereumAddress } from '@masknet/web3-shared-evm' import { List, ListItem, ListItemIcon, ListItemText, Tooltip, Typography } from '@mui/material' -import { first, sortBy } from 'lodash-es' -import { memo } from 'react' -import { useNavigate } from 'react-router-dom' import { useI18N } from '../../../../../utils/index.js' import { useTitle } from '../../../hooks/index.js' import { useWalletGroup } from '../../../hooks/useWalletGroup.js' @@ -86,7 +86,7 @@ const CreateWallet = memo(function CreateWallet() { {groups.map(([key, wallets], index) => { - const theFirstWallet = first(sortBy(wallets, (w) => w.createdAt.getMilliseconds())) + const theFirstWallet = first(sortBy(wallets, (w) => w.createdAt.getTime())) return ( { if (!wallet) return @@ -110,12 +116,12 @@ const ExportPrivateKey = memo(function ExportPrivateKey() { - + ) : ( - + ) } @@ -145,7 +151,9 @@ const ExportPrivateKey = memo(function ExportPrivateKey() { maxHeight="450px" overflow="auto" data-hide-scrollbar> - {wallet?.mnemonicId ? ( + {wallet?.mnemonicId && + walletGroup?.groups[wallet.mnemonicId] && + walletGroup?.groups[wallet.mnemonicId].length > 1 ? ( walletGroup?.groups[wallet.mnemonicId].map((x, index) => ( )) @@ -158,7 +166,7 @@ const ExportPrivateKey = memo(function ExportPrivateKey() { - {t('popups_export_json_file_tips')} + {t('popups_export_keystore_tips')}
    diff --git a/packages/mask/src/extension/popups/pages/Wallet/Interaction/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/Interaction/index.tsx index ebef809111fd..b38a9961c233 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/Interaction/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/Interaction/index.tsx @@ -1,5 +1,10 @@ +import urlcat from 'urlcat' +import { compact, mapValues, omit } from 'lodash-es' +import { toHex, toUtf8 } from 'web3-utils' import { memo, useCallback, useEffect, useMemo, useState } from 'react' -import { useI18N } from '../../../../../utils/i18n-next-ui.js' +import { useAsync, useAsyncFn } from 'react-use' +import { useUpdateEffect } from '@react-hookz/web' +import { Icons } from '@masknet/icons' import { Box, Typography } from '@mui/material' import { useChainContext, useMessages, useWeb3State } from '@masknet/web3-hooks-base' import { @@ -11,23 +16,19 @@ import { formatEthereumAddress, ChainId, ErrorEditor, + addGasMargin, } from '@masknet/web3-shared-evm' -import { toHex, toUtf8 } from 'web3-utils' import { useNavigate, useSearchParams } from 'react-router-dom' -import { SignRequestInfo } from '../../../components/SignRequestInfo/index.js' import { NetworkPluginID, PopupRoutes } from '@masknet/shared-base' import { ActionButton, makeStyles, usePopupCustomSnackbar } from '@masknet/theme' -import { useAsync, useAsyncFn } from 'react-use' import Services from '#services' +import { WalletAssetTabs } from '../type.js' +import { useI18N } from '../../../../../utils/i18n-next-ui.js' +import { SignRequestInfo } from '../../../components/SignRequestInfo/index.js' import { BottomController } from '../../../components/BottomController/index.js' import { TransactionPreview } from '../../../components/TransactionPreview/index.js' import { LoadingPlaceholder } from '../../../components/LoadingPlaceholder/index.js' -import { Icons } from '@masknet/icons' -import { useUpdateEffect } from '@react-hookz/web' import { UnlockERC20Token } from '../../../components/UnlockERC20Token/index.js' -import { compact, mapValues, omit } from 'lodash-es' -import urlcat from 'urlcat' -import { WalletAssetTabs } from '../type.js' import { UnlockERC721Token } from '../../../components/UnlockERC721Token/index.js' const useStyles = makeStyles()((theme) => ({ @@ -204,7 +205,7 @@ const Interaction = memo(function Interaction() { return toHex(value) }) : {}), - + gas: toHex(addGasMargin(gasConfig?.gas ?? x.gas).toString()), chainId: toHex(x.chainId), nonce: toHex(x.nonce), } @@ -224,9 +225,7 @@ const Interaction = memo(function Interaction() { }, }) const editor = response ? ErrorEditor.from(null, response) : undefined - if (editor?.presence) { - throw editor.error - } + if (editor?.presence) throw editor.error if (source) await Services.Helper.removePopupWindow() navigate(urlcat(PopupRoutes.Wallet, { tab: WalletAssetTabs.Activity }), { replace: true }) } catch (error) { diff --git a/packages/mask/src/extension/popups/pages/Wallet/ResetWallet/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/ResetWallet/index.tsx index 215cfdfeed14..b25715616fbb 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/ResetWallet/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/ResetWallet/index.tsx @@ -1,5 +1,4 @@ import Services from '#services' -import { PopupRoutes } from '@masknet/shared-base' import { ActionButton, makeStyles } from '@masknet/theme' import { Box, Button, Typography } from '@mui/material' import { memo, useCallback, useState } from 'react' @@ -79,7 +78,7 @@ const ResetWallet = memo(function ResetWallet() { const [answer, setAnswer] = useState('') const disabled = answer !== 'RESET' - const onBack = useCallback(() => navigate(PopupRoutes.Unlock), []) + const onBack = useCallback(() => navigate(-1), []) const onConfirm = useCallback(async () => { // We don't reset existed wallets until recovery diff --git a/packages/mask/src/extension/popups/pages/Wallet/SelectWallet/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/SelectWallet/index.tsx index a5b163b3aaca..43ab9ebec1a2 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/SelectWallet/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/SelectWallet/index.tsx @@ -1,8 +1,8 @@ -import { memo, useCallback, useEffect, useMemo, useState } from 'react' +import { memo, useCallback, useMemo, useState } from 'react' import { useAsync } from 'react-use' import { first } from 'lodash-es' import { useNavigate, useSearchParams } from 'react-router-dom' -import { ECKeyIdentifier, NetworkPluginID, PopupRoutes } from '@masknet/shared-base' +import { ECKeyIdentifier, EMPTY_LIST, NetworkPluginID, PopupRoutes } from '@masknet/shared-base' import { useChainContext, useChainIdValid, useNetworks, useWallets, useWeb3State } from '@masknet/web3-hooks-base' import { ProviderType, ChainId } from '@masknet/web3-shared-evm' import { Box, Button, Typography } from '@mui/material' @@ -61,7 +61,7 @@ const SelectWallet = memo(function SelectWallet() { const chainIdValid = useChainIdValid(NetworkPluginID.PLUGIN_EVM, chainId) const { smartPayChainId } = PopupContext.useContainer() - const { value: localWallets = [] } = useAsync(async () => Services.Wallet.getWallets(), []) + const { value: localWallets = EMPTY_LIST } = useAsync(async () => Services.Wallet.getWallets(), []) const allWallets = useWallets() @@ -69,13 +69,20 @@ const SelectWallet = memo(function SelectWallet() { if (!allWallets.length && localWallets.length) return localWallets return allWallets }, [localWallets, allWallets]) - const [selected, setSelected] = useState(account) + const defaultWallet = params.get('address') || account || (first(wallets)?.address ?? '') + const [selected = defaultWallet, setSelected] = useState() const handleCancel = useCallback(async () => { if (isVerifyWalletFlow) { navigate(-1) } else { - await Services.Wallet.resolveMaskAccount([]) + // TODO Open the popup via a RPC request, and reject the request + const rejected = await Promise.allSettled([ + Promise.reject({ + message: 'User rejected the request.', + }), + ]) + await Services.Wallet.resolveMaskAccount(rejected[0]) await Services.Helper.removePopupWindow() } }, [isVerifyWalletFlow]) @@ -137,10 +144,6 @@ const SelectWallet = memo(function SelectWallet() { Network, ]) - useEffect(() => { - if (!selected && wallets.length) setSelected(first(wallets)?.address ?? '') - }, [selected, wallets]) - useTitle(external_request ? 'Connecting External Site' : t('popups_select_wallet')) if (!chainIdValid) diff --git a/packages/mask/src/extension/popups/pages/Wallet/SetPaymentPassword/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/SetPaymentPassword/index.tsx index 78134fa644e6..28785b2b7d9a 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/SetPaymentPassword/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/SetPaymentPassword/index.tsx @@ -205,14 +205,15 @@ const SetPaymentPassword = memo(function SetPaymentPassword() { } else { await Services.Wallet.setPassword(data.password) } + queryClient.refetchQueries(['@@has-password']) const hasPassword = await Services.Wallet.hasPassword() if (hasPassword) { - queryClient.invalidateQueries(['@@has-password']) const from = params.get('from') showSnackbar(t('popups_wallet_set_payment_password_successfully'), { variant: 'success' }) CrossIsolationMessages.events.passwordStatusUpdated.sendToAll(true) - navigate({ pathname: from || PopupRoutes.Wallet }, { replace: true }) + params.delete('from') + navigate({ pathname: from || PopupRoutes.Wallet, search: params.toString() }, { replace: true }) } } catch (error) { if (error instanceof Error) { diff --git a/packages/mask/src/extension/popups/pages/Wallet/SwitchWallet/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/SwitchWallet/index.tsx index 440236893e46..53845e9b5a44 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/SwitchWallet/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/SwitchWallet/index.tsx @@ -103,7 +103,7 @@ const SwitchWallet = memo(function SwitchWallet() { const handleLock = useCallback(async () => { await Services.Wallet.lockWallet() - navigate(PopupRoutes.Unlock) + navigate(PopupRoutes.Wallet) }, []) const action = ( diff --git a/packages/mask/src/extension/popups/pages/Wallet/Transfer/NonFungibleTokenSection.tsx b/packages/mask/src/extension/popups/pages/Wallet/Transfer/NonFungibleTokenSection.tsx index 4a194b5cbdc3..fe43bcb8f22c 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/Transfer/NonFungibleTokenSection.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/Transfer/NonFungibleTokenSection.tsx @@ -48,9 +48,6 @@ const useStyles = makeStyles()((theme) => { zIndex: 100, marginTop: 'auto', }, - confirmButton: { - color: `${theme.palette.maskColor.bottom} !important`, - }, } }) @@ -165,12 +162,7 @@ export const NonFungibleTokenSection = memo(function NonFungibleTokenSection() { )}
    - + {t('confirm')}
    diff --git a/packages/mask/src/extension/popups/pages/Wallet/Unlock/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/Unlock/index.tsx index 3adc862666f8..b0b9748117d1 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/Unlock/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/Unlock/index.tsx @@ -49,7 +49,7 @@ const useStyles = makeStyles()((theme) => ({ }, })) -const Unlock = memo(() => { +const Unlock = memo(function Unlock() { const { t } = useI18N() const { classes } = useStyles() const [password, setPassword] = useState('') @@ -65,12 +65,10 @@ const Unlock = memo(() => { if (verified) { if (close_after_unlock && !from) { await Services.Helper.removePopupWindow() - } else { - const path = from - ? urlcat(from, { - tab: from === PopupRoutes.Personas ? PopupHomeTabType.ConnectedWallets : undefined, - }) - : PopupRoutes.Wallet + } else if (from) { + const path = urlcat(from, { + tab: from === PopupRoutes.Personas ? PopupHomeTabType.ConnectedWallets : undefined, + }) navigate(path, { replace: true }) } } diff --git a/packages/mask/src/extension/popups/pages/Wallet/WalletGuard/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/WalletGuard/index.tsx index 4de0ef0914f9..7b907e164600 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/WalletGuard/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/WalletGuard/index.tsx @@ -1,18 +1,51 @@ -import { useWallet, useWallets } from '@masknet/web3-hooks-base' +import { PopupRoutes } from '@masknet/shared-base' +import { useWallets } from '@masknet/web3-hooks-base' import { memo } from 'react' -import { Outlet, useOutletContext } from 'react-router-dom' +import { Navigate, Outlet, useLocation, useOutletContext, useSearchParams } from 'react-router-dom' +import Unlock from '../Unlock/index.js' import { WalletStartUp } from '../components/StartUp/index.js' +import { WalletSetupHeaderUI } from '../components/WalletHeader/WalletSetupHeaderUI.js' import { WalletHeader } from '../components/WalletHeader/index.js' +import { useWalletLockStatus } from '../hooks/index.js' +import { useMessageGuard } from './useMessageGuard.js' +import { usePaymentPasswordGuard } from './usePaymentPasswordGuard.js' export const WalletGuard = memo(function WalletGuard() { - const wallet = useWallet() const wallets = useWallets() const outletContext = useOutletContext() + const location = useLocation() + const [params] = useSearchParams() + const { isLocked, loading } = useWalletLockStatus() + + const hitPaymentPasswordGuard = usePaymentPasswordGuard() + const hitMessageGuard = useMessageGuard() + + if (!wallets.length) { + return ( + <> + + + + ) + } + if (hitPaymentPasswordGuard) { + params.set('from', location.pathname) + return + } + if (isLocked && !loading) { + return ( + <> + + + + ) + } + if (hitMessageGuard) return return ( <> - {!wallet || !wallets.length ? : } + ) }) diff --git a/packages/mask/src/extension/popups/pages/Wallet/WalletGuard/useMessageGuard.ts b/packages/mask/src/extension/popups/pages/Wallet/WalletGuard/useMessageGuard.ts new file mode 100644 index 000000000000..72d93523d0a4 --- /dev/null +++ b/packages/mask/src/extension/popups/pages/Wallet/WalletGuard/useMessageGuard.ts @@ -0,0 +1,13 @@ +import { PopupRoutes } from '@masknet/shared-base' +import { useMatch } from 'react-router-dom' +import { useMessages } from '@masknet/web3-hooks-base' + +/** + * Guardian for pending tasks + */ +export function useMessageGuard() { + const matchInteraction = useMatch(PopupRoutes.ContractInteraction) + const messages = useMessages() + + return !matchInteraction && messages.length > 0 +} diff --git a/packages/mask/src/extension/popups/pages/Wallet/WalletGuard/usePaymentPasswordGuard.ts b/packages/mask/src/extension/popups/pages/Wallet/WalletGuard/usePaymentPasswordGuard.ts new file mode 100644 index 000000000000..2110288c2aeb --- /dev/null +++ b/packages/mask/src/extension/popups/pages/Wallet/WalletGuard/usePaymentPasswordGuard.ts @@ -0,0 +1,10 @@ +import { PopupRoutes } from '@masknet/shared-base' +import { useMatch } from 'react-router-dom' +import { useHasPassword } from '../../../hooks/index.js' + +export function usePaymentPasswordGuard() { + const { hasPassword, loading } = useHasPassword() + const matchSetPaymentPassword = useMatch(PopupRoutes.SetPaymentPassword) + + return !matchSetPaymentPassword && !hasPassword && !loading +} diff --git a/packages/mask/src/extension/popups/pages/Wallet/components/WalletAssets/WalletCollections.tsx b/packages/mask/src/extension/popups/pages/Wallet/components/WalletAssets/WalletCollections.tsx index 692ce899f061..d063cbc9b9f4 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/components/WalletAssets/WalletCollections.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/components/WalletAssets/WalletCollections.tsx @@ -15,18 +15,15 @@ const gridProps = { columns: 'repeat(auto-fill, minmax(20%, 1fr))', gap: '8px', } -const useStyles = makeStyles()((theme) => { - const isDark = theme.palette.mode === 'dark' - return { - importNft: { - cursor: 'pointer', - color: isDark ? theme.palette.maskColor.main : theme.palette.maskColor.highlight, - fontSize: 14, - fontWeight: 400, - textAlign: 'center', - }, - } -}) +const useStyles = makeStyles()((theme) => ({ + importNft: { + cursor: 'pointer', + color: theme.palette.maskColor.main, + fontSize: 14, + fontWeight: 700, + textAlign: 'center', + }, +})) interface Props { onAddToken: (assetTab: WalletAssetTabs) => void diff --git a/packages/mask/src/extension/popups/pages/Wallet/components/WalletHeader/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/components/WalletHeader/index.tsx index aae0166a9978..625c7010128e 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/components/WalletHeader/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/components/WalletHeader/index.tsx @@ -24,7 +24,6 @@ export const WalletHeader = memo(function WalletHeader() { const origin = params.get('source') const currentNetwork = useNetwork(NetworkPluginID.PLUGIN_EVM, chainId) - const matchUnlock = useMatch(PopupRoutes.Unlock) const matchResetWallet = useMatch(PopupRoutes.ResetWallet) const matchWallet = PopupRoutes.Wallet === location.pathname const customHeader = CUSTOM_HEADER_PATTERNS.some((pattern) => matchPath(pattern, location.pathname)) @@ -56,8 +55,7 @@ export const WalletHeader = memo(function WalletHeader() { ) } - if (!wallet || !hasPassword || matchUnlock || matchResetWallet) - return + if (!wallet || !hasPassword || matchResetWallet) return return matchWallet ? ( { - return Services.Wallet.isLocked() - }, []) + const { data: isLocked, isLoading: loading, error, refetch } = useQuery(['@@is-locked'], Services.Wallet.isLocked) useEffect(() => { - return CrossIsolationMessages.events.walletLockStatusUpdated.on(retry) - }, [retry]) + return CrossIsolationMessages.events.walletLockStatusUpdated.on(() => refetch()) + }, []) return { error, diff --git a/packages/mask/src/extension/popups/pages/Wallet/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/index.tsx index 840f97133629..ca4423b98bd2 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/index.tsx @@ -23,7 +23,6 @@ const GasSetting = lazy(() => import('./GasSetting/index.js')) const Transfer = lazy(() => import('./Transfer/index.js')) const ContactList = lazy(() => import('./ContactList/index.js')) const ContractInteraction = lazy(() => import('./Interaction/index.js')) -const Unlock = lazy(() => import('./Unlock/index.js')) const ResetWallet = lazy(() => import('./ResetWallet/index.js')) const SetPaymentPassword = lazy(() => import('./SetPaymentPassword/index.js')) const ChangeOwner = lazy(() => import('./ChangeOwner/index.js')) @@ -56,8 +55,6 @@ export default function Wallet() { } /> } /> } /> - } /> - } /> } /> } /> } /> @@ -74,6 +71,7 @@ export default function Wallet() { } /> } /> } /> + } /> } /> diff --git a/packages/mask/src/extension/popups/normal-client.tsx b/packages/mask/src/extension/popups/render.tsx similarity index 51% rename from packages/mask/src/extension/popups/normal-client.tsx rename to packages/mask/src/extension/popups/render.tsx index fa00c7da1359..2c63c3a8494b 100644 --- a/packages/mask/src/extension/popups/normal-client.tsx +++ b/packages/mask/src/extension/popups/render.tsx @@ -1,66 +1,41 @@ import { activateSiteAdaptorUI } from '../../setup.ui.js' import { startPluginDashboard } from '@masknet/plugin-infra/dashboard' -import { createNormalReactRoot, hydrateNormalReactRoot } from '../../utils/index.js' +import { createNormalReactRoot } from '../../utils/index.js' import { createPluginHost, createPartialSharedUIContext } from '../../../shared/plugin-infra/host.js' import Services from '#services' import Popups from './UI.js' import { NextSharedUIContext, RestPartOfPluginUIContextShared } from '../../utils/plugin-context-shared-ui.js' -import { - MaskMessages, - createSubscriptionFromAsync, - currentPersonaIdentifier, - pluginIDsSettings, -} from '@masknet/shared-base' -import { initialPersonaInformation } from '@masknet/shared' +import { MaskMessages, createSubscriptionFromAsync } from '@masknet/shared-base' import { __setUIContext__ } from '@masknet/plugin-infra/dom/context' +if (location.hash === '') location.assign('#/personas') __setUIContext__({ allPersonas: NextSharedUIContext.allPersonas, queryPersonaAvatar: Services.Identity.getPersonaAvatar, }) -if ( - location.hash === '#/personas' || - (location.hash.includes('#/personas') && location.hash.includes('tab=Connected+Wallets')) -) { - console.time('[SSR] Fill data') - await Promise.all([ - activateSiteAdaptorUI(), - currentPersonaIdentifier.readyPromise, - pluginIDsSettings.readyPromise, - Services.Identity.queryOwnedPersonaInformation(false).then((value) => - initialPersonaInformation.setServerSnapshot(value), - ), - ]) - console.timeEnd('[SSR] Fill data') - - hydrateNormalReactRoot() - setTimeout(startPluginHost, 200) - /** - * Firefox will not help popup fixed width when user click browser action - * So this will determine if the user has set maxWidth to 'unset' when resizing in the window - */ - if (navigator.userAgent.includes('Firefox')) { - setTimeout(() => { - document.body.style.maxWidth = '350px' +/** + * Firefox will not help popup fixed width when user click browser action + * So this will determine if the user has set maxWidth to 'unset' when resizing in the window + */ +if (navigator.userAgent.includes('Firefox')) { + setTimeout(() => { + document.body.style.maxWidth = '350px' - window.addEventListener( - 'resize', - () => { - if (window.innerWidth !== 400) { - document.body.style.maxWidth = 'unset' - } - }, - { once: true }, - ) - }, 200) - } - console.timeEnd('[SSR] Hydrate') -} else { - await activateSiteAdaptorUI() - createNormalReactRoot() - startPluginHost() + window.addEventListener( + 'resize', + () => { + if (window.innerWidth !== 400) { + document.body.style.maxWidth = 'unset' + } + }, + { once: true }, + ) + }, 200) } +await activateSiteAdaptorUI() +createNormalReactRoot() +startPluginHost() function startPluginHost() { // TODO: Should only load plugins when the page is plugin-aware. diff --git a/packages/mask/src/site-adaptor-infra/ui.ts b/packages/mask/src/site-adaptor-infra/ui.ts index 98458e6ad3b1..d00c2e128ceb 100644 --- a/packages/mask/src/site-adaptor-infra/ui.ts +++ b/packages/mask/src/site-adaptor-infra/ui.ts @@ -4,6 +4,7 @@ import { assertNotEnvironment, Environment } from '@dimensiondev/holoflows-kit' import { delay, waitDocumentReadyState } from '@masknet/kit' import type { SiteAdaptorUI } from '@masknet/types' import { type Plugin, startPluginSiteAdaptor, SiteAdaptorContextRef } from '@masknet/plugin-infra/content-script' +import { __setSiteAdaptorContext__ } from '@masknet/plugin-infra/content-script/context' import { Modals, sharedUIComponentOverwrite, sharedUINetworkIdentifier, type ModalProps } from '@masknet/shared' import { createSubscriptionFromValueRef, @@ -28,7 +29,6 @@ import '../utils/debug/general.js' import { configureSelectorMissReporter } from '../utils/startWatch.js' import { NextSharedUIContext, RestPartOfPluginUIContextShared } from '../utils/plugin-context-shared-ui.js' import { definedSiteAdaptorsUI } from './define.js' -import { __setSiteAdaptorContext__ } from '@masknet/plugin-infra/content-script/context' const definedSiteAdaptorsResolved = new Map() @@ -164,8 +164,6 @@ export async function activateSiteAdaptorUIInner(ui_deferred: SiteAdaptorUI.Defe postMessage: ui.automation?.nativeCompositionDialog?.appendText, setCurrentPersonaIdentifier: Services.Settings.setCurrentPersonaIdentifier, setPluginMinimalModeEnabled: Services.Settings.setPluginMinimalModeEnabled, - setDecentralizedSearchSettings: Services.Settings.setDecentralizedSearchSettings, - getDecentralizedSearchSettings: Services.Settings.getDecentralizedSearchSettings, getSearchedKeyword: ui.collecting.getSearchedKeyword, hasHostPermission: Services.Helper.hasHostPermission, requestHostPermission: Services.Helper.requestHostPermission, diff --git a/packages/mask/src/site-adaptors/minds.com/collecting/identity.ts b/packages/mask/src/site-adaptors/minds.com/collecting/identity.ts index c6a28208891a..4494289f25d6 100644 --- a/packages/mask/src/site-adaptors/minds.com/collecting/identity.ts +++ b/packages/mask/src/site-adaptors/minds.com/collecting/identity.ts @@ -11,8 +11,7 @@ async function resolveLastRecognizedIdentityInner( cancel: AbortSignal, ) { const assign = async () => { - const handle = selfInfoSelectors().handle - const avatar = selfInfoSelectors().avatar + const { handle, avatar } = selfInfoSelectors() ref.value = { identifier: ProfileIdentifier.of(mindsBase.networkIdentifier, handle).unwrapOr(undefined), diff --git a/packages/mask/src/site-adaptors/minds.com/collecting/post.ts b/packages/mask/src/site-adaptors/minds.com/collecting/post.ts index 8500348e5603..534050bce134 100644 --- a/packages/mask/src/site-adaptors/minds.com/collecting/post.ts +++ b/packages/mask/src/site-adaptors/minds.com/collecting/post.ts @@ -34,13 +34,13 @@ function collectPostsMindsInner( new MutationObserverWatcher(postContentSelector()).useForeach((node, key, metadata) => { const activitySelector = new LiveSelector() .replace(() => [metadata.realCurrent]) - .closest('m-activityv2, m-activity__modal') + .closest('m-activity, m-activity__modal') const activityNode = activitySelector.evaluate()[0]! as HTMLElement // ? inject after comments const commentsSelector = activitySelector .clone() - .querySelectorAll('m-activityv2__content .m-comment__message') + .querySelectorAll('m-activity__content .m-comment__message') // ? inject comment text field const commentBoxSelector = activitySelector diff --git a/packages/mask/src/site-adaptors/minds.com/injection/PostReplacer.tsx b/packages/mask/src/site-adaptors/minds.com/injection/PostReplacer.tsx index c216a5a53234..af778cf4da1a 100644 --- a/packages/mask/src/site-adaptors/minds.com/injection/PostReplacer.tsx +++ b/packages/mask/src/site-adaptors/minds.com/injection/PostReplacer.tsx @@ -4,8 +4,8 @@ import type { PostInfo } from '@masknet/plugin-infra/content-script' function resolveContentNode(node: HTMLElement) { return node.closest( [ - 'm-activityv2__content .m-activityContentText__body > m-readmore span:first-child', - 'm-activityv2__content .m-activityContent__mediaDescriptionText', + 'm-activity__content .m-activityContentText__body > m-readmore span:first-child', + 'm-activity__content .m-activityContent__mediaDescriptionText', ].join(',') as any, ) } diff --git a/packages/mask/src/site-adaptors/minds.com/utils/fetch.ts b/packages/mask/src/site-adaptors/minds.com/utils/fetch.ts index 2f39dd07879d..64ab84cc3531 100644 --- a/packages/mask/src/site-adaptors/minds.com/utils/fetch.ts +++ b/packages/mask/src/site-adaptors/minds.com/utils/fetch.ts @@ -19,14 +19,19 @@ const parseNameArea = (nameArea: HTMLAnchorElement) => { } export const postIdParser = (node: HTMLElement) => { - const idNode = node.querySelector('m-activityv2__permalink .m-activityPermalink__wrapper--link') + const idNode = node.querySelector('m-activity__permalink .m-activityPermalink__wrapper--link') return idNode ? idNode.getAttribute('href')?.split('/')[2] ?? undefined : undefined } export const postNameParser = (node: HTMLElement) => { return parseNameArea( assertNonNull( - node.querySelector('m-activityv2__ownerblock .m-activityOwnerBlock__displayName'), + node.querySelector( + [ + 'm-activity__ownerblock .m-activityOwnerBlock__primaryName', + 'm-activity__ownerblock .m-activityOwnerBlock__secondaryName', // It's `secondaryName` in detail page + ].join(','), + ), ), ) } @@ -69,7 +74,7 @@ export const postContentMessageParser = (node: HTMLElement) => { } else return makeTypedMessageEmpty() } - const content = node.querySelector('m-activityv2__content') + const content = node.querySelector('m-activity__content') return content ? Array.from(content.childNodes).flatMap(make) : [] } diff --git a/packages/mask/src/site-adaptors/minds.com/utils/selector.ts b/packages/mask/src/site-adaptors/minds.com/utils/selector.ts index 5d6d05eb6e8f..b68287ae195d 100644 --- a/packages/mask/src/site-adaptors/minds.com/utils/selector.ts +++ b/packages/mask/src/site-adaptors/minds.com/utils/selector.ts @@ -30,10 +30,11 @@ export const postEditorDraftContentSelector = () => { return querySelector('m-composer__modal m-composer__textarea textarea.m-composerTextarea__message') } -export const handleSelector = () => querySelector('.m-sidebarNavigation__item--user > a > div > span') +export const handleSelector = () => + querySelector('.m-sidebarNavigation__item--user [data-ref="sidenav-channel"]') export const selfInfoSelectors = () => ({ - handle: handleSelector().evaluate()?.innerText.trim(), + handle: handleSelector().evaluate()?.getAttribute('href')?.slice(1), avatar: querySelector('.m-sidebarNavigation__item--user > a > div > img').evaluate()?.src, }) @@ -68,8 +69,8 @@ export const searchResultHeadingSelector = () => querySelector('m-discovery__sea export const postContentSelector = () => new LiveSelector().querySelectorAll( [ - 'm-activityv2 m-activityv2__content .m-activityTop__mainColumn', - 'm-activityv2 m-activityv2__content .m-activityContentText__body > m-readmore > span:first-child', + 'm-activity m-activity__content .m-activityTop__mainColumn', + 'm-activity m-activity__content .m-activityContentText__body > m-readmore > span:first-child', 'm-activity:not(.m-activity--minimalMode) m-activity__content .m-activityContent__messageWrapper > span:first-child', 'm-activity:not(.m-activity--minimalMode) m-activity__content .m-activityContent__mediaDescriptionText', ].join(','), diff --git a/packages/mask/src/site-adaptors/mirror.xyz/collecting/posts.ts b/packages/mask/src/site-adaptors/mirror.xyz/collecting/posts.ts index 71652ecd8015..5177a9f7d7c6 100644 --- a/packages/mask/src/site-adaptors/mirror.xyz/collecting/posts.ts +++ b/packages/mask/src/site-adaptors/mirror.xyz/collecting/posts.ts @@ -18,7 +18,7 @@ export function queryInjectPoint(node: HTMLElement) { const allANode = node.querySelectorAll( [ // post detail header - `:scope div[href$="/${authorWallet}" i]:has(img[alt^="0x" i][decoding="async"]) > div:last-child`, // img alt is always address + `:scope [href$="/${authorWallet}" i]:has(img[alt^="0x" i][decoding="async"]) > div:last-of-type`, // img alt is always address // collection page card footer ':scope header div:has(> span img[alt="Publisher"])', ].join(','), diff --git a/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/TipsButtonWrapper.tsx b/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/TipsButtonWrapper.tsx index 69881b8e1d89..ec563e91379f 100644 --- a/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/TipsButtonWrapper.tsx +++ b/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/TipsButtonWrapper.tsx @@ -1,14 +1,14 @@ +import { memo, useMemo } from 'react' +import { EMPTY_LIST, PluginID, type SocialAccount } from '@masknet/shared-base' +import { makeStyles } from '@masknet/theme' +import type { Web3Helper } from '@masknet/web3-helpers' +import { useNetworkContext, useWeb3Others } from '@masknet/web3-hooks-base' import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor, useIsMinimalMode, } from '@masknet/plugin-infra/content-script' -import { EMPTY_LIST, PluginID, type SocialAccount } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import type { Web3Helper } from '@masknet/web3-helpers' -import { useNetworkContext, useWeb3Others } from '@masknet/web3-hooks-base' -import { memo, useMemo } from 'react' import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' export const useStyles = makeStyles()((theme) => ({ @@ -36,7 +36,7 @@ export const TipsButtonWrapper = memo(function TipsButtonWrapper({ slot }: Props const { classes } = useStyles() const visitingIdentity = useCurrentVisitingIdentity() - const isMinimal = useIsMinimalMode(PluginID.Tips) + const isMinimalMode = useIsMinimalMode(PluginID.Tips) const { pluginID } = useNetworkContext() const Others = useWeb3Others() @@ -61,8 +61,8 @@ export const TipsButtonWrapper = memo(function TipsButtonWrapper({ slot }: Props return }, [visitingIdentity.identifier, accounts, slot]) - const isDashboard = location.href === '/' - if (!component || !visitingIdentity.identifier || isMinimal || isDashboard) return null + + if (!component || !visitingIdentity.identifier || isMinimalMode || location.href === '/') return null return ( {component} diff --git a/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/adjustArticleInfoBar.ts b/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/adjustArticleInfoBar.ts new file mode 100644 index 000000000000..b35ac41857f6 --- /dev/null +++ b/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/adjustArticleInfoBar.ts @@ -0,0 +1,15 @@ +function selector() { + const authorWallet = location.pathname.split('/')[1].toLowerCase() + return `#__next div:has(> div > a[href$="mirror.xyz/${authorWallet}" i] button[data-state] img[alt^="0x" i]) + div` +} + +export function adjustArticleInfoBar(signal: AbortSignal) { + const node = document.querySelector(selector()) + if (!node) return + const timer = setInterval(() => { + if (node.offsetWidth !== node.parentElement?.offsetWidth) return + node.style.justifyContent = 'flex-start' + clearInterval(timer) + }, 250) + signal.addEventListener('abort', () => clearInterval(timer)) +} diff --git a/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/index.tsx b/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/index.tsx index 222b0fc0e9b1..5c6a914724ea 100644 --- a/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/index.tsx +++ b/packages/mask/src/site-adaptors/mirror.xyz/injection/Tips/index.tsx @@ -1,9 +1,11 @@ import { injectOnMenu } from './MenuAuthorTipButton.js' import { injectOnVerification } from './PostVerification.js' import { injectTipsButtonOnProfile as injectOnProfile } from './ProfilePage.js' +import { adjustArticleInfoBar } from './adjustArticleInfoBar.js' export function injectTips(signal: AbortSignal) { injectOnMenu(signal) injectOnProfile(signal) injectOnVerification(signal) + adjustArticleInfoBar(signal) } diff --git a/packages/mask/src/site-adaptors/twitter.com/collecting/identity.ts b/packages/mask/src/site-adaptors/twitter.com/collecting/identity.ts index b03f2ee057ba..4338b9b0596a 100644 --- a/packages/mask/src/site-adaptors/twitter.com/collecting/identity.ts +++ b/packages/mask/src/site-adaptors/twitter.com/collecting/identity.ts @@ -124,21 +124,20 @@ function resolveCurrentVisitingIdentityInner( const update = async (twitterId: string) => { const user = await Twitter.getUserByScreenName(twitterId) if (process.env.NODE_ENV === 'development') { - console.assert(user?.legacy, `Can't get get user by screen name ${twitterId}`) + console.assert(user, `Can't get get user by screen name ${twitterId}`) } - if (!user?.legacy) return + if (!user) return - const nickname = user.legacy.name - const handle = user.legacy.screen_name + const handle = user.screenName const ownerHandle = ownerRef.value.identifier?.userId const isOwner = !!(ownerHandle && handle.toLowerCase() === ownerHandle.toLowerCase()) - const avatar = user.legacy.profile_image_url_https.replace(/_normal(\.\w+)$/, '_400x400$1') - const bio = user.legacy.description - const homepage = user.legacy.entities.url?.urls[0]?.expanded_url ?? '' + const avatar = user.avatarURL + const bio = user.bio + const homepage = user.homepage ref.value = { identifier: ProfileIdentifier.of(twitterBase.networkIdentifier, handle).unwrapOr(undefined), - nickname, + nickname: user.nickname, avatar, bio, homepage, diff --git a/packages/mask/src/site-adaptors/twitter.com/collecting/post.ts b/packages/mask/src/site-adaptors/twitter.com/collecting/post.ts index e32fb549f6ed..fe275760e909 100644 --- a/packages/mask/src/site-adaptors/twitter.com/collecting/post.ts +++ b/packages/mask/src/site-adaptors/twitter.com/collecting/post.ts @@ -1,7 +1,7 @@ -import { DOMProxy, IntervalWatcher, type DOMProxyEvents } from '@dimensiondev/holoflows-kit' +import Services from '#services' +import { IntervalWatcher } from '@dimensiondev/holoflows-kit' import type { PostInfo } from '@masknet/plugin-infra/content-script' import { PostIdentifier, ProfileIdentifier } from '@masknet/shared-base' -import { parseId } from '../utils/url.js' import { FlattenTypedMessage, extractTextFromTypedMessage, @@ -12,11 +12,9 @@ import { makeTypedMessageTupleFromList, } from '@masknet/typed-message' import type { SiteAdaptorUI } from '@masknet/types' -import type { EventListener } from '@servie/events' -import { memoize, noop } from 'lodash-es' +import { memoize } from 'lodash-es' import utils from 'web3-utils' -import Services from '#services' -import { creator, activatedSiteAdaptor_state } from '../../../site-adaptor-infra/index.js' +import { activatedSiteAdaptor_state, creator } from '../../../site-adaptor-infra/index.js' import { createRefsForCreatePostContext } from '../../../site-adaptor-infra/utils/create-post-context.js' import { untilElementAvailable } from '../../../utils/dom.js' import { getCurrentIdentifier } from '../../utils.js' @@ -30,6 +28,7 @@ import { timelinePostContentSelector, toastLinkSelector, } from '../utils/selector.js' +import { parseId } from '../utils/url.js' import { IdentityProviderTwitter } from './identity.js' function getPostActionsNode(postNode: HTMLElement | null) { @@ -95,21 +94,9 @@ function registerPostCollectorInner( const tweetNode = getTweetNode(node) if (!tweetNode || shouldSkipDecrypt(node, tweetNode)) return const refs = createRefsForCreatePostContext() - let actionsElementProxy: DOMProxy | undefined = undefined - const actionsInjectPoint = getPostActionsNode(proxy.current) - let unwatchPostNodeChange = noop - if (actionsInjectPoint && !isQuotedTweet(tweetNode)) { - actionsElementProxy = DOMProxy({}) - actionsElementProxy.realCurrent = actionsInjectPoint - const handleChanged: EventListener, 'currentChanged'> = (e) => { - actionsElementProxy!.realCurrent = getPostActionsNode(e.new) || null - } - unwatchPostNodeChange = proxy.on('currentChanged', handleChanged) - } const info = twitterShared.utils.createPostContext({ comments: undefined, rootElement: proxy, - actionsElement: actionsElementProxy, isFocusing: isDetailTweet(tweetNode), suggestedInjectionPoint: tweetNode, ...refs.subscriptions, @@ -133,7 +120,6 @@ function registerPostCollectorInner( onTargetChanged: run, onRemove: () => { postStore.delete(proxy) - unwatchPostNodeChange() }, onNodeMutation: run, } diff --git a/packages/mask/src/site-adaptors/twitter.com/injection/Calendar.tsx b/packages/mask/src/site-adaptors/twitter.com/injection/Calendar.tsx index 67b61b8e5160..33355729977e 100644 --- a/packages/mask/src/site-adaptors/twitter.com/injection/Calendar.tsx +++ b/packages/mask/src/site-adaptors/twitter.com/injection/Calendar.tsx @@ -5,13 +5,36 @@ import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderI import { CalendarContent } from '@masknet/plugin-calendar' const sidebarSearchSelector: () => LiveSelector = () => { - return querySelector('[data-testid="sidebarColumn"] [role="search"]').closest(4) + return querySelector( + '[data-testid="sidebarColumn"] > div > div > div > div[tabindex="0"] > div > div:not(div[tabindex="0"]):empty', + ) +} + +const sidebarExplorePageSelector: () => LiveSelector = () => { + return querySelector('[data-testid="settingsAppBar"]') + .closest(12) + .querySelector('[data-testid="sidebarColumn"] [tabindex="0"] > div') } +const sidebarSearchPageSelector: () => LiveSelector = () => { + return querySelector('[data-testid="searchBoxOverflowButton"]') + .closest(11) + .querySelector('[data-testid="sidebarColumn"] [tabindex="0"] > div > div') +} export function injectCalendar(signal: AbortSignal) { const watcher = new MutationObserverWatcher(sidebarSearchSelector()) + const exploreWatcher = new MutationObserverWatcher(sidebarExplorePageSelector()) + const searchWatcher = new MutationObserverWatcher(sidebarSearchPageSelector()) startWatch(watcher, signal) + startWatch(exploreWatcher, signal) + startWatch(searchWatcher, signal) attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { untilVisible: true, signal }).render( , ) + attachReactTreeWithContainer(exploreWatcher.firstDOMProxy.beforeShadow, { untilVisible: true, signal }).render( + , + ) + attachReactTreeWithContainer(searchWatcher.firstDOMProxy.afterShadow, { untilVisible: true, signal }).render( + , + ) } diff --git a/packages/mask/src/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfileDialog.tsx b/packages/mask/src/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfileDialog.tsx index 65037d6b6f13..599146ed9900 100644 --- a/packages/mask/src/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfileDialog.tsx +++ b/packages/mask/src/site-adaptors/twitter.com/injection/NFT/NFTAvatarEditProfileDialog.tsx @@ -15,8 +15,8 @@ import { usePersonasFromDB } from '../../../../components/DataSource/usePersonas import Services from '#services' import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' import { ButtonStyle } from '../../constant.js' -import { searchProfileAvatarSelector, searchProfileSaveSelector } from '../../utils/selector.js' import { startWatch } from '../../../../utils/startWatch.js' +import { searchProfileAvatarSelector, searchProfileSaveSelector } from '../../utils/selector.js' export function injectOpenNFTAvatarEditProfileButtonAtEditProfileDialog(signal: AbortSignal) { const watcher = new MutationObserverWatcher(searchProfileAvatarSelector()) diff --git a/packages/mask/src/site-adaptors/twitter.com/injection/PostDialogHint.tsx b/packages/mask/src/site-adaptors/twitter.com/injection/PostDialogHint.tsx index 797946c29324..0f1f47a00862 100644 --- a/packages/mask/src/site-adaptors/twitter.com/injection/PostDialogHint.tsx +++ b/packages/mask/src/site-adaptors/twitter.com/injection/PostDialogHint.tsx @@ -1,16 +1,17 @@ -import { useCallback } from 'react' -import { alpha } from '@mui/material' -import { makeStyles, MaskColorVar } from '@masknet/theme' -import { makeTypedMessageText } from '@masknet/typed-message' import { MutationObserverWatcher, type LiveSelector } from '@dimensiondev/holoflows-kit' import { CrossIsolationMessages, sayHelloShowed } from '@masknet/shared-base' -import { isReplyPageSelector, postEditorInPopupSelector, searchReplyToolbarSelector } from '../utils/selector.js' -import { startWatch, type WatchOptions } from '../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' +import { makeStyles, MaskColorVar } from '@masknet/theme' +import { makeTypedMessageText } from '@masknet/typed-message' +import { alpha } from '@mui/material' +import { clamp } from 'lodash-es' +import { useCallback } from 'react' import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint.js' -import { twitterBase } from '../base.js' import { useI18N } from '../../../utils/index.js' +import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' +import { startWatch, type WatchOptions } from '../../../utils/startWatch.js' +import { twitterBase } from '../base.js' import { hasEditor, isCompose } from '../utils/postBox.js' +import { isReplyPageSelector, postEditorInPopupSelector, searchReplyToolbarSelector } from '../utils/selector.js' const useStyles = makeStyles()((theme) => ({ iconButton: { @@ -29,6 +30,7 @@ const useStyles = makeStyles()((theme) => ({ export function injectPostDialogHintAtTwitter(signal: AbortSignal) { const emptyNode = document.createElement('div') + renderPostDialogHintTo('timeline', searchReplyToolbarSelector(), { signal, missingReportRule: { name: 'PostDialog hint timeline', rule: 'https://twitter.com/home' }, @@ -47,13 +49,34 @@ export function injectPostDialogHintAtTwitter(signal: AbortSignal) { ) } -function renderPostDialogHintTo(reason: 'timeline' | 'popup', ls: LiveSelector, options: WatchOptions) { +function renderPostDialogHintTo( + reason: 'timeline' | 'popup', + ls: LiveSelector, + options: WatchOptions, +) { const watcher = new MutationObserverWatcher(ls) startWatch(watcher, options) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal: options.signal }).render( - , - ) + attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { + signal: options.signal, + tag: () => { + // Vertical center the button when font size of Twitter is set to `large` or `very large` + const tag = document.createElement('span') + Object.assign(tag.style, { + display: 'inline-flex', + alignItems: 'center', + height: '100%', + }) + const svgIcon = document.querySelector('[data-testid="geoButton"] svg') + const size = svgIcon ? clamp(svgIcon.getBoundingClientRect().width, 18, 24) : undefined + const geoButton = document.querySelector('[data-testid="geoButton"]') + const padding = geoButton && size ? (geoButton.getBoundingClientRect().width - size) / 2 : undefined + if (padding) tag.style.setProperty('--icon-padding', `${padding}px`) + if (size) tag.style.setProperty('--icon-size', `${size}px`) + + return tag + }, + }).render() } function PostDialogHintAtTwitter({ reason }: { reason: 'timeline' | 'popup' }) { diff --git a/packages/mask/src/site-adaptors/twitter.com/injection/ProfileCard/index.tsx b/packages/mask/src/site-adaptors/twitter.com/injection/ProfileCard/index.tsx index 9f9656fe003a..9f29ae96ed85 100644 --- a/packages/mask/src/site-adaptors/twitter.com/injection/ProfileCard/index.tsx +++ b/packages/mask/src/site-adaptors/twitter.com/injection/ProfileCard/index.tsx @@ -1,7 +1,9 @@ import { useEffect, useRef, useState } from 'react' import { useAsync } from 'react-use' +import { Fade } from '@mui/material' import { CrossIsolationMessages, ProfileIdentifier, type SocialIdentity } from '@masknet/shared-base' import { LoadingBase, ShadowRootPopper, makeStyles } from '@masknet/theme' +import { AnchorProvider, queryClient } from '@masknet/shared-base-ui' import { Twitter } from '@masknet/web3-providers' import { useSocialIdentity } from '../../../../components/DataSource/useActivatedUI.js' import { ProfileCard } from '../../../../components/InjectedComponents/ProfileCard/index.js' @@ -9,8 +11,6 @@ import { attachReactTreeWithoutContainer } from '../../../../utils/index.js' import { twitterBase } from '../../base.js' import { CARD_HEIGHT, CARD_WIDTH } from './constants.js' import { useControlProfileCard } from './useControlProfileCard.js' -import { Fade } from '@mui/material' -import { AnchorProvider, queryClient } from '@masknet/shared-base-ui' export function injectProfileCardHolder(signal: AbortSignal) { attachReactTreeWithoutContainer('profile-card', , signal) @@ -59,16 +59,14 @@ function ProfileCardHolder() { queryKey: ['twitter', 'profile', twitterId], queryFn: () => Twitter.getUserByScreenName(twitterId), }) - if (!user?.legacy) return null - - const handle = user.legacy.screen_name + if (!user) return null return { - identifier: ProfileIdentifier.of(twitterBase.networkIdentifier, handle).unwrapOr(undefined), - nickname: user.legacy.name, - avatar: user.legacy.profile_image_url_https.replace(/_normal(\.\w+)$/, '_400x400$1'), - bio: user.legacy.description, - homepage: user.legacy.entities.url?.urls[0]?.expanded_url ?? '', + identifier: ProfileIdentifier.of(twitterBase.networkIdentifier, user.screenName).unwrapOr(undefined), + nickname: user.nickname, + avatar: user.avatarURL, + bio: user.bio, + homepage: user.homepage, } }, [twitterId]) diff --git a/packages/mask/src/site-adaptors/twitter.com/injection/ProfileTabContent.tsx b/packages/mask/src/site-adaptors/twitter.com/injection/ProfileTabContent.tsx index fb864fa1387b..867672cfe3ed 100644 --- a/packages/mask/src/site-adaptors/twitter.com/injection/ProfileTabContent.tsx +++ b/packages/mask/src/site-adaptors/twitter.com/injection/ProfileTabContent.tsx @@ -26,21 +26,21 @@ function injectProfileTabContentState(signal: AbortSignal) { } export function injectProfileTabContentAtTwitter(signal: AbortSignal) { - const contentLoseConnectionWatcher = new MutationObserverWatcher( + const lostConnectionContentWatcher = new MutationObserverWatcher( searchProfileTabLoseConnectionPageSelector(), ).useForeach(() => MaskMessages.events.profileTabHidden.sendToLocal({ hidden: true })) - const contentContentWatcher = new MutationObserverWatcher(searchProfileTabPageSelector()).useForeach(() => + const contentWatcher = new MutationObserverWatcher(searchProfileTabPageSelector()).useForeach(() => MaskMessages.events.profileTabHidden.sendToLocal({ hidden: false }), ) - const ContentForEmptyWatcher = new MutationObserverWatcher(searchProfileEmptySelector()).useForeach(() => + const emptyContentWatcher = new MutationObserverWatcher(searchProfileEmptySelector()).useForeach(() => MaskMessages.events.profileTabHidden.sendToLocal({ hidden: false }), ) - startWatch(contentLoseConnectionWatcher, { signal, shadowRootDelegatesFocus: false }) - startWatch(contentContentWatcher, { signal, shadowRootDelegatesFocus: false }) - startWatch(ContentForEmptyWatcher, { signal, shadowRootDelegatesFocus: false }) + startWatch(lostConnectionContentWatcher, { signal, shadowRootDelegatesFocus: false }) + startWatch(contentWatcher, { signal, shadowRootDelegatesFocus: false }) + startWatch(emptyContentWatcher, { signal, shadowRootDelegatesFocus: false }) injectProfileTabContentForEmptyState(signal) injectProfileTabContentState(signal) diff --git a/packages/mask/src/site-adaptors/twitter.com/injection/Tips/FollowTipsButton.tsx b/packages/mask/src/site-adaptors/twitter.com/injection/Tips/FollowTipsButton.tsx index bc8596b47107..8e74831af76f 100644 --- a/packages/mask/src/site-adaptors/twitter.com/injection/Tips/FollowTipsButton.tsx +++ b/packages/mask/src/site-adaptors/twitter.com/injection/Tips/FollowTipsButton.tsx @@ -9,11 +9,10 @@ import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/rend import { startWatch } from '../../../../utils/startWatch.js' import { TipButtonStyle } from '../../constant.js' import { normalFollowButtonSelector as selector } from '../../utils/selector.js' -import type { SocialIdentity } from '@masknet/shared-base' -import { getUserIdentity } from '../../utils/user.js' import { isVerifiedUser } from '../../utils/AvatarType.js' +import { useUserIdentity } from './hooks.js' -function getTwitterId(ele: HTMLElement) { +function getUserId(ele: HTMLElement) { const profileLink = ele.closest('[data-testid="UserCell"]')?.querySelector('a[role="link"]') if (!profileLink) return return profileLink.getAttribute('href')?.slice(1) @@ -27,19 +26,17 @@ export function injectTipsButtonOnFollowButton(signal: AbortSignal) { const remove = () => remover() const run = async () => { - const twitterId = getTwitterId(ele) - if (!twitterId) return + const userId = getUserId(ele) + if (!userId) return const proxy = DOMProxy({ afterShadowRootInit: Flags.shadowRootInit, }) proxy.realCurrent = ele const isVerified = isVerifiedUser(ele) - const identity = await getUserIdentity(twitterId) - if (!identity) return const root = attachReactTreeWithContainer(proxy.beforeShadow, { signal }) - root.render(isVerified ? :
    ) + root.render(isVerified ? :
    ) remover = root.destroy } @@ -54,7 +51,7 @@ export function injectTipsButtonOnFollowButton(signal: AbortSignal) { ) } -const useStyles = makeStyles()((theme) => ({ +const useStyles = makeStyles()(() => ({ disabled: { display: 'none', }, @@ -69,13 +66,14 @@ const useStyles = makeStyles()((theme) => ({ })) interface Props { - identity: SocialIdentity + userId: string } -const FollowButtonTipsSlot = memo(({ identity }: Props) => { +const FollowButtonTipsSlot = memo(function FollowButtonTipsSlot({ userId }: Props) { const themeSetting = useThemeSettings() const tipStyle = TipButtonStyle[themeSetting.size] const { classes, cx } = useStyles() + const identity = useUserIdentity(userId) const [disabled, setDisabled] = useState(true) diff --git a/packages/mask/src/site-adaptors/twitter.com/injection/Tips/PostTipsButton.tsx b/packages/mask/src/site-adaptors/twitter.com/injection/Tips/PostTipsButton.tsx new file mode 100644 index 000000000000..a04bb8021278 --- /dev/null +++ b/packages/mask/src/site-adaptors/twitter.com/injection/Tips/PostTipsButton.tsx @@ -0,0 +1,109 @@ +import { DOMProxy, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' +import { Flags } from '@masknet/flags' +import { Plugin, createInjectHooksRenderer, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' +import { makeStyles } from '@masknet/theme' +import { noop } from 'lodash-es' +import { memo, useMemo, useState } from 'react' +import { useThemeSettings } from '../../../../components/DataSource/useActivatedUI.js' +import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' +import { startWatch } from '../../../../utils/startWatch.js' +import { TipButtonStyle } from '../../constant.js' +import { querySelectorAll } from '../../utils/selector.js' +import { useUserIdentity } from './hooks.js' + +function postShareButtonSelector() { + return querySelectorAll('article[data-testid="tweet"] [role="group"] > div:has([aria-haspopup="menu"]):last-child') +} + +function getUserId(ele: HTMLElement) { + const avatar = ele + .closest('[data-testid="tweet"]') + ?.querySelector('[data-testid^="UserAvatar-Container-"]') + if (!avatar) return + return avatar.dataset.testid?.slice(21) // "UserAvatar-Container-".length === 21 +} + +function createRootElement() { + const root = document.createElement('div') + Object.assign(root.style, { + height: '100%', + display: 'flex', + alignItems: 'center', + }) + return root +} + +export function injectTipsButtonOnPost(signal: AbortSignal) { + const watcher = new MutationObserverWatcher(postShareButtonSelector()) + startWatch( + watcher.useForeach((ele) => { + let remover = noop + const remove = () => remover() + + const run = async () => { + const userId = getUserId(ele) + if (!userId) return + const proxy = DOMProxy({ + afterShadowRootInit: Flags.shadowRootInit, + }) + proxy.realCurrent = ele + ele.style.flex = '1' + + const root = attachReactTreeWithContainer(proxy.afterShadow, { + signal, + tag: createRootElement, + // untilVisible: true, + }) + root.render() + remover = root.destroy + } + + run() + return { + onNodeMutation: run, + onTargetChanged: run, + onRemove: remove, + } + }), + signal, + ) +} + +const useStyles = makeStyles()(() => ({ + disabled: { + display: 'none', + }, +})) + +interface Props { + userId: string +} + +const PostTipsSlot = memo(function PostTipsSlot({ userId }: Props) { + const themeSetting = useThemeSettings() + const tipStyle = TipButtonStyle[themeSetting.size] + const { classes } = useStyles() + const identity = useUserIdentity(userId) + + const [disabled, setDisabled] = useState(true) + + const component = useMemo(() => { + const Component = createInjectHooksRenderer( + useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, + (plugin) => plugin.TipsRealm?.UI?.Content, + ) + return ( + + ) + }, [identity?.identifier, tipStyle.buttonSize, tipStyle.iconSize]) + + if (!identity?.identifier) return null + + return {component} +}) diff --git a/packages/mask/src/site-adaptors/twitter.com/injection/Tips/ProfileTipButton.tsx b/packages/mask/src/site-adaptors/twitter.com/injection/Tips/ProfileTipsButton.tsx similarity index 100% rename from packages/mask/src/site-adaptors/twitter.com/injection/Tips/ProfileTipButton.tsx rename to packages/mask/src/site-adaptors/twitter.com/injection/Tips/ProfileTipsButton.tsx diff --git a/packages/mask/src/site-adaptors/twitter.com/injection/Tips/hooks.ts b/packages/mask/src/site-adaptors/twitter.com/injection/Tips/hooks.ts new file mode 100644 index 000000000000..1b77f3ec129c --- /dev/null +++ b/packages/mask/src/site-adaptors/twitter.com/injection/Tips/hooks.ts @@ -0,0 +1,10 @@ +import { useQuery } from '@tanstack/react-query' +import { getUserIdentity } from '../../utils/user.js' + +export function useUserIdentity(userId: string) { + const { data: identity } = useQuery({ + queryKey: ['get-user-identity', userId], + queryFn: () => getUserIdentity(userId), + }) + return identity +} diff --git a/packages/mask/src/site-adaptors/twitter.com/injection/Tips/index.tsx b/packages/mask/src/site-adaptors/twitter.com/injection/Tips/index.tsx index 4d2fdd6b0f15..fdc530d9445f 100644 --- a/packages/mask/src/site-adaptors/twitter.com/injection/Tips/index.tsx +++ b/packages/mask/src/site-adaptors/twitter.com/injection/Tips/index.tsx @@ -1,7 +1,9 @@ -import { injectOpenTipsButtonOnProfile } from './ProfileTipButton.js' +import { injectOpenTipsButtonOnProfile } from './ProfileTipsButton.js' import { injectTipsButtonOnFollowButton } from './FollowTipsButton.js' +import { injectTipsButtonOnPost } from './PostTipsButton.js' export function injectTips(signal: AbortSignal) { injectOpenTipsButtonOnProfile(signal) injectTipsButtonOnFollowButton(signal) + injectTipsButtonOnPost(signal) } diff --git a/packages/mask/src/site-adaptors/twitter.com/utils/fetch.ts b/packages/mask/src/site-adaptors/twitter.com/utils/fetch.ts index 7d645e4ee826..cf13b717f6a9 100644 --- a/packages/mask/src/site-adaptors/twitter.com/utils/fetch.ts +++ b/packages/mask/src/site-adaptors/twitter.com/utils/fetch.ts @@ -124,6 +124,8 @@ export function postContentMessageParser(node: HTMLElement): TypedMessage { if (!alt) return makeTypedMessageEmpty() return makeTypedMessageText(alt) + } else if (node instanceof HTMLSpanElement) { + return makeTypedMessageText(node.textContent ?? '') } else if (node.childNodes.length) { const messages = makeTypedMessageTuple(flattenDeep(Array.from(node.childNodes).map(make))) return FlattenTypedMessage.NoContext(messages) diff --git a/packages/mask/src/site-adaptors/twitter.com/utils/selector.ts b/packages/mask/src/site-adaptors/twitter.com/utils/selector.ts index 913d51a07b42..4e37edafc27d 100644 --- a/packages/mask/src/site-adaptors/twitter.com/utils/selector.ts +++ b/packages/mask/src/site-adaptors/twitter.com/utils/selector.ts @@ -15,7 +15,7 @@ export const querySelectorAll = (selector: string) => { // #region "Enhanced Profile" export const searchProfileTabListLastChildSelector: () => LiveSelector = () => querySelector( - '[data-testid="primaryColumn"] div + [role="navigation"][aria-label] [data-testid="ScrollSnap-List"] a[href*="/like"]', + '[data-testid="primaryColumn"] div + [role="navigation"][aria-label] [data-testid="ScrollSnap-List"] div[role="presentation"]:last-of-type a[role="tab"]', ).closest(1) export const nextTabListSelector: () => LiveSelector = () => querySelector('[data-testid="ScrollSnap-nextButtonWrapper"]') @@ -90,7 +90,9 @@ export const postEditorContentInPopupSelector: () => LiveSelector = () '[aria-labelledby="modal-header"] > div:first-child > div:first-child > div:first-child > div:nth-child(3)', ) export const postEditorInPopupSelector: () => LiveSelector = () => - querySelector('[aria-labelledby="modal-header"] div[data-testid="toolBar"] div[data-testid="geoButton"]') + querySelector( + '[aria-labelledby="modal-header"] div[data-testid="toolBar"] [role="presentation"]:has(> div[data-testid="geoButton"])', + ) export const sideBarProfileSelector: () => LiveSelector = () => querySelector('[role="banner"] [role="navigation"] [data-testid="AppTabBar_Profile_Link"] > div') export const postEditorInTimelineSelector: () => LiveSelector = () => @@ -163,8 +165,8 @@ export const postsContentSelector = () => querySelectorAll( [ // tweets on timeline page - '[data-testid="tweet"] div + div div[lang]', - '[data-testid="tweet"] div + div div[data-testid="card.wrapper"]', + '[data-testid="tweet"] [data-testid="tweetText"]', + '[data-testid="tweet"]:not(:has([data-testid="tweetText"])) [data-testid="tweetPhoto"]', // tweets with only image. // tweets on detailed page '[data-testid="tweet"] + div > div:first-child div[lang]', @@ -174,9 +176,6 @@ export const postsContentSelector = () => '[data-testid="tweet"] [aria-labelledby] div[role="link"] div[lang]', // quoted tweets in detail page '[data-testid="tweet"] > div:last-child div[role="link"] div[lang]', - - // reply-tweets - '[data-testid="tweet"] + div div div[lang][dir]', ].join(','), ) @@ -279,7 +278,7 @@ export const searchTwitterAvatarNFTLinkSelector = () => querySelector('a[href export const searchReplyToolbarSelector = () => querySelector('div[data-testid="primaryColumn"] div[data-testid="toolBar"]').querySelector( - 'div[data-testid="geoButton"]', + '[role="presentation"]:has(> div[data-testid="geoButton"])', ) // nameTag dom diff --git a/packages/mask/src/site-adaptors/twitter.com/utils/url.ts b/packages/mask/src/site-adaptors/twitter.com/utils/url.ts index 5c1235e88a85..c35d9854bc46 100644 --- a/packages/mask/src/site-adaptors/twitter.com/utils/url.ts +++ b/packages/mask/src/site-adaptors/twitter.com/utils/url.ts @@ -13,9 +13,7 @@ export const hostLeadingUrlAutoTwitter = (isMobile: boolean) => // https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/entities-object#photo_format export const canonifyImgUrl = (url: string) => { const parsed = new URL(url) - if (parsed.hostname !== 'pbs.twimg.com') { - return url - } + if (parsed.hostname !== 'pbs.twimg.com') return url const { searchParams } = parsed searchParams.set('name', 'orig') // we can't understand original image format when given url labeled as webp @@ -24,7 +22,7 @@ export const canonifyImgUrl = (url: string) => { const pngURL = parsed.href searchParams.set('format', 'jpg') const jpgURL = parsed.href - return [pngURL, jpgURL] + return [jpgURL, pngURL] } return parsed.href } diff --git a/packages/mask/src/site-adaptors/twitter.com/utils/user.ts b/packages/mask/src/site-adaptors/twitter.com/utils/user.ts index 8445235136e4..439dd309fb33 100644 --- a/packages/mask/src/site-adaptors/twitter.com/utils/user.ts +++ b/packages/mask/src/site-adaptors/twitter.com/utils/user.ts @@ -34,15 +34,13 @@ export const getNickname = () => { export const getTwitterId = () => { const ele = searchAvatarSelector().evaluate()?.closest('a') || searchNFTAvatarSelector().evaluate()?.closest('a') - if (ele) { - const link = ele.getAttribute('href') - if (link) { - const [, userId] = link.match(/^\/(\w+)\/(photo|nft)$/) ?? [] - return userId - } - } + if (!ele) return '' - return '' + const link = ele.getAttribute('href') + if (!link) return '' + + const [, userId] = link.match(/^\/(\w+)\/(photo|nft)$/) ?? [] + return userId } export const getBio = () => { @@ -67,20 +65,14 @@ export const getAvatar = () => { export async function getUserIdentity(twitterId: string): Promise { const user = await Twitter.getUserByScreenName(twitterId) - if (!user?.legacy) return - - const nickname = user.legacy.name - const handle = user.legacy.screen_name - const avatar = user.legacy.profile_image_url_https.replace(/_normal(\.\w+)$/, '_400x400$1') - const bio = user.legacy.description - const homepage = user.legacy.entities.url?.urls[0]?.expanded_url ?? '' + if (!user) return return { - identifier: ProfileIdentifier.of(twitterBase.networkIdentifier, handle).unwrapOr(undefined), - nickname, - avatar, - bio, - homepage, + identifier: ProfileIdentifier.of(twitterBase.networkIdentifier, user.screenName).unwrapOr(undefined), + nickname: user.nickname, + avatar: user.avatarURL, + bio: user.bio, + homepage: user.homepage, } } diff --git a/packages/mask/src/utils/createNormalReactRoot.tsx b/packages/mask/src/utils/createNormalReactRoot.tsx index 153c5c0d2803..6c347ca3d3a9 100644 --- a/packages/mask/src/utils/createNormalReactRoot.tsx +++ b/packages/mask/src/utils/createNormalReactRoot.tsx @@ -1,7 +1,6 @@ -import { createContext, StrictMode } from 'react' +import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { DisableShadowRootContext } from '@masknet/theme' -import { once } from 'lodash-es' function cleanup() { if (process.env.NODE_ENV === 'development') { @@ -29,35 +28,3 @@ export function createNormalReactRoot(jsx: JSX.Element, dom?: HTMLElement) { const container = getContainer(dom) return createRoot(container).render(Root(jsx)) } - -// Note: we should not really do this. the normal way is to call hydrateRoot. -// but we have too many useSyncExternalStore calls that does not provides onHydrate (3rd argument) -// therefore hydrate actually works worse than render. -export function hydrateNormalReactRoot(jsx: JSX.Element, dom?: HTMLElement) { - cleanup() - const cursorLoadingHint = document.createElement('style') - cursorLoadingHint.innerText = 'button, a { cursor: wait !important; }' - document.head.appendChild(cursorLoadingHint) - - const container = getContainer(dom) - container.style.display = 'none' - let called = 0 - function hydrateFinished() { - // in dev mode each component will be called twice therefore we only add 0.5 - called += process.env.NODE_ENV === 'development' ? 0.5 : 1 - // this is a magic number, the times we re-render before the real UI show up. - if (called < 25) return - replace() - } - const replace = once(() => { - const old = document.getElementById('root-ssr') - if (old) old.style.display = 'none' - container.style.display = 'initial' - cursorLoadingHint.remove() - }) - setTimeout(replace, 1500) - return createRoot(container).render( - {Root(jsx)}, - ) -} -export const HydrateFinished = createContext(() => {}) diff --git a/packages/mask/src/utils/fetcher.ts b/packages/mask/src/utils/fetcher.ts index 66b69eede156..8df9e76db50c 100644 --- a/packages/mask/src/utils/fetcher.ts +++ b/packages/mask/src/utils/fetcher.ts @@ -38,7 +38,7 @@ function canAccessAsContent(url: string) { const target = new URL(url, location.href) if ( location.origin.endsWith('twitter.com') && - ['https://abs.twimg.com', 'https://upload.twitter.com'].includes(target.origin) + (target.origin.endsWith('twitter.com') || target.origin.endsWith('twimg.com')) ) return true diff --git a/packages/mask/src/utils/i18n-next-ui.ts b/packages/mask/src/utils/i18n-next-ui.ts index 72baa5499cd9..7326454e4c1e 100644 --- a/packages/mask/src/utils/i18n-next-ui.ts +++ b/packages/mask/src/utils/i18n-next-ui.ts @@ -1,5 +1,3 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - import { useTranslation, type UseTranslationOptions } from 'react-i18next' import type en from '../../shared-ui/locales/en-US.json' import type { i18NextInstance } from '@masknet/shared-base' diff --git a/packages/mask/src/utils/theme/MaskTheme.ts b/packages/mask/src/utils/theme/MaskTheme.ts index 388efa5a0441..7f37cb0346b1 100644 --- a/packages/mask/src/utils/theme/MaskTheme.ts +++ b/packages/mask/src/utils/theme/MaskTheme.ts @@ -1,5 +1,3 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - import { unstable_createMuiStrictModeTheme, type ThemeOptions } from '@mui/material' import { grey, orange } from '@mui/material/colors' import { cloneDeep, merge } from 'lodash-es' diff --git a/packages/mask/src/utils/theme/useThemeLanguage.ts b/packages/mask/src/utils/theme/useThemeLanguage.ts index 59840084872c..932d222fc599 100644 --- a/packages/mask/src/utils/theme/useThemeLanguage.ts +++ b/packages/mask/src/utils/theme/useThemeLanguage.ts @@ -1,5 +1,3 @@ -// ! This file is used during SSR. DO NOT import new files that does not work in SSR - import { LanguageOptions, SupportedLanguages } from '@masknet/public-api' import { jaJP, koKR, zhTW, zhCN, enUS, type Localization } from '@mui/material/locale/index.js' import { updateLanguage } from '@masknet/shared-base' diff --git a/packages/plugin-infra/src/site-adaptor/context.ts b/packages/plugin-infra/src/site-adaptor/context.ts index db9bf67b0716..8ae4aeb4079a 100644 --- a/packages/plugin-infra/src/site-adaptor/context.ts +++ b/packages/plugin-infra/src/site-adaptor/context.ts @@ -19,5 +19,5 @@ export let currentNextIDPlatform: NextIDPlatform | undefined export let currentPersonaIdentifier: __SiteAdaptorContext__['currentPersonaIdentifier'] export function __setSiteAdaptorContext__(value: __SiteAdaptorContext__) { __setUIContext__(value) - ;({ lastRecognizedProfile, currentVisitingProfile, currentNextIDPlatform } = value) + ;({ lastRecognizedProfile, currentVisitingProfile, currentNextIDPlatform, currentPersonaIdentifier } = value) } diff --git a/packages/plugin-infra/src/types.ts b/packages/plugin-infra/src/types.ts index 0422b5fdac56..43d98fed471f 100644 --- a/packages/plugin-infra/src/types.ts +++ b/packages/plugin-infra/src/types.ts @@ -121,6 +121,7 @@ export namespace Plugin.Shared { /** Select a Mask Wallet account */ selectMaskWalletAccount( chainId: ChainId, + defaultAccount?: string, ): Promise> /** Open Dashboard with a new window */ @@ -356,8 +357,6 @@ export namespace Plugin.SiteAdaptor { getSearchedKeyword?: () => string hasHostPermission?: (origins: readonly string[]) => Promise requestHostPermission?: (origins: readonly string[]) => Promise - getDecentralizedSearchSettings?: () => Promise - setDecentralizedSearchSettings?: (checked: boolean) => Promise } export interface Definition extends GeneralUI.Definition, Shared.DefinitionDeferred { diff --git a/packages/plugins/Approval/src/SiteAdaptor/ApprovalDialog.tsx b/packages/plugins/Approval/src/SiteAdaptor/ApprovalDialog.tsx index 193cd46d52a1..a9044fc3e107 100644 --- a/packages/plugins/Approval/src/SiteAdaptor/ApprovalDialog.tsx +++ b/packages/plugins/Approval/src/SiteAdaptor/ApprovalDialog.tsx @@ -5,65 +5,64 @@ import { TabContext } from '@mui/lab' import { PluginWalletStatusBar, InjectedDialog, NetworkTab } from '@masknet/shared' import { useChainContext } from '@masknet/web3-hooks-base' import { type ChainId } from '@masknet/web3-shared-evm' -import { NetworkPluginID, PluginID } from '@masknet/shared-base' +import { EMPTY_LIST, NetworkPluginID, PluginID } from '@masknet/shared-base' import { useActivatedPlugin } from '@masknet/plugin-infra/dom' import { useI18N } from '../locales/index.js' import { ApprovalTokenContent } from './ApprovalTokenContent.js' import { ApprovalNFTContent } from './ApprovalNFTContent.js' +import { useMemo } from 'react' -const useStyles = makeStyles<{ listItemBackground?: string; listItemBackgroundIcon?: string } | void>()( - (theme, props) => ({ - dialogRoot: { - width: 600, - height: 620, - overflowX: 'hidden', +const useStyles = makeStyles()((theme, props) => ({ + dialogRoot: { + width: 600, + height: 620, + overflowX: 'hidden', + }, + dialogContent: { + width: 600, + background: theme.palette.maskColor.bottom, + padding: 0, + margin: 'auto', + overflowX: 'hidden', + }, + contentWrapper: { + width: 602, + padding: 0, + overflowY: 'auto', + overflowX: 'hidden', + height: '100%', + '::-webkit-scrollbar': { + backgroundColor: 'transparent', + width: 18, }, - dialogContent: { - width: 600, - background: theme.palette.maskColor.bottom, - padding: 0, - margin: 'auto', - overflowX: 'hidden', + '::-webkit-scrollbar-thumb': { + borderRadius: '20px', + width: 5, + border: '7px solid rgba(0, 0, 0, 0)', + backgroundColor: theme.palette.maskColor.secondaryLine, + backgroundClip: 'padding-box', }, - contentWrapper: { - width: 602, - padding: 0, - overflowY: 'auto', - overflowX: 'hidden', - height: '100%', - '::-webkit-scrollbar': { - backgroundColor: 'transparent', - width: 18, - }, - '::-webkit-scrollbar-thumb': { - borderRadius: '20px', - width: 5, - border: '7px solid rgba(0, 0, 0, 0)', - backgroundColor: theme.palette.maskColor.secondaryLine, - backgroundClip: 'padding-box', - }, + }, + dialogTitle: { + '& > p': { + overflow: 'visible', }, - dialogTitle: { - '& > p': { - overflow: 'visible', - }, - }, - abstractTabWrapper: { - width: '100%', - paddingBottom: theme.spacing(2), - }, - approvalWrapper: { - display: 'flex', - flexDirection: 'column', - width: '100%', - height: '100%', - }, - footer: { - position: 'sticky', - bottom: 0, - }, - }), -) + }, + abstractTabWrapper: { + width: '100%', + paddingBottom: theme.spacing(2), + }, + approvalWrapper: { + display: 'flex', + flexDirection: 'column', + width: '100%', + height: '100%', + }, + footer: { + position: 'sticky', + bottom: 0, + }, +})) export interface ApprovalDialogProps { open: boolean @@ -111,9 +110,11 @@ function ApprovalWrapper(props: ApprovalWrapperProps) { const { chainId } = useChainContext() const approvalDefinition = useActivatedPlugin(PluginID.Approval, 'any') - const chainIdList = compact( - approvalDefinition?.enableRequirement.web3?.[NetworkPluginID.PLUGIN_EVM]?.supportedChainIds ?? [], - ) + const chainIdList = useMemo(() => { + return compact( + approvalDefinition?.enableRequirement.web3?.[NetworkPluginID.PLUGIN_EVM]?.supportedChainIds ?? EMPTY_LIST, + ) + }, [approvalDefinition]) const { classes } = useStyles() return ( diff --git a/packages/plugins/Approval/src/SiteAdaptor/pipes.ts b/packages/plugins/Approval/src/SiteAdaptor/pipes.ts index bd407257db6a..035900c743c6 100644 --- a/packages/plugins/Approval/src/SiteAdaptor/pipes.ts +++ b/packages/plugins/Approval/src/SiteAdaptor/pipes.ts @@ -20,6 +20,7 @@ export const resolveNetworkOnRabby = createLookupTableResolver ({ width: '100%', }, cancel: { - backgroundColor: theme.palette.background.default, - color: theme.palette.mode === 'dark' ? '#FFFFFF' : '#111418', - border: 'none', '&:hover': { border: 'none', + background: theme.palette.maskColor.bottom, }, }, content: { diff --git a/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx b/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx index b56818bcc355..729395c21f53 100644 --- a/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx +++ b/packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx @@ -1,18 +1,18 @@ /* cspell: disable */ import React, { useState, useMemo } from 'react' +import { Tab } from '@mui/material' +import { TabContext, TabPanel } from '@mui/lab' +import { safeUnreachable } from '@masknet/kit' import { PluginID } from '@masknet/shared-base' import { useIsMinimalMode } from '@masknet/plugin-infra/content-script' import { makeStyles, MaskTabList, useTabs } from '@masknet/theme' -import { Tab } from '@mui/material' -import { TabContext, TabPanel } from '@mui/lab' +import { useLocationChange } from '@masknet/shared-base-ui' import { DatePickerTab } from './components/DatePickerTab.js' import { useEventList, useNFTList, useNewsList } from '../hooks/useEventList.js' -import { EventList } from './components/EventList.js' import { NewsList } from './components/NewsList.js' +import { EventList } from './components/EventList.js' import { NFTList } from './components/NFTList.js' import { Footer } from './components/Footer.js' -import { safeUnreachable } from '@masknet/kit' -import { DatePicker } from './components/DatePicker.js' import { useI18N } from '../locales/i18n_generated.js' const useStyles = makeStyles()((theme) => ({ @@ -21,11 +21,8 @@ const useStyles = makeStyles()((theme) => ({ flexDirection: 'column', borderRadius: '12px', border: `1px solid ${theme.palette.maskColor.line}`, - marginTop: '60px', position: 'relative', - }, - hidden: { - display: 'hidden', + marginBottom: '20px', }, tab: { fontSize: 16, @@ -37,18 +34,22 @@ const useStyles = makeStyles()((theme) => ({ padding: '8px 16px 0 16px', borderRadius: '12px 12px 0 0', }, + tabPanel: { + padding: '0 4px 0 12px', + }, })) -export function CalendarContent() { +export function CalendarContent({ target }: { target?: string }) { + const t = useI18N() const { classes } = useStyles() - const disable = useIsMinimalMode(PluginID.Calendar) + const [pathname, setPathname] = useState(location.pathname) + const isMinimalMode = useIsMinimalMode(PluginID.Calendar) const [currentTab, onChange, tabs] = useTabs('news', 'event', 'nfts') const [selectedDate, setSelectedDate] = useState(new Date()) const [open, setOpen] = useState(false) - const t = useI18N() - const { data: eventList, isLoading: eventLoading } = useEventList() - const { data: newsList, isLoading: newsLoading } = useNewsList() - const { data: nftList, isLoading: nftLoading } = useNFTList() + const { data: eventList, isLoading: eventLoading } = useEventList(selectedDate) + const { data: newsList, isLoading: newsLoading } = useNewsList(selectedDate) + const { data: nftList, isLoading: nftLoading } = useNFTList(selectedDate) const list = useMemo(() => { switch (currentTab) { case 'news': @@ -63,8 +64,14 @@ export function CalendarContent() { } }, [currentTab, newsList, eventList, nftList]) const dateString = useMemo(() => selectedDate.toLocaleDateString(), [selectedDate]) + + useLocationChange(() => { + setPathname(location.pathname) + }) + if (isMinimalMode || (target && !pathname?.includes(target))) return null + return ( -
    +
    @@ -79,30 +86,31 @@ export function CalendarContent() { selectedDate={selectedDate} setSelectedDate={(date: Date) => setSelectedDate(date)} list={list} + currentTab={currentTab} /> - setOpen(open)} - list={list} - selectedDate={selectedDate} - setSelectedDate={setSelectedDate} - /> - + - + - - + +