From 07f8987da684914a861eabef33cec3e4ac05c3c0 Mon Sep 17 00:00:00 2001
From: mtyszczak <mateusz.tyszczak@gmail.com>
Date: Tue, 18 Mar 2025 12:26:07 +0100
Subject: [PATCH] Add dynamic route detection

---
 src/App.vue                                   |  9 +-
 .../onboarding/OnboardingWalletButton.vue     |  3 +-
 src/components/onboarding/SelectWallet.vue    | 57 ++++++------
 .../onboarding/WalletOnboarding.vue           |  4 +-
 src/components/sidebar/AppSidebar.vue         | 87 +++++++++++--------
 src/components/ui/sidebar/index.ts            |  2 +-
 src/stores/wallet.store.ts                    | 32 ++++++-
 7 files changed, 118 insertions(+), 76 deletions(-)

diff --git a/src/App.vue b/src/App.vue
index ba83458..31d2b94 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -6,8 +6,9 @@ import AppSidebar from '@/components/sidebar';
 import { SidebarProvider } from '@/components/ui/sidebar';
 import ToggleSidebar from './components/sidebar/ToggleSidebar.vue';
 import { Toaster } from 'vue-sonner';
-import { useUserStore } from './stores/user.store';
-import { getWax } from './stores/wax.store';
+import { useUserStore } from '@/stores/user.store';
+import { getWax } from '@/stores/wax.store';
+import AppHeader from '@/components/AppHeader.vue';
 
 const WalletOnboarding = defineAsyncComponent(() => import('@/components/onboarding/index'));
 
