Skip to content
Snippets Groups Projects
Unverified Commit 39c3e36f authored by Mateusz Tyszczak's avatar Mateusz Tyszczak :scroll:
Browse files

Add loading to button

parent 432671d1
No related branches found
No related tags found
No related merge requests found
Pipeline #117965 passed
...@@ -6,18 +6,16 @@ import AppSidebar from '@/components/sidebar'; ...@@ -6,18 +6,16 @@ import AppSidebar from '@/components/sidebar';
import { SidebarProvider } from '@/components/ui/sidebar'; import { SidebarProvider } from '@/components/ui/sidebar';
import ToggleSidebar from './components/sidebar/ToggleSidebar.vue'; import ToggleSidebar from './components/sidebar/ToggleSidebar.vue';
import { Toaster } from 'vue-sonner'; import { Toaster } from 'vue-sonner';
import { useUserStore } from './stores/user.store';
import { getWax } from './stores/wax.store';
const WalletOnboarding = defineAsyncComponent(() => import('@/components/onboarding/index')); const WalletOnboarding = defineAsyncComponent(() => import('@/components/onboarding/index'));
const hasUser = ref(true); const hasUser = ref(true);
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const walletStore = useWalletStore(); const walletStore = useWalletStore();
onMounted(() => { const userStore = useUserStore();
settingsStore.loadSettings(); onMounted(async() => {
hasUser.value = settingsStore.settings.account !== undefined;
if (hasUser.value)
void walletStore.createWalletFor(settingsStore.settings);
if (window.matchMedia) { if (window.matchMedia) {
const media = window.matchMedia('(prefers-color-scheme: dark)'); const media = window.matchMedia('(prefers-color-scheme: dark)');
if (media.matches) if (media.matches)
...@@ -26,8 +24,17 @@ onMounted(() => { ...@@ -26,8 +24,17 @@ onMounted(() => {
document.documentElement.classList[event.matches ? 'add' : 'remove']('dark'); document.documentElement.classList[event.matches ? 'add' : 'remove']('dark');
}); });
} }
settingsStore.loadSettings();
hasUser.value = settingsStore.settings.account !== undefined;
if (hasUser.value) {
void walletStore.createWalletFor(settingsStore.settings);
const wax = await getWax();
const { accounts: [ account ] } = await wax.api.database_api.find_accounts({ accounts: [ settingsStore.settings.account! ], delayed_votes_active: false });
void userStore.setUserData(account);
}
}); });
const complete = (data: { account: string; wallet: UsedWallet }) => { const complete = async(data: { account: string; wallet: UsedWallet }) => {
hasUser.value = true; hasUser.value = true;
const settings = { const settings = {
account: data.account, account: data.account,
...@@ -35,6 +42,9 @@ const complete = (data: { account: string; wallet: UsedWallet }) => { ...@@ -35,6 +42,9 @@ const complete = (data: { account: string; wallet: UsedWallet }) => {
}; };
settingsStore.setSettings(settings); settingsStore.setSettings(settings);
void walletStore.createWalletFor(settings); void walletStore.createWalletFor(settings);
const wax = await getWax();
const { accounts: [ account ] } = await wax.api.database_api.find_accounts({ accounts: [ settingsStore.settings.account! ], delayed_votes_active: false });
void userStore.setUserData(account);
}; };
</script> </script>
......
...@@ -15,7 +15,7 @@ import { Separator } from '@/components/ui/separator'; ...@@ -15,7 +15,7 @@ import { Separator } from '@/components/ui/separator';
import PublicKey from '@/components/hive/PublicKey.vue'; import PublicKey from '@/components/hive/PublicKey.vue';
import { Checkbox } from '@/components/ui/checkbox' import { Checkbox } from '@/components/ui/checkbox'
import { getWax } from '@/stores/wax.store'; import { getWax } from '@/stores/wax.store';
import { AccountAuthorityUpdateOperation, type TRole } from "@hiveio/wax/vite"; import type { TRole } from "@hiveio/wax/vite";
const emit = defineEmits(["setaccount", "close"]); const emit = defineEmits(["setaccount", "close"]);
...@@ -135,6 +135,7 @@ const generateAccountUpdateTransaction = async(): Promise<string> => { ...@@ -135,6 +135,7 @@ const generateAccountUpdateTransaction = async(): Promise<string> => {
const wax = await getWax(); const wax = await getWax();
const tx = await wax.createTransaction(); const tx = await wax.createTransaction();
const accountName = updateAccountNameOperation.value!.startsWith('@') ? updateAccountNameOperation.value!.slice(1) : updateAccountNameOperation.value!; const accountName = updateAccountNameOperation.value!.startsWith('@') ? updateAccountNameOperation.value!.slice(1) : updateAccountNameOperation.value!;
const { AccountAuthorityUpdateOperation } = await import("@hiveio/wax/vite");
const op = await AccountAuthorityUpdateOperation.createFor(wax, accountName); const op = await AccountAuthorityUpdateOperation.createFor(wax, accountName);
for(const key in updateAuthType) { for(const key in updateAuthType) {
if (updateAuthType[key as TRole]) if (updateAuthType[key as TRole])
......
...@@ -6,6 +6,7 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' ...@@ -6,6 +6,7 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { useSettingsStore, getWalletIcon } from '@/stores/settings.store'; import { useSettingsStore, getWalletIcon } from '@/stores/settings.store';
import { computed } from 'vue'; import { computed } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useUserStore } from "@/stores/user.store";
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const hasUser = computed(() => settingsStore.settings.account !== undefined); const hasUser = computed(() => settingsStore.settings.account !== undefined);
...@@ -17,6 +18,8 @@ const logout = () => { ...@@ -17,6 +18,8 @@ const logout = () => {
const { toggleSidebar, isMobile } = useSidebar(); const { toggleSidebar, isMobile } = useSidebar();
const userStore = useUserStore();
const items = [ const items = [
{ {
title: "Home", title: "Home",
...@@ -51,7 +54,7 @@ const items = [ ...@@ -51,7 +54,7 @@ const items = [
<SidebarHeader class="pb-0"> <SidebarHeader class="pb-0">
<div class="flex items-center rounded-lg p-2 mt-1 mx-1 bg-background/40 border" v-if="settingsStore.isLoaded && hasUser"> <div class="flex items-center rounded-lg p-2 mt-1 mx-1 bg-background/40 border" v-if="settingsStore.isLoaded && hasUser">
<Avatar class="w-8 h-8 mr-2"> <Avatar class="w-8 h-8 mr-2">
<AvatarImage src="https://github.com/unovue.png" alt="@unovue" /> <AvatarImage v-if="userStore.profileImage" :src="userStore.profileImage" />
<AvatarFallback>{{ settingsStore.settings.account?.slice(0, 2) }}</AvatarFallback> <AvatarFallback>{{ settingsStore.settings.account?.slice(0, 2) }}</AvatarFallback>
</Avatar> </Avatar>
<span class="font-bold">@{{ settingsStore.settings.account }}</span> <span class="font-bold">@{{ settingsStore.settings.account }}</span>
......
...@@ -3,15 +3,19 @@ import type { HTMLAttributes } from 'vue' ...@@ -3,15 +3,19 @@ import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { Primitive, type PrimitiveProps } from 'reka-ui' import { Primitive, type PrimitiveProps } from 'reka-ui'
import { type ButtonVariants, buttonVariants } from '.' import { type ButtonVariants, buttonVariants } from '.'
import { mdiLoading } from '@mdi/js'
interface Props extends PrimitiveProps { interface Props extends PrimitiveProps {
variant?: ButtonVariants['variant'] variant?: ButtonVariants['variant']
size?: ButtonVariants['size'] size?: ButtonVariants['size']
class?: HTMLAttributes['class'] class?: HTMLAttributes['class']
loading?: boolean
disabled?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
as: 'button', as: 'button',
loading: false
}) })
</script> </script>
...@@ -19,8 +23,16 @@ const props = withDefaults(defineProps<Props>(), { ...@@ -19,8 +23,16 @@ const props = withDefaults(defineProps<Props>(), {
<Primitive <Primitive
:as="as" :as="as"
:as-child="asChild" :as-child="asChild"
:class="cn(buttonVariants({ variant, size }), props.class)" :disabled="loading || disabled"
:class="[ cn(buttonVariants({ variant, size }), props.class)]"
> >
<slot /> <span v-if="loading" class="animate-spin absolute mx-auto">
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path :style="{
fill: !variant || variant === 'default' ? 'hsl(var(--background))' : 'hsl(var(--foreground))'
}" :d="mdiLoading"/></svg>
</span>
<span :style="{ 'visibility': loading ? 'hidden' : 'visible' }" class="inline-flex items-center justify-center gap-2">
<slot/>
</span>
</Primitive> </Primitive>
</template> </template>
...@@ -4,7 +4,7 @@ import { Skeleton } from '@/components/ui/skeleton'; ...@@ -4,7 +4,7 @@ import { Skeleton } from '@/components/ui/skeleton';
import { mdiAccountKeyOutline } from '@mdi/js'; import { mdiAccountKeyOutline } from '@mdi/js';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { AccountAuthorityUpdateOperation, type authority } from '@hiveio/wax/vite'; import type { authority } from '@hiveio/wax/vite';
import { getWax } from '@/stores/wax.store'; import { getWax } from '@/stores/wax.store';
import PublicKey from '@/components/hive/PublicKey.vue'; import PublicKey from '@/components/hive/PublicKey.vue';
...@@ -20,6 +20,7 @@ const retrieveAuthority = async() => { ...@@ -20,6 +20,7 @@ const retrieveAuthority = async() => {
try { try {
const wax = await getWax(); const wax = await getWax();
const { AccountAuthorityUpdateOperation } = await import("@hiveio/wax/vite");
const op = await AccountAuthorityUpdateOperation.createFor(wax, settingsStore.settings.account!); const op = await AccountAuthorityUpdateOperation.createFor(wax, settingsStore.settings.account!);
memoKey.value = op.role("memo").value; memoKey.value = op.role("memo").value;
postingAuthority.value = op.role("posting").value; postingAuthority.value = op.role("posting").value;
......
...@@ -9,8 +9,7 @@ import { onMounted, ref } from 'vue'; ...@@ -9,8 +9,7 @@ import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { getWax } from '@/stores/wax.store'; import { getWax } from '@/stores/wax.store';
import { useWalletStore } from '@/stores/wallet.store'; import { useWalletStore } from '@/stores/wallet.store';
import { AccountAuthorityUpdateOperation } from '@hiveio/wax'; import { toastError } from '@/utils/parse-error';
import { toastError } from '@/lib/parse-error';
const settings = useSettingsStore(); const settings = useSettingsStore();
...@@ -43,6 +42,7 @@ const updateAuthority = async() => { ...@@ -43,6 +42,7 @@ const updateAuthority = async() => {
const wax = await getWax(); const wax = await getWax();
const tx = await wax.createTransaction(); const tx = await wax.createTransaction();
const { AccountAuthorityUpdateOperation } = await import("@hiveio/wax/vite");
const op = await AccountAuthorityUpdateOperation.createFor(wax, creator.value.startsWith('@') ? creator.value.slice(1) : creator.value); const op = await AccountAuthorityUpdateOperation.createFor(wax, creator.value.startsWith('@') ? creator.value.slice(1) : creator.value);
if (memoKey.value) if (memoKey.value)
op.role("memo").set(memoKey.value); op.role("memo").set(memoKey.value);
...@@ -95,7 +95,7 @@ const updateAuthority = async() => { ...@@ -95,7 +95,7 @@ const updateAuthority = async() => {
<Label for="updateAuthority_ownerKey">Add Owner Key</Label> <Label for="updateAuthority_ownerKey">Add Owner Key</Label>
<Input id="updateAuthority_ownerKey" placeholder="Nothing to add" v-model="ownerKey" class="my-2" /> <Input id="updateAuthority_ownerKey" placeholder="Nothing to add" v-model="ownerKey" class="my-2" />
</div> </div>
<Button class="my-2" @click="updateAuthority" :disabled="isLoading">Update Authority</Button> <Button class="my-2" @click="updateAuthority" :loading="isLoading">Update Authority</Button>
<p>Note: By clicking the above button, the transaction will be created, signed, and broadcasted immediately to the mainnet chain</p> <p>Note: By clicking the above button, the transaction will be created, signed, and broadcasted immediately to the mainnet chain</p>
</div> </div>
</CardContent> </CardContent>
......
...@@ -12,7 +12,7 @@ import { onMounted, ref } from 'vue'; ...@@ -12,7 +12,7 @@ import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { getWax } from '@/stores/wax.store'; import { getWax } from '@/stores/wax.store';
import { useWalletStore } from '@/stores/wallet.store'; import { useWalletStore } from '@/stores/wallet.store';
import { toastError } from '@/lib/parse-error'; import { toastError } from '@/utils/parse-error';
const settings = useSettingsStore(); const settings = useSettingsStore();
...@@ -178,7 +178,7 @@ const createAccount = async() => { ...@@ -178,7 +178,7 @@ const createAccount = async() => {
<Label for="createAccount_r3">Create claimed</Label> <Label for="createAccount_r3">Create claimed</Label>
</div> </div>
</RadioGroup> </RadioGroup>
<Button @click="createAccount" :disabled="isLoading">Create account</Button> <Button @click="createAccount" :loading="isLoading">Create account</Button>
<p>Note: By clicking the above button, the transaction will be created, signed, and broadcasted immediately to the mainnet chain</p> <p>Note: By clicking the above button, the transaction will be created, signed, and broadcasted immediately to the mainnet chain</p>
</div> </div>
</CardContent> </CardContent>
......
...@@ -9,7 +9,7 @@ import { Switch } from '@/components/ui/switch'; ...@@ -9,7 +9,7 @@ import { Switch } from '@/components/ui/switch';
import { useWalletStore } from '@/stores/wallet.store'; import { useWalletStore } from '@/stores/wallet.store';
import { getWax } from '@/stores/wax.store'; import { getWax } from '@/stores/wax.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { toastError } from '@/lib/parse-error'; import { toastError } from '@/utils/parse-error';
const walletStore = useWalletStore(); const walletStore = useWalletStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
...@@ -18,6 +18,7 @@ const hasWallet = computed(() => walletStore.hasWallet); ...@@ -18,6 +18,7 @@ const hasWallet = computed(() => walletStore.hasWallet);
const wallet = computed(() => walletStore.wallet); const wallet = computed(() => walletStore.wallet);
const isEncrypt = ref(false); const isEncrypt = ref(false);
const isLoading = ref(false);
const encryptForKey = ref(''); const encryptForKey = ref('');
const inputData = ref(''); const inputData = ref('');
const outputData = ref(''); const outputData = ref('');
...@@ -37,13 +38,21 @@ const getMemoKeyForUser = async(user: string): Promise<string | void> => { ...@@ -37,13 +38,21 @@ const getMemoKeyForUser = async(user: string): Promise<string | void> => {
} }
const useMyMemoKey = async () => { const useMyMemoKey = async () => {
const key = await getMemoKeyForUser(settingsStore.account!); try {
if (key) isLoading.value = true;
encryptForKey.value = key;
const key = await getMemoKeyForUser(settingsStore.account!);
if (key)
encryptForKey.value = key;
} finally {
isLoading.value = false;
}
} }
const encryptOrDecrypt = async () => { const encryptOrDecrypt = async () => {
try { try {
isLoading.value = true;
if (isEncrypt.value) { if (isEncrypt.value) {
let publicKey: string; let publicKey: string;
let accountOrKey = encryptForKey.value; let accountOrKey = encryptForKey.value;
...@@ -60,6 +69,8 @@ const encryptOrDecrypt = async () => { ...@@ -60,6 +69,8 @@ const encryptOrDecrypt = async () => {
} }
} catch (error) { } catch (error) {
toastError(`Error ${isEncrypt.value ? 'encrypting' : 'decrypting'} memo`, error); toastError(`Error ${isEncrypt.value ? 'encrypting' : 'decrypting'} memo`, error);
} finally {
isLoading.value = false;
} }
}; };
</script> </script>
...@@ -84,7 +95,7 @@ const encryptOrDecrypt = async () => { ...@@ -84,7 +95,7 @@ const encryptOrDecrypt = async () => {
<div class="flex mb-4 underline text-sm" v-if="isEncrypt"> <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> <a @click="useMyMemoKey" class="ml-auto mr-1 cursor-pointer" style="color: hsla(var(--foreground) / 70%)">Use my memo key</a>
</div> </div>
<Button :disabled="!hasWallet || (!encryptForKey && isEncrypt)" @click="encryptOrDecrypt">{{ isEncrypt ? "Encrypt" : "Decrypt" }}</Button> <Button :loading="isLoading" :disabled="!hasWallet || (!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" disabled/>
</CardContent> </CardContent>
</Card> </Card>
......
...@@ -8,7 +8,7 @@ import { useWalletStore } from '@/stores/wallet.store'; ...@@ -8,7 +8,7 @@ import { useWalletStore } from '@/stores/wallet.store';
import { getWax } from '@/stores/wax.store'; import { getWax } from '@/stores/wax.store';
import type { ITransactionBase, TRole } from '@hiveio/wax/vite'; import type { ITransactionBase, TRole } from '@hiveio/wax/vite';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { toastError } from '@/lib/parse-error'; import { toastError } from '@/utils/parse-error';
const walletStore = useWalletStore(); const walletStore = useWalletStore();
...@@ -21,6 +21,7 @@ const outputData = ref(''); ...@@ -21,6 +21,7 @@ const outputData = ref('');
const router = useRouter(); const router = useRouter();
const isLoading = ref(false); const isLoading = ref(false);
const isBroadcasting = ref(false);
const sign = async () => { const sign = async () => {
try { try {
...@@ -63,7 +64,7 @@ const sign = async () => { ...@@ -63,7 +64,7 @@ const sign = async () => {
const broadcast = async () => { const broadcast = async () => {
try { try {
isLoading.value = true; isBroadcasting.value = true;
const wax = await getWax(); const wax = await getWax();
...@@ -74,7 +75,7 @@ const broadcast = async () => { ...@@ -74,7 +75,7 @@ const broadcast = async () => {
} catch (error) { } catch (error) {
toastError('Error broadcasting transaction', error); toastError('Error broadcasting transaction', error);
} finally { } finally {
isLoading.value = false; isBroadcasting.value = false;
} }
}; };
...@@ -95,11 +96,11 @@ onMounted(() => { ...@@ -95,11 +96,11 @@ onMounted(() => {
<CardContent> <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"/>
<div class="my-4 space-x-4"> <div class="my-4 space-x-4">
<Button :disabled="!inputData || !hasWallet || isLoading" @click="sign">Sign transaction</Button> <Button :disabled="!inputData || !hasWallet || isBroadcasting" :loading="isLoading" @click="sign">Sign transaction</Button>
</div> </div>
<Textarea v-model="outputData" placeholder="Signature" copy-enabled class="my-4" disabled/> <Textarea v-model="outputData" placeholder="Signature" copy-enabled class="my-4" disabled/>
<div class="my-4 space-x-4"> <div class="my-4 space-x-4">
<Button :disabled="!outputData || isLoading" @click="broadcast">Broadcast signed transaction</Button> <Button :disabled="!outputData || isLoading" :loading="isBroadcasting" @click="broadcast">Broadcast signed transaction</Button>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
......
import { WaxChainApiError, WaxError } from "@hiveio/wax/vite";
import { toast } from "vue-sonner";
export const toastError = (title: string, error: unknown) => {
let description: string;
if (typeof error === "object" && error) {
if (error instanceof WaxError) {
if (error instanceof WaxChainApiError) {
if (error.apiError
&& typeof error.apiError === "object"
&& "error" in error.apiError
&& typeof error.apiError.error === "object"
&& error.apiError.error
&& "message" in error.apiError.error
&& typeof error.apiError.error.message === "string")
description = error.apiError.error.message;
else
description = error.message;
} else {
description = error.message;
}
} else if ("message" in error) {
description = (error as Error).message;
} else {
description = String(error);
}
} else {
description = String(error);
}
toast.error(title, { description });
};
import { defineStore } from "pinia"
import type { ApiAccount } from "@hiveio/wax/vite";
export const useUserStore = defineStore('user', {
state: () => ({
isReady: false,
parsedJsonMetadata: undefined as undefined | Record<string, any>,
userData: undefined as undefined | ApiAccount
}),
getters: {
profileImage: (ctx): undefined | string => ctx.isReady ? ctx.parsedJsonMetadata?.profile?.profile_image : undefined,
name: (ctx): undefined | string => ctx.isReady ? ctx.parsedJsonMetadata?.profile?.name : undefined,
about: (ctx): undefined | string => ctx.isReady ? ctx.parsedJsonMetadata?.profile?.about : undefined,
website: (ctx): undefined | string => ctx.isReady ? ctx.parsedJsonMetadata?.profile?.website : undefined
},
actions: {
// Used for logout
resetSettings() {
this.isReady = false;
this.parsedJsonMetadata = undefined;
this.userData = undefined;
},
setUserData(data: ApiAccount) {
this.userData = data;
this.parsedJsonMetadata = JSON.parse(data.posting_json_metadata);
this.isReady = true;
}
}
})
import { createHiveChain, type TWaxExtended, type asset } from "@hiveio/wax/vite"; import type { TWaxExtended, asset } from "@hiveio/wax/vite";
export interface WaxApi { export interface WaxApi {
database_api: { database_api: {
...@@ -17,7 +17,7 @@ let chain: TWaxExtended<WaxApi>; ...@@ -17,7 +17,7 @@ let chain: TWaxExtended<WaxApi>;
export const getWax = async() => { export const getWax = async() => {
if (!chain) if (!chain)
chain = (await createHiveChain()).extend<WaxApi>(); chain = (await (await import("@hiveio/wax/vite")).createHiveChain()).extend<WaxApi>();
return chain; return chain;
}; };
import { toast } from "vue-sonner";
export const toastError = (title: string, error: unknown) => {
let description: string;
if (typeof error === "object" && error && "message" in error) {
if ("name" in error
&& error.name === "WaxChainApiError"
&& "apiError" in error
&& error.apiError
&& typeof error.apiError === "object"
&& "error" in error.apiError
&& typeof error.apiError.error === "object"
&& error.apiError.error
&& "message" in error.apiError.error
&& typeof error.apiError.error.message === "string")
description = error.apiError.error.message as string;
else
description = error.message as string;
} else
description = String(error);
toast.error(title, { description });
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment