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