@@ -49,8 +50,8 @@ const complete = async(data: { account: string; wallet: UsedWallet }) => {
           <ToggleSidebar class="m-3" />
           <RouterView />
         </main>
-        <aside v-if="settingsStore.isLoaded && !hasUser" class="fixed inset-0 flex items-center justify-center z-20">
-          <WalletOnboarding @complete="complete" />
+        <aside v-if="walletStore.isWalletSelectModalOpen" class="fixed inset-0 flex items-center justify-center z-20">
+          <WalletOnboarding @close="walletStore.closeWalletSelectModal()" @complete="complete" />
         </aside>
       </SidebarProvider>
       <Toaster theme="dark" closeButton richColors />
diff --git a/src/components/onboarding/OnboardingWalletButton.vue b/src/components/onboarding/OnboardingWalletButton.vue
index c23034c..0ee361f 100644
--- a/src/components/onboarding/OnboardingWalletButton.vue
+++ b/src/components/onboarding/OnboardingWalletButton.vue
@@ -11,6 +11,7 @@ const props = defineProps<{
   description: string;
   disabled?: boolean;
   downloadUrl: string;
+  downloadUrlTriggersClick?: boolean;
 }>();
 
 const emit = defineEmits(['click']);
@@ -28,7 +29,7 @@ const emit = defineEmits(['click']);
     <TooltipProvider :delayDuration="200" disableHoverableContent>
       <Tooltip>
         <TooltipTrigger class="absolute right-4 top-1/2 transform -translate-y-1/2 w-8 h-8">
-          <a :href="props.downloadUrl" v-if="props.disabled" target="_blank">
+          <a :href="props.downloadUrl" @click="props.downloadUrlTriggersClick && emit('click')" v-if="props.disabled" target="_blank">
             <Button variant="ghost" class="w-8 h-8 p-0">
               <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsl(var(--foreground))" :d="mdiDownload"/></svg>
             </Button>
diff --git a/src/components/onboarding/SelectWallet.vue b/src/components/onboarding/SelectWallet.vue
index ebb956b..06094a1 100644
--- a/src/components/onboarding/SelectWallet.vue
+++ b/src/components/onboarding/SelectWallet.vue
@@ -1,52 +1,45 @@
 <script setup lang="ts">
 import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
 import OnboardingButton from "@/components/onboarding/OnboardingWalletButton.vue";
-import { onMounted, onUnmounted, ref } from 'vue';
-import { useMetamaskStore } from "@/stores/metamask.store";
+import { Button } from "@/components/ui/button";
 import { getWalletIcon, UsedWallet } from "@/stores/settings.store";
+import { useWalletStore } from "@/stores/wallet.store";
+import { mdiClose } from "@mdi/js";
+import { computed } from "vue";
 
-const hasMetamask = ref(false);
-const hasKeychain = ref(false);
-const hasPeakVault = ref(false);
-let timeoutId: number;
+const walletStore = useWalletStore();
+const walletsStatus = computed(() => walletStore.walletsStatus);
 
-const metamaskStore = useMetamaskStore();
-
-const checkForWallets = () => {
-  if (!hasMetamask.value)
-    metamaskStore.connect().then(() => hasMetamask.value = true).catch(console.error);
-  if (!hasKeychain.value)
-    hasKeychain.value = "hive_keychain" in window;
-  if (!hasPeakVault.value)
-    hasPeakVault.value = "peakvault" in window;
-};
-
-onMounted(() => {
-  timeoutId = setTimeout(() => checkForWallets(), 1500) as unknown as number;
-  checkForWallets();
-});
-
-onUnmounted(() => {
-  clearTimeout(timeoutId);
-});
-
-const emit = defineEmits(["walletSelect"]);
+const emit = defineEmits(["walletSelect", "close"]);
 
 const useWallet = (type: UsedWallet) => {
   emit("walletSelect", type);
 };
+
+const close = () => {
+  emit("close");
+};
 </script>
 
 <template>
   <Card class="w-[350px]">
     <CardHeader>
-      <CardTitle>Select wallet</CardTitle>
-      <CardDescription>We support multiple on-chain wallets</CardDescription>
+      <CardTitle>
+        <div class="inline-flex justify-between w-full">
+          <div class="inline-flex items-center">
+            <span class="mt-[2px]">Select wallet</span>
+          </div>
+          <Button variant="ghost" size="sm" class="px-2" @click="close">
+            <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsl(var(--foreground))" :d="mdiClose"/></svg>
+          </Button>
+        </div>
+      </CardTitle>
+      <CardDescription>If your wallet is not detected, try unlocking it and refreshing the page</CardDescription>
     </CardHeader>
     <CardContent class="space-y-2">
-      <OnboardingButton downloadUrl="https://docs.metamask.io/snaps/get-started/install-flask/" :disabled="!hasMetamask" @click="useWallet(UsedWallet.METAMASK)" :logoUrl="getWalletIcon(UsedWallet.METAMASK)" name="Metamask" description="Use your derived keys"/>
-      <OnboardingButton downloadUrl="https://hive-keychain.com/" :disabled="!hasKeychain" @click="useWallet(UsedWallet.KEYCHAIN)" :logoUrl="getWalletIcon(UsedWallet.KEYCHAIN)" name="Keychain" description="Use already imported accounts"/>
-      <OnboardingButton downloadUrl="https://vault.peakd.com/peakvault/guide.html#installation" :disabled="!hasPeakVault" @click="useWallet(UsedWallet.PEAKVAULT)" :logoUrl="getWalletIcon(UsedWallet.PEAKVAULT)" name="PeakVault" description="Use already imported accounts"/>
+      <OnboardingButton downloadUrl="https://docs.metamask.io/snaps/get-started/install-flask/" :disabled="!walletsStatus.metamask" @click="useWallet(UsedWallet.METAMASK)" :logoUrl="getWalletIcon(UsedWallet.METAMASK)" name="Metamask" description="Use your derived keys"/>
+      <OnboardingButton downloadUrl="https://hive-keychain.com/" :disabled="!walletsStatus.keychain" @click="useWallet(UsedWallet.KEYCHAIN)" :logoUrl="getWalletIcon(UsedWallet.KEYCHAIN)" name="Keychain" description="Use already imported accounts"/>
+      <OnboardingButton downloadUrl="https://vault.peakd.com/peakvault/guide.html#installation" :disabled="!walletsStatus.peakvault" @click="useWallet(UsedWallet.PEAKVAULT)" :logoUrl="getWalletIcon(UsedWallet.PEAKVAULT)" name="PeakVault" description="Use already imported accounts"/>
     </CardContent>
     <CardFooter></CardFooter>
   </Card>
diff --git a/src/components/onboarding/WalletOnboarding.vue b/src/components/onboarding/WalletOnboarding.vue
index 0a0135b..7c77292 100644
--- a/src/components/onboarding/WalletOnboarding.vue
+++ b/src/components/onboarding/WalletOnboarding.vue
@@ -7,7 +7,7 @@ import KeychainConnect from '@/components/onboarding/wallets/keychain/KeychainCo
 import MetamaskConnect from '@/components/onboarding/wallets/metamask/MetamaskConnect.vue';
 import ThankYou from '@/components/onboarding/ThankYou.vue';
 
-const emit = defineEmits(["complete"]);
+const emit = defineEmits(["complete", "close"]);
 
 const selectedWallet = ref<UsedWallet | null>(null);
 const selectedAccount = ref<string | null>(null);
@@ -49,7 +49,7 @@ const backToStage1 = () => {
 <template>
   <div class="bg-black/30 backdrop-blur-sm h-full w-full z-50 flex items-center justify-center">
     <div class="onboarding-container">
-      <SelectWallet v-if="stage_1_SelectWallet" @walletSelect="walletSelect" />
+      <SelectWallet v-if="stage_1_SelectWallet" @close="emit('close')" @walletSelect="walletSelect" />
       <div v-if="stage_2_ConnectWallet">
         <KeychainConnect v-if="selectedWallet === UsedWallet.KEYCHAIN" @close="backToStage1" @setaccount="setAccount" />
         <PeakVaultConnect v-if="selectedWallet === UsedWallet.PEAKVAULT" @close="backToStage1" @setaccount="setAccount" />
diff --git a/src/components/sidebar/AppSidebar.vue b/src/components/sidebar/AppSidebar.vue
index 72ae1ef..896dad4 100644
--- a/src/components/sidebar/AppSidebar.vue
+++ b/src/components/sidebar/AppSidebar.vue
@@ -8,6 +8,8 @@ import { computed } from 'vue';
 import { Button } from '@/components/ui/button';
 import { useUserStore } from "@/stores/user.store";
 import ThemeSwitch from "../ui/theme-switch";
+import { useWalletStore } from "@/stores/wallet.store";
+import { useRouter } from "vue-router";
 
 const settingsStore = useSettingsStore();
 const hasUser = computed(() => settingsStore.settings.account !== undefined);
@@ -17,58 +19,77 @@ const logout = () => {
   window.location.reload();
 };
 
+const router = useRouter();
+
+const walletStore = useWalletStore();
+
 const { toggleSidebar, isMobile } = useSidebar();
 
 const userStore = useUserStore();
 
-const items = [
-  {
-    title: "Home",
-    url: "/",
-    icon: mdiHomeOutline,
-  },
-  {
-    title: "Memo encryption",
-    url: "/sign/message",
-    icon: mdiMessageLockOutline,
-  },
-  {
-    title: "Transaction signing",
-    url: "/sign/transaction",
-    icon: mdiFileSign,
-  },
-  {
-    title: "Process Account Creation",
-    url: "/account/create",
-    icon: mdiAccountPlusOutline,
-  },
+const groups = [{
+  title: "Account management",
+  items: [
+    {
+      title: "Home",
+      url: "/",
+      icon: mdiHomeOutline,
+    },
+    {
+      title: "Process Account Creation",
+      url: "/account/create",
+      icon: mdiAccountPlusOutline,
+    },
+    {
+      title: "Process Authority Update",
+      url: "/account/update",
+      icon: mdiAccountArrowUpOutline,
+    },
+  ]
+}, {
+  title: "Signing",
+  items: [
   {
-    title: "Process Authority Update",
-    url: "/account/update",
-    icon: mdiAccountArrowUpOutline,
-  }
-];
+      title: "Memo encryption",
+      url: "/sign/message",
+      icon: mdiMessageLockOutline,
+    },
+    {
+      title: "Transaction signing",
+      url: "/sign/transaction",
+      icon: mdiFileSign,
+    },
+  ]
+}];
 </script>
 
 <template>
   <Sidebar>
     <SidebarHeader class="pb-0">
+      <div class="flex items-center p-2">
+        <img src="/icon.svg" class="h-8 w-8" />
+        <span class="text-foreground/80 font-bold text-xl ml-2">Hive Bridge</span>
+      </div>
       <div class="flex items-center rounded-lg p-2 mt-1 mx-1 bg-background/40 border">
         <Avatar class="w-8 h-8 mr-2">
-          <AvatarImage v-if="userStore.profileImage" :src="userStore.profileImage" />
+          <AvatarImage :src="userStore.profileImage ? userStore.profileImage : '/icon.svg'" />
           <AvatarFallback v-if="settingsStore.isLoaded && hasUser">{{ settingsStore.settings.account?.slice(0, 2) }}</AvatarFallback>
         </Avatar>
         <span class="font-bold max-w-[140px] truncate" v-if="settingsStore.isLoaded && hasUser">@{{ settingsStore.settings.account }}</span>
         <ThemeSwitch class="ml-auto w-5 h-5 mr-1" />
       </div>
+      <Button class="bg-background/40" variant="outline" @click="settingsStore.isLoaded && hasUser ? logout : walletStore.openWalletSelectModal()">
+        <img v-if="hasUser" :src="getWalletIcon(settingsStore.settings.wallet!)" class="h-6 w-6" />
+        <span class="font-bold">{{ settingsStore.isLoaded && hasUser ? 'Disconnect' : 'Connect' }}</span>
+      </Button>
     </SidebarHeader>
     <SidebarContent>
-      <SidebarGroup>
-        <SidebarGroupLabel class="text-foreground/60">Hive Bridge</SidebarGroupLabel>
+      <SidebarGroup class="pb-0" v-for="group in groups" :key="group.title">
+        <SidebarGroupLabel class="text-foreground/60">{{ group.title }}</SidebarGroupLabel>
         <SidebarGroupContent>
           <SidebarMenu>
-            <SidebarMenuItem v-for="item in items" :key="item.title">
-              <SidebarMenuButton asChild>
+            <SidebarMenuItem v-for="item in group.items" :key="item.title">
+              <SidebarMenuButton asChild :class="{ 'bg-primary/5': router.currentRoute.value.path === item.url }">
                 <RouterLink @click="isMobile && toggleSidebar()" :to="item.url">
                   <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsl(var(--foreground))" :d="item.icon"/></svg>
                   <span class="text-foreground/80">{{item.title}}</span>
@@ -80,10 +101,6 @@ const items = [
       </SidebarGroup>
     </SidebarContent>
     <SidebarFooter>
-      <Button class="bg-background/40" variant="outline" :disabled="!settingsStore.isLoaded || !hasUser" @click="logout">
-        <img v-if="hasUser" :src="getWalletIcon(settingsStore.settings.wallet!)" class="h-6 w-6" />
-        <span class="font-bold">Disconnect</span>
-      </Button>
     </SidebarFooter>
   </Sidebar>
 </template>
diff --git a/src/components/ui/sidebar/index.ts b/src/components/ui/sidebar/index.ts
index 5bd36ee..b0a2e64 100644
--- a/src/components/ui/sidebar/index.ts
+++ b/src/components/ui/sidebar/index.ts
@@ -40,7 +40,7 @@ export const sidebarMenuButtonVariants = cva(
   {
     variants: {
       variant: {
-        default: 'hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground',
+        default: 'hover:bg-primary/10 hover:text-sidebar-accent-foreground',
         outline:
           'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
       },
diff --git a/src/stores/wallet.store.ts b/src/stores/wallet.store.ts
index 1da5f95..1ca62d9 100644
--- a/src/stores/wallet.store.ts
+++ b/src/stores/wallet.store.ts
@@ -5,14 +5,44 @@ import { useMetamaskStore } from "./metamask.store";
 import { createKeychainWalletFor } from "@/utils/wallet/keychain";
 import { createPeakVaultWalletFor } from "@/utils/wallet/peakvault";
 
+let intervalId: NodeJS.Timeout | undefined;
+
 export const useWalletStore = defineStore('wallet', {
   state: () => ({
-    wallet: undefined as undefined | Wallet
+    _walletsStatus: {
+      metamask: false,
+      keychain: false,
+      peakvault: false
+    },
+    wallet: undefined as undefined | Wallet,
+    isWalletSelectModalOpen: false
   }),
   getters: {
     hasWallet: state => !!state.wallet,
+    walletsStatus: state => {
+      if (!intervalId) {
+        const metamaskStore = useMetamaskStore();
+
+        const checkForWallets = () => {
+          metamaskStore.connect().then(() => state._walletsStatus.metamask = true).catch(console.error);
+          state._walletsStatus.keychain = "hive_keychain" in window;
+          state._walletsStatus.peakvault = "peakvault" in window;
+        };
+
+        intervalId = setInterval(checkForWallets, 1000);
+        checkForWallets();
+      }
+
+      return state._walletsStatus;
+    }
   },
   actions: {
+    openWalletSelectModal() {
+      this.isWalletSelectModalOpen = true;
+    },
+    closeWalletSelectModal() {
+      this.isWalletSelectModalOpen = false;
+    },
     async createWalletFor(settings: Settings) {
       switch(settings.wallet) {
         case UsedWallet.METAMASK: {
-- 
GitLab