From 98bcebd3ae9058b7d1037b1557f0b6201085d291 Mon Sep 17 00:00:00 2001 From: mtyszczak <mateusz.tyszczak@gmail.com> Date: Wed, 19 Mar 2025 13:04:17 +0100 Subject: [PATCH] Minor fixes --- src/App.vue | 2 + src/components/ErrorDialog.vue | 45 ++++++++++++++ src/components/navigation/AppHeader.vue | 9 ++- src/components/ui/dialog/Dialog.vue | 14 +++++ src/components/ui/dialog/DialogClose.vue | 11 ++++ src/components/ui/dialog/DialogContent.vue | 50 ++++++++++++++++ .../ui/dialog/DialogDescription.vue | 24 ++++++++ src/components/ui/dialog/DialogFooter.vue | 19 ++++++ src/components/ui/dialog/DialogHeader.vue | 16 +++++ .../ui/dialog/DialogScrollContent.vue | 59 +++++++++++++++++++ src/components/ui/dialog/DialogTitle.vue | 29 +++++++++ src/components/ui/dialog/DialogTrigger.vue | 11 ++++ src/components/ui/dialog/index.ts | 9 +++ .../utilcards/ConfirmAccountUpdateCard.vue | 10 ++-- .../utilcards/ConfirmCreateAccountCard.vue | 16 ++--- src/components/utilcards/MemoEncryptCard.vue | 4 +- .../utilcards/SignTransactionCard.vue | 4 +- src/stores/error-dialog.store.ts | 24 ++++++++ src/utils/parse-error.ts | 14 ++++- 19 files changed, 347 insertions(+), 23 deletions(-) create mode 100644 src/components/ErrorDialog.vue create mode 100644 src/components/ui/dialog/Dialog.vue create mode 100644 src/components/ui/dialog/DialogClose.vue create mode 100644 src/components/ui/dialog/DialogContent.vue create mode 100644 src/components/ui/dialog/DialogDescription.vue create mode 100644 src/components/ui/dialog/DialogFooter.vue create mode 100644 src/components/ui/dialog/DialogHeader.vue create mode 100644 src/components/ui/dialog/DialogScrollContent.vue create mode 100644 src/components/ui/dialog/DialogTitle.vue create mode 100644 src/components/ui/dialog/DialogTrigger.vue create mode 100644 src/components/ui/dialog/index.ts create mode 100644 src/stores/error-dialog.store.ts diff --git a/src/App.vue b/src/App.vue index 6c11479..6bd5063 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,6 +8,7 @@ import { Toaster } from 'vue-sonner'; import { useUserStore } from '@/stores/user.store'; import { getWax } from '@/stores/wax.store'; import AppHeader from '@/components/navigation/AppHeader.vue'; +import ErrorDialog from './components/ErrorDialog.vue'; const WalletOnboarding = defineAsyncComponent(() => import('@/components/onboarding/index')); @@ -56,6 +57,7 @@ const complete = async(data: { account: string; wallet: UsedWallet }) => { </aside> </SidebarProvider> <Toaster theme="dark" closeButton richColors /> + <ErrorDialog /> </div> </div> </template> diff --git a/src/components/ErrorDialog.vue b/src/components/ErrorDialog.vue new file mode 100644 index 0000000..10eda67 --- /dev/null +++ b/src/components/ErrorDialog.vue @@ -0,0 +1,45 @@ +<script setup lang="ts"> +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Button } from "@/components/ui/button"; +import { useErrorDialogStore } from '@/stores/error-dialog.store'; +import { toRaw } from 'vue'; + +const errorStore = useErrorDialogStore(); + +const updateOpen = (value: boolean) => { + if (value === false) + errorStore.closeError(); +}; + +const logOriginator = () => { + console.error(toRaw(errorStore.originator)); +}; + +const createErrorText = () => `${errorStore.title} - ${errorStore.description}`; +</script> + +<template> + <Dialog :open="errorStore.hasError" class="max-w-[90vw]" @update:open="updateOpen"> + <DialogContent class="max-w-[95vw] sm:max-w-[600px] md:max-w-[700px] lg:max-w-[800px] grid-rows-[auto_minmax(0,1fr)_auto] p-0 max-h-[90dvh]"> + <DialogHeader class="p-6 pb-0"> + <DialogTitle>{{ errorStore.title }}</DialogTitle> + <DialogDescription> + Read the error message below to understand what went wrong. + </DialogDescription> + </DialogHeader> + <div class="py-4 overflow-y-auto px-6"> + <code> + <pre class="break-all whitespace-pre-wrap">{{ errorStore.description }}</pre> + </code> + </div> + <DialogFooter class="p-6 pt-0"> + <Button variant="secondary" @click="logOriginator"> + Log error to console + </Button> + <Button :copy="createErrorText"> + Copy error message + </Button> + </DialogFooter> + </DialogContent> + </Dialog> +</template> \ No newline at end of file diff --git a/src/components/navigation/AppHeader.vue b/src/components/navigation/AppHeader.vue index fcb24b2..1961a5c 100644 --- a/src/components/navigation/AppHeader.vue +++ b/src/components/navigation/AppHeader.vue @@ -11,15 +11,14 @@ import { mdiLogout } from '@mdi/js'; const settingsStore = useSettingsStore(); const hasUser = computed(() => settingsStore.settings.account !== undefined); +const walletStore = useWalletStore(); +const userStore = useUserStore(); const logout = () => { settingsStore.resetSettings(); - window.location.reload(); + walletStore.resetWallet(); + userStore.resetSettings(); }; - -const walletStore = useWalletStore(); - -const userStore = useUserStore(); </script> <template> diff --git a/src/components/ui/dialog/Dialog.vue b/src/components/ui/dialog/Dialog.vue new file mode 100644 index 0000000..9fc9c7d --- /dev/null +++ b/src/components/ui/dialog/Dialog.vue @@ -0,0 +1,14 @@ +<script setup lang="ts"> +import { DialogRoot, type DialogRootEmits, type DialogRootProps, useForwardPropsEmits } from 'reka-ui' + +const props = defineProps<DialogRootProps>() +const emits = defineEmits<DialogRootEmits>() + +const forwarded = useForwardPropsEmits(props, emits) +</script> + +<template> + <DialogRoot v-bind="forwarded"> + <slot /> + </DialogRoot> +</template> diff --git a/src/components/ui/dialog/DialogClose.vue b/src/components/ui/dialog/DialogClose.vue new file mode 100644 index 0000000..ba036b5 --- /dev/null +++ b/src/components/ui/dialog/DialogClose.vue @@ -0,0 +1,11 @@ +<script setup lang="ts"> +import { DialogClose, type DialogCloseProps } from 'reka-ui' + +const props = defineProps<DialogCloseProps>() +</script> + +<template> + <DialogClose v-bind="props"> + <slot /> + </DialogClose> +</template> diff --git a/src/components/ui/dialog/DialogContent.vue b/src/components/ui/dialog/DialogContent.vue new file mode 100644 index 0000000..d84f271 --- /dev/null +++ b/src/components/ui/dialog/DialogContent.vue @@ -0,0 +1,50 @@ +<script setup lang="ts"> +import { cn } from '@/lib/utils' +import { X } from 'lucide-vue-next' +import { + DialogClose, + DialogContent, + type DialogContentEmits, + type DialogContentProps, + DialogOverlay, + DialogPortal, + useForwardPropsEmits, +} from 'reka-ui' +import { computed, type HTMLAttributes } from 'vue' + +const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>() +const emits = defineEmits<DialogContentEmits>() + +const delegatedProps = computed(() => { + const { class: _, ...delegated } = props + + return delegated +}) + +const forwarded = useForwardPropsEmits(delegatedProps, emits) +</script> + +<template> + <DialogPortal> + <DialogOverlay + class="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" + /> + <DialogContent + v-bind="forwarded" + :class=" + cn( + 'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', + props.class, + )" + > + <slot /> + + <DialogClose + class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground" + > + <X class="w-4 h-4" /> + <span class="sr-only">Close</span> + </DialogClose> + </DialogContent> + </DialogPortal> +</template> diff --git a/src/components/ui/dialog/DialogDescription.vue b/src/components/ui/dialog/DialogDescription.vue new file mode 100644 index 0000000..5afbab0 --- /dev/null +++ b/src/components/ui/dialog/DialogDescription.vue @@ -0,0 +1,24 @@ +<script setup lang="ts"> +import { cn } from '@/lib/utils' +import { DialogDescription, type DialogDescriptionProps, useForwardProps } from 'reka-ui' +import { computed, type HTMLAttributes } from 'vue' + +const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>() + +const delegatedProps = computed(() => { + const { class: _, ...delegated } = props + + return delegated +}) + +const forwardedProps = useForwardProps(delegatedProps) +</script> + +<template> + <DialogDescription + v-bind="forwardedProps" + :class="cn('text-sm text-muted-foreground', props.class)" + > + <slot /> + </DialogDescription> +</template> diff --git a/src/components/ui/dialog/DialogFooter.vue b/src/components/ui/dialog/DialogFooter.vue new file mode 100644 index 0000000..ac2d0c1 --- /dev/null +++ b/src/components/ui/dialog/DialogFooter.vue @@ -0,0 +1,19 @@ +<script setup lang="ts"> +import type { HTMLAttributes } from 'vue' +import { cn } from '@/lib/utils' + +const props = defineProps<{ class?: HTMLAttributes['class'] }>() +</script> + +<template> + <div + :class=" + cn( + 'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2', + props.class, + ) + " + > + <slot /> + </div> +</template> diff --git a/src/components/ui/dialog/DialogHeader.vue b/src/components/ui/dialog/DialogHeader.vue new file mode 100644 index 0000000..b2c9085 --- /dev/null +++ b/src/components/ui/dialog/DialogHeader.vue @@ -0,0 +1,16 @@ +<script setup lang="ts"> +import type { HTMLAttributes } from 'vue' +import { cn } from '@/lib/utils' + +const props = defineProps<{ + class?: HTMLAttributes['class'] +}>() +</script> + +<template> + <div + :class="cn('flex flex-col gap-y-1.5 text-center sm:text-left', props.class)" + > + <slot /> + </div> +</template> diff --git a/src/components/ui/dialog/DialogScrollContent.vue b/src/components/ui/dialog/DialogScrollContent.vue new file mode 100644 index 0000000..ee6b5e2 --- /dev/null +++ b/src/components/ui/dialog/DialogScrollContent.vue @@ -0,0 +1,59 @@ +<script setup lang="ts"> +import { cn } from '@/lib/utils' +import { X } from 'lucide-vue-next' +import { + DialogClose, + DialogContent, + type DialogContentEmits, + type DialogContentProps, + DialogOverlay, + DialogPortal, + useForwardPropsEmits, +} from 'reka-ui' +import { computed, type HTMLAttributes } from 'vue' + +const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>() +const emits = defineEmits<DialogContentEmits>() + +const delegatedProps = computed(() => { + const { class: _, ...delegated } = props + + return delegated +}) + +const forwarded = useForwardPropsEmits(delegatedProps, emits) +</script> + +<template> + <DialogPortal> + <DialogOverlay + class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" + > + <DialogContent + :class=" + cn( + 'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full', + props.class, + ) + " + v-bind="forwarded" + @pointer-down-outside="(event) => { + const originalEvent = event.detail.originalEvent; + const target = originalEvent.target as HTMLElement; + if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) { + event.preventDefault(); + } + }" + > + <slot /> + + <DialogClose + class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary" + > + <X class="w-4 h-4" /> + <span class="sr-only">Close</span> + </DialogClose> + </DialogContent> + </DialogOverlay> + </DialogPortal> +</template> diff --git a/src/components/ui/dialog/DialogTitle.vue b/src/components/ui/dialog/DialogTitle.vue new file mode 100644 index 0000000..30cbb36 --- /dev/null +++ b/src/components/ui/dialog/DialogTitle.vue @@ -0,0 +1,29 @@ +<script setup lang="ts"> +import { cn } from '@/lib/utils' +import { DialogTitle, type DialogTitleProps, useForwardProps } from 'reka-ui' +import { computed, type HTMLAttributes } from 'vue' + +const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>() + +const delegatedProps = computed(() => { + const { class: _, ...delegated } = props + + return delegated +}) + +const forwardedProps = useForwardProps(delegatedProps) +</script> + +<template> + <DialogTitle + v-bind="forwardedProps" + :class=" + cn( + 'text-lg font-semibold leading-none tracking-tight', + props.class, + ) + " + > + <slot /> + </DialogTitle> +</template> diff --git a/src/components/ui/dialog/DialogTrigger.vue b/src/components/ui/dialog/DialogTrigger.vue new file mode 100644 index 0000000..2984f37 --- /dev/null +++ b/src/components/ui/dialog/DialogTrigger.vue @@ -0,0 +1,11 @@ +<script setup lang="ts"> +import { DialogTrigger, type DialogTriggerProps } from 'reka-ui' + +const props = defineProps<DialogTriggerProps>() +</script> + +<template> + <DialogTrigger v-bind="props"> + <slot /> + </DialogTrigger> +</template> diff --git a/src/components/ui/dialog/index.ts b/src/components/ui/dialog/index.ts new file mode 100644 index 0000000..ca8cfea --- /dev/null +++ b/src/components/ui/dialog/index.ts @@ -0,0 +1,9 @@ +export { default as Dialog } from './Dialog.vue' +export { default as DialogClose } from './DialogClose.vue' +export { default as DialogContent } from './DialogContent.vue' +export { default as DialogDescription } from './DialogDescription.vue' +export { default as DialogFooter } from './DialogFooter.vue' +export { default as DialogHeader } from './DialogHeader.vue' +export { default as DialogScrollContent } from './DialogScrollContent.vue' +export { default as DialogTitle } from './DialogTitle.vue' +export { default as DialogTrigger } from './DialogTrigger.vue' diff --git a/src/components/utilcards/ConfirmAccountUpdateCard.vue b/src/components/utilcards/ConfirmAccountUpdateCard.vue index 33bf986..193e353 100644 --- a/src/components/utilcards/ConfirmAccountUpdateCard.vue +++ b/src/components/utilcards/ConfirmAccountUpdateCard.vue @@ -78,23 +78,23 @@ const updateAuthority = async() => { </CardHeader> <CardContent> <div class="my-4 space-y-2"> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="updateAuthority_creator">Account To Update</Label> <Input id="updateAuthority_creator" v-model="creator" class="my-2" /> </div> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="updateAuthority_memoKey">New Memo Key</Label> <Input id="updateAuthority_memoKey" placeholder="Nothing to update" v-model="memoKey" class="my-2" /> </div> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="updateAuthority_postingKey">Add Posting Key</Label> <Input id="updateAuthority_postingKey" placeholder="Nothing to add" v-model="postingKey" class="my-2" /> </div> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="updateAuthority_activeKey">Add Active Key</Label> <Input id="updateAuthority_activeKey" placeholder="Nothing to add" v-model="activeKey" class="my-2" /> </div> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="updateAuthority_ownerKey">Add Owner Key</Label> <Input id="updateAuthority_ownerKey" placeholder="Nothing to add" v-model="ownerKey" class="my-2" /> </div> diff --git a/src/components/utilcards/ConfirmCreateAccountCard.vue b/src/components/utilcards/ConfirmCreateAccountCard.vue index f6e2913..bc31d83 100644 --- a/src/components/utilcards/ConfirmCreateAccountCard.vue +++ b/src/components/utilcards/ConfirmCreateAccountCard.vue @@ -130,31 +130,31 @@ const createAccount = async() => { </CardHeader> <CardContent> <div class="my-4 space-y-4"> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="createAccount_creator">Creator Account Name</Label> <Input id="createAccount_creator" v-model="creator" class="my-2" /> </div> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="createAccount_accountName">New Account Name</Label> <Input id="createAccount_accountName" v-model="accountName" class="my-2" /> </div> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="createAccount_memoKey">Memo Key</Label> <Input id="createAccount_memoKey" v-model="memoKey" class="my-2" /> </div> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="createAccount_postingKey">Posting Key</Label> <Input id="createAccount_postingKey" v-model="postingKey" class="my-2" /> </div> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="createAccount_activeKey">Active Key</Label> <Input id="createAccount_activeKey" v-model="activeKey" class="my-2" /> </div> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="createAccount_ownerKey">Owner Key</Label> <Input id="createAccount_ownerKey" v-model="ownerKey" class="my-2" /> </div> - <div class="grid w-full max-w-sm items-center"> + <div class="grid w-full items-center"> <Label for="createAccount_postingMetadata">Posting Metadata</Label> <Textarea id="createAccount_postingMetadata" v-model="postingMetadata" class="my-2" /> </div> @@ -164,7 +164,7 @@ const createAccount = async() => { <span class="text-sm">Enable Delegation</span> </label> </div> - <div class="grid w-full max-w-sm items-center" v-if="enableDelegation"> + <div class="grid w-full items-center" v-if="enableDelegation"> <Label for="createAccount_delegationAmount">Delegation amount</Label> <div class="flex items-center space-x-2"> <Input id="createAccount_delegationAmount" type="number" v-model="delegationAmount" min="0" class="my-2" /> diff --git a/src/components/utilcards/MemoEncryptCard.vue b/src/components/utilcards/MemoEncryptCard.vue index 2b7b6c8..d1a3955 100644 --- a/src/components/utilcards/MemoEncryptCard.vue +++ b/src/components/utilcards/MemoEncryptCard.vue @@ -101,13 +101,13 @@ const encryptOrDecrypt = async () => { </TabsTrigger> </TabsList> </Tabs> - <Textarea v-model="inputData" placeholder="Input" class="my-4"/> + <Textarea v-model="inputData" placeholder="Input" class="my-4" height="200px"/> <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="(!encryptForKey && isEncrypt)" @click="encryptOrDecrypt">{{ isEncrypt ? "Encrypt" : "Decrypt" }}</Button> - <Textarea v-model="outputData" placeholder="Output" copy-enabled class="my-4" disabled/> + <Textarea v-model="outputData" placeholder="Output" copy-enabled class="my-4" height="200px" disabled/> </CardContent> </Card> </template> \ No newline at end of file diff --git a/src/components/utilcards/SignTransactionCard.vue b/src/components/utilcards/SignTransactionCard.vue index 638d864..e82af94 100644 --- a/src/components/utilcards/SignTransactionCard.vue +++ b/src/components/utilcards/SignTransactionCard.vue @@ -99,11 +99,11 @@ onMounted(() => { <CardDescription class="mr-8">Use this module to sign the provided transaction</CardDescription> </CardHeader> <CardContent> - <Textarea v-model="inputData" placeholder="Transaction in API JSON form" class="my-4"/> + <Textarea v-model="inputData" placeholder="Transaction in API JSON form" class="my-4" height="200px"/> <div class="my-4 space-x-4"> <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/> + <Textarea v-model="outputData" placeholder="Signed transaction" copy-enabled class="my-4" height="200px" disabled/> <div class="my-4 space-x-4"> <Button :disabled="!outputData || isLoading" :loading="isBroadcasting" @click="broadcast">Broadcast signed transaction</Button> </div> diff --git a/src/stores/error-dialog.store.ts b/src/stores/error-dialog.store.ts new file mode 100644 index 0000000..35831ab --- /dev/null +++ b/src/stores/error-dialog.store.ts @@ -0,0 +1,24 @@ +import { defineStore } from "pinia" + +export const useErrorDialogStore = defineStore('errorDialog', { + state: () => ({ + title: undefined as undefined | string, + originator: undefined as unknown, + description: undefined as undefined | string + }), + getters: { + hasError: ctx => ctx.title !== undefined + }, + actions: { + closeError() { + this.title = undefined; + this.originator = undefined; + this.description = undefined; + }, + setError(title: string, originator: unknown, description?: string) { + this.title = title; + this.originator = originator; + this.description = description; + } + } +}) diff --git a/src/utils/parse-error.ts b/src/utils/parse-error.ts index 18b2ef1..3bde273 100644 --- a/src/utils/parse-error.ts +++ b/src/utils/parse-error.ts @@ -1,4 +1,5 @@ import { toast } from "vue-sonner"; +import { useErrorDialogStore } from '@/stores/error-dialog.store'; export const toastError = (title: string, error: unknown) => { let description: string; @@ -20,5 +21,16 @@ export const toastError = (title: string, error: unknown) => { } else description = String(error); - toast.error(title, { description }); + toast.error(title, { + description: description.length > 100 ? description.slice(0, 100) + "..." : description, + duration: 8_000, + action: { + label: "Details", + onClick: () => { + const errorStore = useErrorDialogStore(); + + errorStore.setError(title, error, description); + } + } + }); }; -- GitLab