diff --git a/apps/extension/src/App/Accounts/ParentAccounts.tsx b/apps/extension/src/App/Accounts/ParentAccounts.tsx index ef375f7ad3..b437d43548 100644 --- a/apps/extension/src/App/Accounts/ParentAccounts.tsx +++ b/apps/extension/src/App/Accounts/ParentAccounts.tsx @@ -1,5 +1,5 @@ -import { useContext } from "react"; -import { useNavigate } from "react-router-dom"; +import { useContext, useEffect } from "react"; +import { Outlet, useNavigate } from "react-router-dom"; import { ActionButton, @@ -7,7 +7,7 @@ import { KeyListItem, Stack, } from "@namada/components"; -import { DerivedAccount } from "@namada/types"; +import { AccountType, DerivedAccount } from "@namada/types"; import { ParentAccountsFooter } from "App/Accounts/ParentAccountsFooter"; import { PageHeader } from "App/Common"; import routes from "App/routes"; @@ -20,8 +20,37 @@ import { openSetupTab } from "utils"; */ export const ParentAccounts = (): JSX.Element => { const navigate = useNavigate(); - const { activeAccountId, parentAccounts, changeActiveAccountId } = - useContext(AccountContext); + const { + activeAccountId, + parentAccounts, + accounts: allAccounts, + changeActiveAccountId, + } = useContext(AccountContext); + + // We check which accounts need to be re-imported + const accounts = allAccounts + .filter( + (account) => account.parentId || account.type === AccountType.Ledger + ) + .map((account) => { + const outdated = + account.type !== AccountType.Ledger && + typeof account.pseudoExtendedKey === "undefined"; + + // The only account without a parent is the ledger account + const parent = + parentAccounts.find((pa) => pa.id === account.parentId) || account; + + return { ...parent, outdated }; + }); + + useEffect(() => { + const hasOutdatedAccounts = accounts.some((account) => account.outdated); + // If there are outdated accounts, we redirect to the update required page + if (hasOutdatedAccounts) { + navigate(routes.accountsUpdateRequired()); + } + }, []); const goToSetupPage = async (): Promise => { await openSetupTab(); @@ -44,47 +73,52 @@ export const ParentAccounts = (): JSX.Element => { }; return ( - - - - - - {[...parentAccounts].reverse().map((account, idx) => ( - 2 && idx > parentAccounts.length - 4 ? "bottom" : "top" - } - isMainKey={activeAccountId === account.id} - onRename={() => goToRenameAccount(account)} - onDelete={() => goToDeletePage(account)} - onViewAccount={() => goToViewAccount(account)} - onViewRecoveryPhrase={() => goToViewRecoveryPhrase(account)} - onSelectAccount={() => { - changeActiveAccountId( - account.id, - account.type as ParentAccount - ); - }} - /> - ))} + <> + + + + + + {[...accounts].reverse().map((account, idx) => ( + 2 && idx > accounts.length - 4 ? "bottom" : "top" + } + isMainKey={activeAccountId === account.id} + onRename={() => goToRenameAccount(account)} + onDelete={() => goToDeletePage(account)} + onViewAccount={() => goToViewAccount(account)} + onViewRecoveryPhrase={() => goToViewRecoveryPhrase(account)} + onSelectAccount={() => { + changeActiveAccountId( + account.id, + account.type as ParentAccount + ); + }} + /> + ))} + + - - + + + ); }; diff --git a/apps/extension/src/App/Accounts/UpdateRequired.tsx b/apps/extension/src/App/Accounts/UpdateRequired.tsx new file mode 100644 index 0000000000..d2dbad7dd1 --- /dev/null +++ b/apps/extension/src/App/Accounts/UpdateRequired.tsx @@ -0,0 +1,88 @@ +import clsx from "clsx"; +import { IoClose } from "react-icons/io5"; +import { PiWarning } from "react-icons/pi"; +import { useNavigate } from "react-router-dom"; + +import { Modal, Stack } from "@namada/components"; +import routes from "App/routes"; +import updateRequried from "./assets/update-required.png"; + +export const UpdateRequired = (): JSX.Element => { + const navigate = useNavigate(); + + const onCloseModal = (): void => { + navigate(routes.viewAccountList()); + }; + + return ( + +
+
+ +
+
+ + + +

+ Account update required! +

+
+

+ Accounts marked with an exclamation symbol +
+ + (){" "} + + need to be re-imported to support shielded transfers.* +

+ +

To Re-import your account:

+
    +
  1. + If necessary. Re-copy and note
down your seed phrase / private + key +
    + + Accounts CAN NOT be re-imported without the seed phrase + +
  2. +
  3. Delete the marked account from 
the keychain
  4. +
  5. + Re-Import the account using your seed phrase / private key +
  6. +
+
+

+ * Ledger accounts will receive shielded +
functions in a separate update in an +
upcoming release +

+
+
+
+
+ ); +}; diff --git a/apps/extension/src/App/Accounts/assets/update-required.png b/apps/extension/src/App/Accounts/assets/update-required.png new file mode 100644 index 0000000000..c09e49f50f Binary files /dev/null and b/apps/extension/src/App/Accounts/assets/update-required.png differ diff --git a/apps/extension/src/App/Accounts/index.ts b/apps/extension/src/App/Accounts/index.ts index 137fc07868..d9dc4d0400 100644 --- a/apps/extension/src/App/Accounts/index.ts +++ b/apps/extension/src/App/Accounts/index.ts @@ -1,4 +1,5 @@ export * from "./DeleteAccount"; export * from "./RenameAccount"; +export * from "./UpdateRequired"; export * from "./ViewAccount"; export * from "./ViewMnemonic"; diff --git a/apps/extension/src/App/AppContent.tsx b/apps/extension/src/App/AppContent.tsx index 0aaff8d78a..8db957c8bb 100644 --- a/apps/extension/src/App/AppContent.tsx +++ b/apps/extension/src/App/AppContent.tsx @@ -9,6 +9,7 @@ import { openSetupTab } from "utils"; import { DeleteAccount, RenameAccount, + UpdateRequired, ViewAccount, ViewMnemonic, } from "./Accounts"; @@ -86,10 +87,12 @@ export const AppContent = ({ warnings }: Props): JSX.Element => { path={routes.viewAccountMnemonic()} element={} /> - } - /> + }> + } + /> + )} diff --git a/apps/extension/src/App/routes.ts b/apps/extension/src/App/routes.ts index 7c11ff43b5..5e358630f0 100644 --- a/apps/extension/src/App/routes.ts +++ b/apps/extension/src/App/routes.ts @@ -8,6 +8,7 @@ export default { network: (): string => `/network`, warnings: (): string => `/warnings`, viewAccountList: () => `/accounts/view`, + accountsUpdateRequired: () => `/accounts/view/update-required`, viewAccountMnemonic: (accountId: string = ":accountId") => `/accounts/mnemonic/${accountId}`, viewAccount: (accountId: string = ":accountId") => diff --git a/apps/extension/src/react-app.env.d.ts b/apps/extension/src/react-app.env.d.ts new file mode 100644 index 0000000000..49977505d3 --- /dev/null +++ b/apps/extension/src/react-app.env.d.ts @@ -0,0 +1 @@ +declare module "*.png"; diff --git a/apps/extension/webpack.config.js b/apps/extension/webpack.config.js index af267d0673..d7f982e734 100644 --- a/apps/extension/webpack.config.js +++ b/apps/extension/webpack.config.js @@ -197,6 +197,14 @@ module.exports = { and: [/\.(ts|tsx|md)$/], }, }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [ + { + loader: require.resolve("file-loader"), + }, + ], + }, ], }, resolve: { diff --git a/packages/components/src/KeyListItem.tsx b/packages/components/src/KeyListItem.tsx index bc0d86f6fe..d03c423c0d 100644 --- a/packages/components/src/KeyListItem.tsx +++ b/packages/components/src/KeyListItem.tsx @@ -1,14 +1,16 @@ -import { Checkbox, DropdownMenu } from "@namada/components"; +import { Checkbox, DropdownMenu, Stack } from "@namada/components"; import { AccountType } from "@namada/types"; import clsx from "clsx"; import { createElement } from "react"; +import { PiWarning } from "react-icons/pi"; import { tv } from "tailwind-variants"; type KeyListItemProps = { as: keyof React.ReactHTML; alias: string; type: AccountType; + outdated: boolean; isMainKey: boolean; onRename: () => void; onDelete: () => void; @@ -21,7 +23,7 @@ type KeyListItemProps = { const keyListItem = tv({ base: clsx( "flex items-center bg-black rounded-md text-white", - "grid text-base font-medium gap-8 grid-cols-[24px_auto_16px] p-5" + "grid text-base font-medium gap-8 grid-cols-[24px_1fr_auto] p-5" ), variants: { selected: { @@ -34,6 +36,7 @@ export const KeyListItem = ({ alias, isMainKey, type, + outdated, onDelete, onRename, onViewAccount, @@ -53,37 +56,47 @@ export const KeyListItem = ({ checked={isMainKey} /> -