diff --git a/src/features/accounts/components/accounts-menu/accounts-menu.tsx b/src/features/accounts/components/accounts-menu/accounts-menu.tsx index 70f542c..01be66a 100644 --- a/src/features/accounts/components/accounts-menu/accounts-menu.tsx +++ b/src/features/accounts/components/accounts-menu/accounts-menu.tsx @@ -1,4 +1,4 @@ -import React from "react" +import { useEffect, useState } from "react" import { AnonymousIdentity, ANON_IDENTITY, @@ -26,10 +26,12 @@ import { useDisclosure, VStack, UsbIcon, + useToast, } from "@liftedinit/ui" import { AddAccountModal } from "./add-account-modal" import { EditAccountModal } from "./edit-account-modal" import { Account, AccountId } from "../../types" +import { useNetworkContext } from "features/network" export type AccountItemWithIdDisplayStrings = [ AccountId, @@ -64,10 +66,28 @@ export function AccountsMenu() { }), ) - const [editAccount, setEditAccount] = React.useState< + const [editAccount, setEditAccount] = useState< [number, Account] | undefined >() + const toast = useToast() + const { services } = useNetworkContext() + useEffect(() => { + ; (async () => { + const isWebAuthnIdentity = + activeAccount?.identity instanceof WebAuthnIdentity + if (isWebAuthnIdentity && !services.has("idstore")) { + setActiveId(0) // reset to Anonymous + toast({ + status: "warning", + title: "Unsupported Identity", + description: + "Selected Neighborhood does not support Hardware Authenticators", + }) + } + })() + }, [activeAccount, services, setActiveId, toast]) + function onEditClick(acct: [number, Account]) { setEditAccount(acct) onEditModalOpen() diff --git a/src/features/accounts/components/accounts-menu/add-account-modal.tsx b/src/features/accounts/components/accounts-menu/add-account-modal.tsx index 0b7800b..43e7251 100644 --- a/src/features/accounts/components/accounts-menu/add-account-modal.tsx +++ b/src/features/accounts/components/accounts-menu/add-account-modal.tsx @@ -10,6 +10,7 @@ import { Tabs, VStack, } from "@liftedinit/ui" +import { useNetworkContext } from "features/network" import React from "react" import { SocialLogin } from "../social-login" @@ -87,13 +88,13 @@ export function AddAccountModal({ )} {(addMethod === AddAccountMethodTypes.importAuthenticator || addMethod === AddAccountMethodTypes.createAuthenticator) && ( - - )} + + )} {showDefaultFooter && ( @@ -164,6 +165,7 @@ const createCards = [ label: "Hardware Authenticator", title: "create new with hardware authenticator", onClickArg: AddAccountMethodTypes.createAuthenticator, + requires: "idstore", }, ] @@ -172,18 +174,21 @@ function CreateAccountOptions({ }: { onAddMethodClick: (method: AddAccountMethodTypes) => void }) { + const { services } = useNetworkContext() return ( - {createCards.map((c, idx) => { - return ( - onAddMethodClick(c.onClickArg)} - /> - ) - })} + {createCards + .filter(c => !c.requires || services.has(c.requires)) + .map((c, idx) => { + return ( + onAddMethodClick(c.onClickArg)} + /> + ) + })} ) } @@ -203,6 +208,7 @@ const importCards = [ label: "Hardware Authenticator", title: "import with hardware authenticator", onClickArg: AddAccountMethodTypes.importAuthenticator, + requires: "idstore", }, ] function ImportAcountOptions({ @@ -210,18 +216,21 @@ function ImportAcountOptions({ }: { onAddMethodClick: (method: AddAccountMethodTypes) => void }) { + const { services } = useNetworkContext() return ( - {importCards.map((c, idx) => { - return ( - onAddMethodClick(c.onClickArg)} - /> - ) - })} + {importCards + .filter(c => !c.requires || services.has(c.requires)) + .map((c, idx) => { + return ( + onAddMethodClick(c.onClickArg)} + /> + ) + })} ) } diff --git a/src/features/network/network-provider.tsx b/src/features/network/network-provider.tsx index 6b34674..8da4943 100644 --- a/src/features/network/network-provider.tsx +++ b/src/features/network/network-provider.tsx @@ -1,4 +1,11 @@ -import React, { createContext, ReactNode, useContext, useMemo } from "react" +import { + createContext, + ReactNode, + useContext, + useEffect, + useMemo, + useState, +} from "react" import { Network, Ledger, @@ -15,14 +22,18 @@ export interface INetworkContext { query?: Network command?: Network legacy?: Network[] // Legacy networks are past networks that are no longer active. They are used to query for past events. + services: Set } -export const NetworkContext = createContext({}) +export const NetworkContext = createContext({ + services: new Set(), +}) export function NetworkProvider({ children }: { children: ReactNode }) { const network = useNetworkStore().getActiveNetwork() const legacy_networks = useNetworkStore().getLegacyNetworks() const account = useAccountsStore(s => s.byId.get(s.activeId)) + const [services, setServices] = useState>(new Set()) const url = network.url const legacy_urls = legacy_networks?.map(n => n.url) @@ -43,8 +54,28 @@ export function NetworkProvider({ children }: { children: ReactNode }) { const command = new Network(url, identity) command.apply([Ledger, IdStore, Account]) - return { query, command, legacy } - }, [account, url, legacy_urls]) + return { query, command, legacy, services } + }, [account, url, legacy_urls, services]) + + useEffect(() => { + async function updateServices() { + if (!context.query || !context.query.base) { + return + } + const updated = new Set() + try { + const { endpoints } = await context.query.base.endpoints() + endpoints + .map((endpoint: string) => endpoint.split(".")[0]) + .forEach((service: string) => updated.add(service)) + } catch (error) { + console.error(`Couldn't update services: ${(error as Error).message}`) + } + setServices(updated) + } + updateServices() + // eslint-disable-next-line + }, [url]) return (