From 9273cafeb5f9f3ff51433b724c47b4fb1581f4b3 Mon Sep 17 00:00:00 2001
From: mtyszczak <mateusz.tyszczak@gmail.com>
Date: Tue, 18 Mar 2025 15:43:34 +0100
Subject: [PATCH] Add login on action

---
 src/components/ui/tabs/Tabs.vue               | 15 ++++++++++
 src/components/ui/tabs/TabsContent.vue        | 22 ++++++++++++++
 src/components/ui/tabs/TabsList.vue           | 25 ++++++++++++++++
 src/components/ui/tabs/TabsTrigger.vue        | 29 +++++++++++++++++++
 src/components/ui/tabs/index.ts               |  4 +++
 .../utilcards/ConfirmAccountUpdateCard.vue    |  6 +++-
 .../utilcards/ConfirmCreateAccountCard.vue    |  8 +++--
 src/components/utilcards/MemoEncryptCard.vue  | 25 +++++++++++-----
 .../utilcards/SignTransactionCard.vue         |  5 +++-
 src/stores/wallet.store.ts                    | 20 +++++++++++--
 10 files changed, 145 insertions(+), 14 deletions(-)
 create mode 100644 src/components/ui/tabs/Tabs.vue
 create mode 100644 src/components/ui/tabs/TabsContent.vue
 create mode 100644 src/components/ui/tabs/TabsList.vue
 create mode 100644 src/components/ui/tabs/TabsTrigger.vue
 create mode 100644 src/components/ui/tabs/index.ts

diff --git a/src/components/ui/tabs/Tabs.vue b/src/components/ui/tabs/Tabs.vue
new file mode 100644
index 0000000..15aeca8
--- /dev/null
+++ b/src/components/ui/tabs/Tabs.vue
@@ -0,0 +1,15 @@
+<script setup lang="ts">
+import type { TabsRootEmits, TabsRootProps } from 'reka-ui'
+import { TabsRoot, useForwardPropsEmits } from 'reka-ui'
+
+const props = defineProps<TabsRootProps>()
+const emits = defineEmits<TabsRootEmits>()
+
+const forwarded = useForwardPropsEmits(props, emits)
+</script>
+
+<template>
+  <TabsRoot v-bind="forwarded">
+    <slot />
+  </TabsRoot>
+</template>
diff --git a/src/components/ui/tabs/TabsContent.vue b/src/components/ui/tabs/TabsContent.vue
new file mode 100644
index 0000000..662638d
--- /dev/null
+++ b/src/components/ui/tabs/TabsContent.vue
@@ -0,0 +1,22 @@
+<script setup lang="ts">
+import { cn } from '@/lib/utils'
+import { TabsContent, type TabsContentProps } from 'reka-ui'
+import { computed, type HTMLAttributes } from 'vue'
+
+const props = defineProps<TabsContentProps & { class?: HTMLAttributes['class'] }>()
+
+const delegatedProps = computed(() => {
+  const { class: _, ...delegated } = props
+
+  return delegated
+})
+</script>
+
+<template>
+  <TabsContent
+    :class="cn('mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', props.class)"
+    v-bind="delegatedProps"
+  >
+    <slot />
+  </TabsContent>
+</template>
diff --git a/src/components/ui/tabs/TabsList.vue b/src/components/ui/tabs/TabsList.vue
new file mode 100644
index 0000000..d5c79ef
--- /dev/null
+++ b/src/components/ui/tabs/TabsList.vue
@@ -0,0 +1,25 @@
+<script setup lang="ts">
+import { cn } from '@/lib/utils'
+import { TabsList, type TabsListProps } from 'reka-ui'
+import { computed, type HTMLAttributes } from 'vue'
+
+const props = defineProps<TabsListProps & { class?: HTMLAttributes['class'] }>()
+
+const delegatedProps = computed(() => {
+  const { class: _, ...delegated } = props
+
+  return delegated
+})
+</script>
+
+<template>
+  <TabsList
+    v-bind="delegatedProps"
+    :class="cn(
+      'inline-flex items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground',
+      props.class,
+    )"
+  >
+    <slot />
+  </TabsList>
+</template>
diff --git a/src/components/ui/tabs/TabsTrigger.vue b/src/components/ui/tabs/TabsTrigger.vue
new file mode 100644
index 0000000..bb8b445
--- /dev/null
+++ b/src/components/ui/tabs/TabsTrigger.vue
@@ -0,0 +1,29 @@
+<script setup lang="ts">
+import { cn } from '@/lib/utils'
+import { TabsTrigger, type TabsTriggerProps, useForwardProps } from 'reka-ui'
+import { computed, type HTMLAttributes } from 'vue'
+
+const props = defineProps<TabsTriggerProps & { class?: HTMLAttributes['class'] }>()
+
+const delegatedProps = computed(() => {
+  const { class: _, ...delegated } = props
+
+  return delegated
+})
+
+const forwardedProps = useForwardProps(delegatedProps)
+</script>
+
+<template>
+  <TabsTrigger
+    v-bind="forwardedProps"
+    :class="cn(
+      'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
+      props.class,
+    )"
+  >
+    <span class="truncate">
+      <slot />
+    </span>
+  </TabsTrigger>
+</template>
diff --git a/src/components/ui/tabs/index.ts b/src/components/ui/tabs/index.ts
new file mode 100644
index 0000000..a5e58dc
--- /dev/null
+++ b/src/components/ui/tabs/index.ts
@@ -0,0 +1,4 @@
+export { default as Tabs } from './Tabs.vue'
+export { default as TabsContent } from './TabsContent.vue'
+export { default as TabsList } from './TabsList.vue'
+export { default as TabsTrigger } from './TabsTrigger.vue'
diff --git a/src/components/utilcards/ConfirmAccountUpdateCard.vue b/src/components/utilcards/ConfirmAccountUpdateCard.vue
index 8e0273d..85d7e10 100644
--- a/src/components/utilcards/ConfirmAccountUpdateCard.vue
+++ b/src/components/utilcards/ConfirmAccountUpdateCard.vue
@@ -5,7 +5,7 @@ import { Button } from '@/components/ui/button';
 import { Input } from '@/components/ui/input'
 import { Label } from '@/components/ui/label'
 import { useSettingsStore } from '@/stores/settings.store';
