Skip to content
Snippets Groups Projects
Verified Commit 4abab212 authored by Mateusz Tyszczak's avatar Mateusz Tyszczak :scroll:
Browse files

Add request account creation

parent 4fdf51b3
No related branches found
No related tags found
No related merge requests found
Pipeline #118308 passed
...@@ -6,6 +6,7 @@ const props = defineProps<{ ...@@ -6,6 +6,7 @@ const props = defineProps<{
afterValue?: string; afterValue?: string;
context?: number; context?: number;
disableCopy?: boolean; disableCopy?: boolean;
class?: string;
}>(); }>();
const context = props.context ?? 6; const context = props.context ?? 6;
...@@ -16,8 +17,8 @@ const value = String(props.value); ...@@ -16,8 +17,8 @@ const value = String(props.value);
<template> <template>
<div class="flex items-center"> <div class="flex items-center">
<span class="font-mono pt-[2px] mr-1"> <span class="font-mono pt-[2px] mr-1">
<span v-if="context > 0">{{ value.slice(0, context) }}...{{ value.slice(-context) }}</span> <span v-if="context > 0" :class="props.class">{{ value.slice(0, context) }}...{{ value.slice(-context) }}</span>
<span v-else>{{ value }}</span> <span v-else :class="props.class">{{ value }}</span>
<span class="ml-2" v-if="props.afterValue">{{ props.afterValue }}</span> <span class="ml-2" v-if="props.afterValue">{{ props.afterValue }}</span>
</span> </span>
<Button v-if="!props.disableCopy" :value="value"/> <Button v-if="!props.disableCopy" :value="value"/>
......
<script setup lang="ts"> <script setup lang="ts">
import { mdiHomeOutline, mdiMessageLockOutline, mdiFileSign, mdiAccountPlusOutline, mdiAccountArrowUpOutline } from "@mdi/js" import { mdiHomeOutline, mdiMessageLockOutline, mdiFileSign, mdiAccountPlusOutline, mdiAccountArrowUpOutline, mdiAccountReactivateOutline } from "@mdi/js"
import { Sidebar, SidebarContent, SidebarHeader, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar" import { Sidebar, SidebarContent, SidebarHeader, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar"
import { useSidebar } from "@/components/ui/sidebar"; import { useSidebar } from "@/components/ui/sidebar";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { Button } from "@/components/ui/button";
import { useWalletStore } from "@/stores/wallet.store";
import { computed } from "vue";
const router = useRouter(); const router = useRouter();
const { toggleSidebar, isMobile } = useSidebar(); const { toggleSidebar, isMobile } = useSidebar();
const walletStore = useWalletStore();
const isDisabledMenuButton = computed(() => !walletStore.hasWallet);
const groups = [{ const groups = [{
title: "Account management", title: "Account management",
items: [ items: [
...@@ -16,15 +23,22 @@ const groups = [{ ...@@ -16,15 +23,22 @@ const groups = [{
url: "/", url: "/",
icon: mdiHomeOutline, icon: mdiHomeOutline,
}, },
{
title: "Request Account Creation",
url: "/account/request",
icon: mdiAccountPlusOutline,
},
{ {
title: "Process Account Creation", title: "Process Account Creation",
url: "/account/create", url: "/account/create",
icon: mdiAccountPlusOutline, icon: mdiAccountReactivateOutline,
disabled: isDisabledMenuButton,
}, },
{ {
title: "Process Authority Update", title: "Process Authority Update",
url: "/account/update", url: "/account/update",
icon: mdiAccountArrowUpOutline, icon: mdiAccountArrowUpOutline,
disabled: isDisabledMenuButton,
}, },
] ]
}, { }, {
...@@ -42,6 +56,13 @@ const groups = [{ ...@@ -42,6 +56,13 @@ const groups = [{
}, },
] ]
}]; }];
const navigateTo = (url: string) => {
router.push(url);
if (isMobile.value)
toggleSidebar();
};
</script> </script>
<template> <template>
...@@ -59,10 +80,10 @@ const groups = [{ ...@@ -59,10 +80,10 @@ const groups = [{
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem v-for="item in group.items" :key="item.title"> <SidebarMenuItem v-for="item in group.items" :key="item.title">
<SidebarMenuButton asChild :class="{ 'bg-primary/5': router.currentRoute.value.path === item.url }"> <SidebarMenuButton asChild :class="{ 'bg-primary/5': router.currentRoute.value.path === item.url }">
<RouterLink @click="isMobile && toggleSidebar()" :to="item.url"> <Button variant="ghost" :disabled="item.disabled?.value" @click="navigateTo(item.url)" class="flex justify-start">
<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> <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> <span class="text-foreground/80">{{item.title}}</span>
</RouterLink> </Button>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
</SidebarMenu> </SidebarMenu>
......
...@@ -154,8 +154,12 @@ const generateAccountUpdateTransaction = async(): Promise<string | void> => { ...@@ -154,8 +154,12 @@ const generateAccountUpdateTransaction = async(): Promise<string | void> => {
toastError("Failed to generate account update transaction", error); toastError("Failed to generate account update transaction", error);
} }
}; };
const hasCopiedCreateSignLink = ref(false);
const getAccountCreateSigningLink = (): string => { const getAccountCreateSigningLink = (): string => {
const accountName = createAccountNameOperation.value!.startsWith('@') ? createAccountNameOperation.value!.slice(1) : createAccountNameOperation.value!; const accountName = createAccountNameOperation.value!.startsWith('@') ? createAccountNameOperation.value!.slice(1) : createAccountNameOperation.value!;
hasCopiedCreateSignLink.value = true;
return `${window.location.protocol}//${window.location.host}/account/create?acc=${accountName}&posting=${ return `${window.location.protocol}//${window.location.host}/account/create?acc=${accountName}&posting=${
metamaskPublicKeys.value!.find(node => node.role === "posting")!.publicKey metamaskPublicKeys.value!.find(node => node.role === "posting")!.publicKey
}&active=${ }&active=${
...@@ -166,6 +170,9 @@ const getAccountCreateSigningLink = (): string => { ...@@ -166,6 +170,9 @@ const getAccountCreateSigningLink = (): string => {
metamaskPublicKeys.value!.find(node => node.role === "memo")!.publicKey metamaskPublicKeys.value!.find(node => node.role === "memo")!.publicKey
}`; }`;
}; };
const hasCopiedUpdateSignLink = ref(false);
const getAuthorityUpdateSigningLink = (): string => { const getAuthorityUpdateSigningLink = (): string => {
const accountName = updateAccountNameOperation.value!.startsWith('@') ? updateAccountNameOperation.value!.slice(1) : updateAccountNameOperation.value!; const accountName = updateAccountNameOperation.value!.startsWith('@') ? updateAccountNameOperation.value!.slice(1) : updateAccountNameOperation.value!;
const url = new URL(`${window.location.protocol}//${window.location.host}/account/update?acc=${accountName}`); const url = new URL(`${window.location.protocol}//${window.location.host}/account/update?acc=${accountName}`);
...@@ -173,6 +180,8 @@ const getAuthorityUpdateSigningLink = (): string => { ...@@ -173,6 +180,8 @@ const getAuthorityUpdateSigningLink = (): string => {
if (updateAuthType[key as TRole]) if (updateAuthType[key as TRole])
url.searchParams.set(key, metamaskPublicKeys.value!.find(node => node.role === key)!.publicKey); url.searchParams.set(key, metamaskPublicKeys.value!.find(node => node.role === key)!.publicKey);
hasCopiedUpdateSignLink.value = true;
return url.toString(); return url.toString();
}; };
...@@ -247,6 +256,9 @@ const updateAccountName = (value: string | any) => { ...@@ -247,6 +256,9 @@ const updateAccountName = (value: string | any) => {
<Button :disabled="isLoading || !updateAccountNameOperation" :copy="getAuthorityUpdateSigningLink" variant="outline" size="lg" class="mt-4 px-8 py-4 border-[#FF5C16] border-[1px]"> <Button :disabled="isLoading || !updateAccountNameOperation" :copy="getAuthorityUpdateSigningLink" variant="outline" size="lg" class="mt-4 px-8 py-4 border-[#FF5C16] border-[1px]">
<span class="text-md font-bold">Copy signing link</span> <span class="text-md font-bold">Copy signing link</span>
</Button> </Button>
<p v-if="hasCopiedUpdateSignLink" class="mt-4">
Now send this link to someone who has an account to execute this operation in blockchain
</p>
<Separator label="Or" class="mt-8" /> <Separator label="Or" class="mt-8" />
<div class="flex justify-center mt-4"> <div class="flex justify-center mt-4">
<Button :disabled="isLoading" :copy="generateAccountUpdateTransaction" variant="outline" size="lg" class="px-8 opacity-[0.9] py-4 border-[#FF5C16] border-[1px]"> <Button :disabled="isLoading" :copy="generateAccountUpdateTransaction" variant="outline" size="lg" class="px-8 opacity-[0.9] py-4 border-[#FF5C16] border-[1px]">
...@@ -256,7 +268,7 @@ const updateAccountName = (value: string | any) => { ...@@ -256,7 +268,7 @@ const updateAccountName = (value: string | any) => {
</div> </div>
</div> </div>
<div v-else-if="showCreateAccountModal"> <div v-else-if="showCreateAccountModal">
<p class="mb-4">Step 6: Fill in this form in order to prepare the operation to create an account with requested metadata. Then please copy the signing link, and send it to someone who already has an account to execute this operation in blockchain:</p> <p class="mb-4">Step 6: Fill in this form in order to prepare the operation to request account creation</p>
<div class="grid mb-2 w-full max-w-sm items-center gap-1.5"> <div class="grid mb-2 w-full max-w-sm items-center gap-1.5">
<Label for="metamask_createAuth_account">New account name</Label> <Label for="metamask_createAuth_account">New account name</Label>
<Input v-model="createAccountNameOperation!" @update:model-value="validateAccountName()" id="metamask_createAuth_account" /> <Input v-model="createAccountNameOperation!" @update:model-value="validateAccountName()" id="metamask_createAuth_account" />
...@@ -273,6 +285,9 @@ const updateAccountName = (value: string | any) => { ...@@ -273,6 +285,9 @@ const updateAccountName = (value: string | any) => {
<Button :copy="getAccountCreateSigningLink" :disabled="isLoading || !createAccountNameOperation || !accountNameValid" variant="outline" size="lg" class="mt-4 px-8 py-4 border-[#FF5C16] border-[1px]"> <Button :copy="getAccountCreateSigningLink" :disabled="isLoading || !createAccountNameOperation || !accountNameValid" variant="outline" size="lg" class="mt-4 px-8 py-4 border-[#FF5C16] border-[1px]">
<span class="text-md font-bold">Copy signing link</span> <span class="text-md font-bold">Copy signing link</span>
</Button> </Button>
<p v-if="hasCopiedCreateSignLink" class="mt-4">
Now send this link to someone who has an account to execute this operation in blockchain
</p>
</div> </div>
</div> </div>
<div v-else-if="accountsMatchingKeys.length === 0"> <div v-else-if="accountsMatchingKeys.length === 0">
......
<script setup lang="ts"> <script setup lang="ts">
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { mdiAccountPlusOutline } from '@mdi/js'; import { mdiAccountReactivateOutline } from '@mdi/js';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
...@@ -124,7 +124,7 @@ const createAccount = async() => { ...@@ -124,7 +124,7 @@ const createAccount = async() => {
<CardHeader> <CardHeader>
<CardTitle class="inline-flex items-center justify-between"> <CardTitle class="inline-flex items-center justify-between">
<span>Process Account Creation</span> <span>Process Account Creation</span>
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsla(var(--foreground) / 80%)" :d="mdiAccountPlusOutline"/></svg> <svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsla(var(--foreground) / 80%)" :d="mdiAccountReactivateOutline"/></svg>
</CardTitle> </CardTitle>
<CardDescription class="mr-8">Use this module to process account creation request sent by other users</CardDescription> <CardDescription class="mr-8">Use this module to process account creation request sent by other users</CardDescription>
</CardHeader> </CardHeader>
......
<script setup lang="ts">
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { mdiAccountPlusOutline } from '@mdi/js';
import { computed, ref } from 'vue';
import { toastError } from '@/utils/parse-error';
import { getWax } from '@/stores/wax.store';
import { useWalletStore } from '@/stores/wallet.store';
import { useMetamaskStore } from '@/stores/metamask.store';
import type { TRole } from '@hiveio/wax/vite';
import { toast } from 'vue-sonner';
const walletStore = useWalletStore();
const metamaskStore = useMetamaskStore();
const publicKeys = ref<Record<TRole, string>>({
owner: "",
active: "",
posting: "",
memo: ""
});
const accountNameValid = ref(false);
const createAccountNameOperation = ref('');
const isLoading = ref(false);
const hasCopiedCreateSignLink = ref(false);
const validateAccountName = async() => {
try {
if(!createAccountNameOperation.value)
return accountNameValid.value = false;
const accountName = createAccountNameOperation.value.startsWith("@") ? createAccountNameOperation.value.slice(1) : createAccountNameOperation.value;
if (!accountName)
return accountNameValid.value = false;
const wax = await getWax();
return accountNameValid.value = wax.isValidAccountName(accountName);
} catch (error) {
toastError("Failed to validate account name", error);
}
}
const parseMetamaskPublicKeys = async() => {
const toastToDismiss = toast.loading("Metamask detected. Parsing public keys...");
try {
isLoading.value = true;
try {
await metamaskStore.connect();
} catch {
toast.error("Metamask is not installed or not connected");
return;
}
const { publicKeys: metamaskPublicKeys } = await metamaskStore.call("hive_getPublicKeys", {
keys: [{
role: "owner"
},{
role: "active"
},{
role: "posting"
},{
role: "memo"
}]
}) as any;
for(const publicKey of metamaskPublicKeys)
publicKeys.value[publicKey.role as TRole] = publicKey.publicKey;
toast.success("Successfully parsed Metamask public keys");
} catch (error) {
toastError("Failed to parse Metamask public keys", error);
throw error; // Make sure this method throws to handle sonner toast properly
} finally {
isLoading.value = false;
toast.dismiss(toastToDismiss);
}
};
const hasMetamaskWithSnap = computed(() => walletStore.walletsStatus.metamask && metamaskStore.isInstalled);
if(hasMetamaskWithSnap)
void parseMetamaskPublicKeys();
const getAccountCreateSigningLink = (): string => {
const accountName = createAccountNameOperation.value!.startsWith('@') ? createAccountNameOperation.value!.slice(1) : createAccountNameOperation.value!;
hasCopiedCreateSignLink.value = true;
return `${window.location.protocol}//${window.location.host}/account/create?acc=${accountName}&${Object.values(publicKeys.value).map((key, index) => `key${index + 1}=${key}`).join('&')}`;
};
</script>
<template>
<Card class="w-full max-w-[600px]">
<CardHeader>
<CardTitle class="inline-flex items-center justify-between">
<span>Request account creation</span>
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsla(var(--foreground) / 80%)" :d="mdiAccountPlusOutline"/></svg>
</CardTitle>
<CardDescription class="mr-8">Fill in this form in order to prepare the operation to request account creation</CardDescription>
</CardHeader>
<CardContent>
<div class="space-y-4" v-if="hasMetamaskWithSnap">
<div class="grid mb-2 w-full items-center gap-1.5">
<Label for="metamask_createAuth_account_card">New account name</Label>
<Input class="w-full" v-model="createAccountNameOperation!" @update:model-value="validateAccountName()" id="metamask_createAuth_account_card" />
<span class="text-red-400" v-if="createAccountNameOperation && !accountNameValid">Invalid account name</span>
</div>
<div v-for="(_key, role) in publicKeys" :key="role" class="grid mb-2 w-full items-center gap-1.5">
<Label :for="`metamask_createAuth_account_key_${role}_card`">{{ role[0].toUpperCase() }}{{ role.slice(1) }} key</Label>
<Input class="w-full" v-model="publicKeys[role]" :id="`metamask_createAuth_account_key_${role}_card`" />
</div>
<Button :copy="getAccountCreateSigningLink" :disabled="isLoading || !createAccountNameOperation || !accountNameValid">
<span class="text-md font-bold">Copy signing link</span>
</Button>
<p v-if="hasCopiedCreateSignLink">
Now send this link to someone who has an account to execute this operation in blockchain
</p>
</div>
<div v-else class="space-y-4">
<div class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4" role="alert">
<p class="font-bold">Wallet required</p>
<p>You have to connect to Metamask wallet before continuing!</p>
</div>
<Button @click="walletStore.openWalletSelectModal()" class="w-full font-bold">
Connect to Metamask wallet
</Button>
</div>
</CardContent>
</Card>
</template>
\ No newline at end of file
<script setup lang="ts">
import RequestAccountCreate from '@/components/utilcards/RequestAccountCreate.vue';
</script>
<template>
<div class="flex p-8">
<RequestAccountCreate />
</div>
</template>
...@@ -2,6 +2,7 @@ import Index from "@/pages/index.vue"; ...@@ -2,6 +2,7 @@ import Index from "@/pages/index.vue";
import SignTransaction from "@/pages/sign/transaction.vue"; import SignTransaction from "@/pages/sign/transaction.vue";
import SignMessage from "@/pages/sign/message.vue"; import SignMessage from "@/pages/sign/message.vue";
import AccountCreate from "@/pages/account/create.vue"; import AccountCreate from "@/pages/account/create.vue";
import RequestCreate from "@/pages/account/request.vue";
import AccountUpdate from "@/pages/account/update.vue"; import AccountUpdate from "@/pages/account/update.vue";
export const routes = [ export const routes = [
...@@ -9,5 +10,6 @@ export const routes = [ ...@@ -9,5 +10,6 @@ export const routes = [
{ path: '/sign/transaction', component: SignTransaction }, { path: '/sign/transaction', component: SignTransaction },
{ path: '/sign/message', component: SignMessage }, { path: '/sign/message', component: SignMessage },
{ path: '/account/create', component: AccountCreate }, { path: '/account/create', component: AccountCreate },
{ path: '/account/request', component: RequestCreate },
{ path: '/account/update', component: AccountUpdate } { path: '/account/update', component: AccountUpdate }
]; ];
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