-import { onMounted, ref } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 import { useRouter } from 'vue-router';
 import { getWax } from '@/stores/wax.store';
 import { useWalletStore } from '@/stores/wallet.store';
@@ -21,6 +21,7 @@ const ownerKey = ref<string>('');
 
 const router = useRouter();
 const wallet = useWalletStore();
+const hasWallet = computed(() => wallet.hasWallet);
 
 onMounted(() => {
   creator.value = router.currentRoute.value.query.acc as string ?? (settings.account ? `@${settings.account}` : '');
@@ -37,6 +38,9 @@ const updateAuthority = async() => {
   try {
     isLoading.value = true;
 
+    if (!hasWallet.value)
+      await wallet.openWalletSelectModal();
+
     if (!memoKey.value && !postingKey.value && !activeKey.value && !ownerKey.value)
       throw new Error("Nothing to update");
 
diff --git a/src/components/utilcards/ConfirmCreateAccountCard.vue b/src/components/utilcards/ConfirmCreateAccountCard.vue
index 038d904..5260c66 100644
--- a/src/components/utilcards/ConfirmCreateAccountCard.vue
+++ b/src/components/utilcards/ConfirmCreateAccountCard.vue
@@ -8,7 +8,7 @@ import { Checkbox } from '@/components/ui/checkbox'
 import { Label } from '@/components/ui/label'
 import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
 import { useSettingsStore } from '@/stores/settings.store';
-import { onMounted, ref } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 import { useRouter } from 'vue-router';
 import { getWax } from '@/stores/wax.store';
 import { useWalletStore } from '@/stores/wallet.store';
@@ -29,6 +29,7 @@ const createAccountType = ref<"default" | "claimed">("default");
 
 const router = useRouter();
 const wallet = useWalletStore();
+const hasWallet = computed(() => wallet.hasWallet);
 
 onMounted(() => {
   creator.value = settings.account ? `@${settings.account}` : '';
@@ -46,6 +47,9 @@ const createAccount = async() => {
   try {
     isLoading.value = true;
 
+    if (!hasWallet.value)
+      await wallet.openWalletSelectModal();
+
     const wax = await getWax();
     const tx = await wax.createTransaction();
     const { median_props: { account_creation_fee } } = await wax.api.database_api.get_witness_schedule({});
@@ -128,7 +132,7 @@ const createAccount = async() => {
       <div class="my-4 space-y-4">
         <div class="grid w-full max-w-sm items-center">
           <Label for="createAccount_creator">Creator Account Name</Label>
-          <Input id="createAccount_creator" v-model="creator" class="my-2" disabled />
+          <Input id="createAccount_creator" v-model="creator" class="my-2" />
         </div>
         <div class="grid w-full max-w-sm items-center">
           <Label for="createAccount_accountName">New Account Name</Label>
diff --git a/src/components/utilcards/MemoEncryptCard.vue b/src/components/utilcards/MemoEncryptCard.vue
index 8c03ca0..2b7b6c8 100644
--- a/src/components/utilcards/MemoEncryptCard.vue
+++ b/src/components/utilcards/MemoEncryptCard.vue
@@ -3,9 +3,9 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
 import { mdiMessageLockOutline } from '@mdi/js';
 import { ref, computed } from 'vue';
 import { Textarea } from '@/components/ui/textarea';
+import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
 import { Button } from '@/components/ui/button';
 import { Input } from '@/components/ui/input';
-import { Switch } from '@/components/ui/switch';
 import { useWalletStore } from '@/stores/wallet.store';
 import { getWax } from '@/stores/wax.store';
 import { useSettingsStore } from '@/stores/settings.store';
@@ -41,6 +41,9 @@ const useMyMemoKey = async () => {
   try {
     isLoading.value = true;
 
+    if (!hasWallet.value)
+      await walletStore.openWalletSelectModal();
+
     const key = await getMemoKeyForUser(settingsStore.account!);
     if (key)
       encryptForKey.value = key;
@@ -53,6 +56,9 @@ const encryptOrDecrypt = async () => {
   try {
     isLoading.value = true;
 
+    if (!hasWallet.value)
+      await walletStore.openWalletSelectModal();
+
     if (isEncrypt.value) {
       let publicKey: string;
       let accountOrKey = encryptForKey.value;
@@ -85,17 +91,22 @@ const encryptOrDecrypt = async () => {
       <CardDescription class="mr-8">Use this module to encrypt / decrypt given message using your memo key for any purpose</CardDescription>
     </CardHeader>
     <CardContent>
-      <div class="my-4 space-x-4 flex">
-        <span>Decrypt</span>
-        <Switch v-model="isEncrypt" />
-        <span>Encrypt</span>
-      </div>
+      <Tabs default-value="decrypt" @update:model-value="value => isEncrypt = value === 'encrypt'">
+        <TabsList>
+          <TabsTrigger value="decrypt">
+            Decrypt
+          </TabsTrigger>
+          <TabsTrigger value="encrypt">
+            Encrypt
+          </TabsTrigger>
+        </TabsList>
+      </Tabs>
       <Textarea v-model="inputData" placeholder="Input" class="my-4"/>
       <Input v-model="encryptForKey" v-if="isEncrypt" placeholder="Receiver account or public key" class="mt-4"/>
       <div class="flex mb-4 underline text-sm" v-if="isEncrypt">
         <a @click="useMyMemoKey" class="ml-auto mr-1 cursor-pointer" style="color: hsla(var(--foreground) / 70%)">Use my memo key</a>
       </div>
-      <Button :loading="isLoading" :disabled="!hasWallet || (!encryptForKey && isEncrypt)" @click="encryptOrDecrypt">{{ isEncrypt ? "Encrypt" : "Decrypt" }}</Button>
+      <Button :loading="isLoading" :disabled="(!encryptForKey && isEncrypt)" @click="encryptOrDecrypt">{{ isEncrypt ? "Encrypt" : "Decrypt" }}</Button>
       <Textarea v-model="outputData" placeholder="Output" copy-enabled class="my-4" disabled/>
     </CardContent>
   </Card>
diff --git a/src/components/utilcards/SignTransactionCard.vue b/src/components/utilcards/SignTransactionCard.vue
index 30158ff..638d864 100644
--- a/src/components/utilcards/SignTransactionCard.vue
+++ b/src/components/utilcards/SignTransactionCard.vue
@@ -27,6 +27,9 @@ const sign = async () => {
   try {
     isLoading.value = true;
 
+    if (!hasWallet.value)
+      await walletStore.openWalletSelectModal();
+
     try {
       JSON.parse(inputData.value);
     } catch (error) {
@@ -98,7 +101,7 @@ onMounted(() => {
     <CardContent>
       <Textarea v-model="inputData" placeholder="Transaction in API JSON form" class="my-4"/>
       <div class="my-4 space-x-4">
-        <Button :disabled="!inputData || !hasWallet || isBroadcasting" :loading="isLoading" @click="sign">Sign transaction</Button>
+        <Button :disabled="!inputData || isBroadcasting" :loading="isLoading" @click="sign">Sign transaction</Button>
       </div>
       <Textarea v-model="outputData" placeholder="Signed transaction" copy-enabled class="my-4" disabled/>
       <div class="my-4 space-x-4">
diff --git a/src/stores/wallet.store.ts b/src/stores/wallet.store.ts
index 1ca62d9..7fc8079 100644
--- a/src/stores/wallet.store.ts
+++ b/src/stores/wallet.store.ts
@@ -5,7 +5,9 @@ import { useMetamaskStore } from "./metamask.store";
 import { createKeychainWalletFor } from "@/utils/wallet/keychain";
 import { createPeakVaultWalletFor } from "@/utils/wallet/peakvault";
 
-let intervalId: NodeJS.Timeout | undefined;
+let walletRetrievalIntervalId: NodeJS.Timeout | undefined;
+
+const intervalIds = new Set<NodeJS.Timeout>();
 
 export const useWalletStore = defineStore('wallet', {
   state: () => ({
@@ -20,7 +22,7 @@ export const useWalletStore = defineStore('wallet', {
   getters: {
     hasWallet: state => !!state.wallet,
     walletsStatus: state => {
-      if (!intervalId) {
+      if (!walletRetrievalIntervalId) {
         const metamaskStore = useMetamaskStore();
 
         const checkForWallets = () => {
@@ -29,7 +31,7 @@ export const useWalletStore = defineStore('wallet', {
           state._walletsStatus.peakvault = "peakvault" in window;
         };
 
-        intervalId = setInterval(checkForWallets, 1000);
+        walletRetrievalIntervalId = setInterval(checkForWallets, 1000);
         checkForWallets();
       }
 
@@ -39,6 +41,18 @@ export const useWalletStore = defineStore('wallet', {
   actions: {
     openWalletSelectModal() {
       this.isWalletSelectModalOpen = true;
+
+      // Allow functionality of waiting for wallet to be added / selected
+      return new Promise<void>(resolve => {
+        let intervalId: NodeJS.Timeout;
+        intervalIds.add(intervalId = setInterval(() => {
+          if (this.wallet && !this.isWalletSelectModalOpen) {
+            clearInterval(intervalId);
+            intervalIds.delete(intervalId);
+            resolve();
+          }
+        }, 1000));
+      });
     },
     closeWalletSelectModal() {
       this.isWalletSelectModalOpen = false;
-- 
GitLab