This commit is contained in:
Georges KABBOUCHI 2021-08-20 01:12:39 +03:00
parent 131bc04455
commit 0ad579c6e7
10 changed files with 420 additions and 52 deletions

View File

@ -31,7 +31,7 @@
<toggle-button
v-if="active && canSimulate"
@change="checked => (checked ? startSimulation() : stopSimulation())"
:checked="forkId"
:checked="!! forkId"
:loading="loading"
class="ml-4 border-l pl-4"
label="Simulation Mode"

View File

@ -0,0 +1,41 @@
<template>
<button
class="flex items-center justify-center flex-shrink-0 py-2 font-semibold whitespace-no-wrap duration-75 ease-out transform rounded-[4px] select-none border border-ocean-blue-pure text-ocean-blue-pure shadow-cta focus:outline-none dark:shadow-none"
:class="{
'bg-opacity-50 pointer-events-none': disabled && !loading,
'hover:-translate-y-px': !disabled && !loading,
'active:translate-y-px': !disabled && !loading
}"
:disabled="disabled || loading"
v-bind="$attrs"
v-on="$listeners"
>
<slot v-if="!loading" />
<CustomTransition
enter-active-class="duration-200 ease-out"
enter-class="w-0 ml-0 opacity-0"
enter-to-class="w-4 opacity-100"
after-enter-class="w-4 "
leave-active-class="duration-100 ease-in"
leave-class="w-4 opacity-100"
leave-to-class="w-0 ml-0 opacity-0"
>
<SVGSpinner v-if="loading" class="h-4 animate-spin-loading" />
</CustomTransition>
</button>
</template>
<script>
import { defineComponent } from '@nuxtjs/composition-api'
import SVGSpinner from '@/assets/icons/spinner.svg?inline'
export default defineComponent({
components: {
SVGSpinner,
},
props: {
loading: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
},
})
</script>

View File

@ -0,0 +1,74 @@
<template>
<div
class="inline-block w-full max-w-md px-8 py-7 overflow-hidden text-left align-bottom transition-all transform bg-white border border-opacity-50 rounded-lg shadow-xl dark:bg-dark-400 sm:my-8 sm:align-middle sm:p-6 border-green-light"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div>
<div class="mt-3 text-center sm:mt-5">
<h3 id="modal-headline" class="font-bold text-2xl">
Add Authority
</h3>
</div>
<div class="my-10">
<div class="relative">
<Input class="mr-4" v-model="authority" />
<span
v-if="!authority"
class="absolute right-0 inset-y-0 mt-3 mr-4 uppercase text-[#1874FF] font-semibold text-xs"
>Paste</span
>
</div>
</div>
<div class="flex justify-between items-center mt-4 sm:mt-6">
<ButtonCTAOutlined class="flex-1 px-8 rounded" @click="close">
Cancel
</ButtonCTAOutlined>
<ButtonCTA
@click="createAuthorityHandler"
class="ml-4 flex-1 px-8"
:loading="creatingAuthority"
:disabled="creatingAuthority"
>
create
</ButtonCTA>
</div>
</div>
</div>
</template>
<script>
import { defineComponent, ref } from '@nuxtjs/composition-api'
import Input from '~/components/common/input/Input.vue'
import { useDSA } from '~/composables/useDSA'
import { useModal } from '~/composables/useModal'
import ButtonCTA from '../../common/input/ButtonCTA.vue'
import ButtonCTAOutlined from '../../common/input/ButtonCTAOutlined.vue'
export default defineComponent({
components: { ButtonCTA, ButtonCTAOutlined, Input },
setup() {
const { close } = useModal()
const { createAuthority, creatingAuthority } = useDSA()
const authority = ref();
const createAuthorityHandler = async () => {
await createAuthority(authority.value)
close()
}
return {
authority,
close,
createAuthorityHandler,
creatingAuthority,
}
},
})
</script>

View File

@ -0,0 +1,89 @@
<template>
<div
class="inline-block w-full max-w-md px-4 py-6 overflow-hidden text-left align-bottom transition-all transform bg-white border border-opacity-50 rounded-lg shadow-xl dark:bg-dark-400 sm:my-8 sm:align-middle sm:p-6 border-green-light"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div>
<div class="mt-3 text-center sm:mt-5">
<h3 id="modal-headline" class="font-bold text-2xl">
Remove Authority
</h3>
<p class="px-6 mt-4 text-[#9FB0C9]">
this action will remove this account as authority
</p>
</div>
<div
class="border-b border-[#DBE5F4] py-4 text-sm text-center flex items-center justify-center"
>
<svg
class="mr-2"
width="12"
height="14"
viewBox="0 0 12 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 7C7.89375 7 9.42857 5.4332 9.42857 3.5C9.42857 1.5668 7.89375 0 6 0C4.10625 0 2.57143 1.5668 2.57143 3.5C2.57143 5.4332 4.10625 7 6 7ZM8.56607 7.89141L7.28571 13.125L6.42857 9.40625L7.28571 7.875H4.71429L5.57143 9.40625L4.71429 13.125L3.43393 7.89141C1.52411 7.98438 0 9.57852 0 11.55V12.6875C0 13.4121 0.575893 14 1.28571 14H10.7143C11.4241 14 12 13.4121 12 12.6875V11.55C12 9.57852 10.4759 7.98438 8.56607 7.89141Z"
fill="#9FB0C9"
/>
</svg>
<div>
{{ authority }}
</div>
</div>
<div class="flex justify-between items-center mt-4 sm:mt-6">
<ButtonCTAOutlined class="flex-1 px-8 rounded" @click="close">
Cancel
</ButtonCTAOutlined>
<ButtonCTA
@click="removeAuthorityHandler(authority)"
class="ml-4 flex-1 px-8"
:loading="removingAuthority"
:disabled="removingAuthority"
>
Remove
</ButtonCTA>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from '@nuxtjs/composition-api'
import { useDSA } from '~/composables/useDSA'
import { useModal } from '~/composables/useModal'
import ButtonCTA from '../../common/input/ButtonCTA.vue'
import ButtonCTAOutlined from '../../common/input/ButtonCTAOutlined.vue'
export default defineComponent({
props: {
authority: {
type: String,
required: true
}
},
components: { ButtonCTA, ButtonCTAOutlined },
setup() {
const { close } = useModal()
const { removeAuthority, removingAuthority } = useDSA()
const removeAuthorityHandler = async (authority) => {
await removeAuthority(authority)
close()
}
return {
close,
removeAuthorityHandler,
removingAuthority,
}
},
})
</script>

View File

@ -45,6 +45,7 @@ export function useBalances() {
});
const fetchBalances = async (refresh = false) => {
if (!balances.user || refresh) {
if (!account.value) return;
balances.user = {
mainnet:
networkName.value === Network.Mainnet
@ -58,6 +59,8 @@ export function useBalances() {
}
if (!balances.dsa || refresh) {
if (!activeAccount.value) return;
balances.dsa = {
mainnet:
networkName.value === Network.Mainnet
@ -123,8 +126,6 @@ export function useBalances() {
};
watch(web3, () => {
console.log("Fetch balances");
fetchBalances(true);
});
return {
@ -144,8 +145,6 @@ async function getBalances(
web3: Web3,
additionalTokens = []
) {
console.log("getBalances", owner, network, web3);
try {
const tokenResolverABI = abis.resolver.balance;
const tokenResolverAddr = addresses[network].resolver.balance;

View File

@ -2,58 +2,63 @@ import { computed, readonly, ref, watch } from "@nuxtjs/composition-api";
import { useWeb3 } from "./useWeb3";
//@ts-ignore
import DSA from "dsa-connect";
import addresses from "~/constant/addresses";
import abis from "~/constant/abis";
import { AbiItem } from "web3-utils";
import { useNotification } from "./useNotification";
const dsa = ref<DSA>();
const accounts = ref<any[]>([]);
const activeAccount = ref<any>();
const authorities = ref<string[]>();
export function useDSA() {
const { web3, chainId } = useWeb3();
const { web3, chainId, networkName, account } = useWeb3();
const { showWarning } = useNotification();
watch(
web3,
() => {
if (web3.value) {
dsa.value = new DSA(web3.value, chainId.value);
watch(web3, () => {
if (web3.value) {
dsa.value = new DSA(web3.value, chainId.value);
}
});
watch(chainId, () => {
if (web3.value) {
dsa.value = new DSA(web3.value, chainId.value);
}
});
watch(dsa, async () => {
if (dsa.value) {
accounts.value = await dsa.value.getAccounts();
if (accounts.value.length > 0) {
activeAccount.value = accounts.value[0];
} else {
activeAccount.value = undefined;
}
}
);
watch(
chainId,
() => {
if (web3.value) {
dsa.value = new DSA(web3.value, chainId.value);
}
},
);
watch(
dsa,
async () => {
if (dsa.value) {
accounts.value = await dsa.value.getAccounts();
if (accounts.value.length > 0) {
activeAccount.value = accounts.value[0];
}
}
//@ts-ignore
window.dsa = dsa.value
}
);
//@ts-ignore
window.dsa = dsa.value;
});
watch(
activeAccount,
async () => {
authorities.value = [];
if (activeAccount.value) {
dsa.value.setAccount(activeAccount.value.id);
fethAuthorities();
}
},
{ immediate: true }
);
const creatingAccount = ref(false);
const creatingAuthority = ref(false);
const removingAuthority = ref(false);
async function createAccount() {
creatingAccount.value = true;
@ -72,6 +77,88 @@ export function useDSA() {
activeAccount.value = account;
}
async function fethAuthorities() {
try {
const accountsResolverInstance = new web3.value.eth.Contract(
abis.resolver.accounts as AbiItem[],
addresses[networkName.value].resolver.accounts
);
const rawData = await accountsResolverInstance.methods
.getAccountAuthorities(activeAccount.value.address)
.call();
authorities.value = rawData;
} catch (error) {}
}
async function createAuthority(newAuthority: string) {
try {
if (!newAuthority) {
return;
}
const owners = authorities.value.map(x => x.toLowerCase());
if (owners.includes(newAuthority.toLowerCase())) {
showWarning("Create Authority", "Account is already an owner!");
return;
}
creatingAuthority.value = true;
const spells = dsa.value.Spell();
spells.add({
connector: "authority",
method: "add",
args: [newAuthority]
});
const transactionHash = await dsa.value.cast({
spells,
from: account.value
});
creatingAuthority.value = false;
fethAuthorities();
return transactionHash;
} catch (error) {
creatingAuthority.value = false;
}
}
async function removeAuthority(authority) {
try {
if (authorities.value.length <= 1) {
showWarning("Remove Authority", "Cannot remove all authorities!");
return;
}
removingAuthority.value = true;
const spells = dsa.value.Spell();
spells.add({
connector: "authority",
method: "remove",
args: [authority]
});
const transactionHash = await dsa.value.cast({
spells,
from: account.value
});
removingAuthority.value = false;
fethAuthorities();
return transactionHash;
} catch (error) {
removingAuthority.value = false;
}
}
return {
dsa,
activeAccount: readonly(activeAccount),
@ -80,6 +167,11 @@ export function useDSA() {
creatingAccount,
setAccount,
web3,
chainId
chainId,
authorities,
createAuthority,
creatingAuthority,
removeAuthority,
removingAuthority
};
}

View File

@ -107,14 +107,14 @@ export function useFormatting() {
return formatter.format(value);
}
function shortenHash(hash: any) {
function shortenHash(hash: any, size = 4) {
if (!hash) return;
if (hash.length < 12) return hash;
const beginningChars = hash.startsWith("0x") ? 6 : 4;
const beginningChars = hash.startsWith("0x") ? size + 2 : size;
const shortened = hash.substr(0, beginningChars) + "…" + hash.substr(-4);
const shortened = hash.substr(0, beginningChars) + "…" + hash.substr(-size);
return shortened;
}

View File

@ -23,12 +23,18 @@ export function useModal() {
const isShown = computed(() => !!modal.value);
function showComponent(component, componentProps = {}) {
modal.value = component;
props.value = componentProps;
}
return {
showNetworksMismatchDialog,
close,
closePersistent,
isShown,
modal: computed(() => modal.value),
props: computed(() => props.value)
props: computed(() => props.value),
showComponent
};
}

View File

@ -43,7 +43,7 @@
<NotificationBar />
<div class="fixed bottom-0 right-0 mr-10 mb-40">
<div class="fixed bottom-0 right-0 mr-10 mb-28">
<button
@click="showSidebarBalances"
class="px-9 h-[56px] bg-primary-blue-dark hover:bg-primary-blue-hover text-white rounded-[28px] text-lg font-semibold shadow flex items-center"

View File

@ -55,18 +55,20 @@
<div class="text-semibold text-sm text-grey-dark uppercase">
#{{ activeAccount.id }}
</div>
<div v-if="false" class="mt-3 text-2xl font-semibold">Account Name</div>
<div class="hidden mt-3 text-2xl font-semibold">Account Name</div>
</div>
<div>
<div class="font-medium text-lg">
Account address
</div>
<div class="mt-4 text-lg font-medium text-grey-dark flex items-center">
<div
class="mt-4 text-lg font-medium text-grey-dark flex items-center"
>
{{ activeAccount.address }}
<button
class="ml-3"
class="ml-3"
v-tooltip.bottom="tooltip"
v-clipboard:copy="activeAccount.address"
v-clipboard:success="onCopy"
@ -101,43 +103,108 @@
</div>
</div>
<div class="mt-12 hidden">
<h3 class="text-semibold text-sm text-primary-gray-dark uppercase">
Authorities
</h3>
<div class="mt-12">
<div class="flex justify-between items-center">
<h3 class="text-semibold text-sm text-primary-gray uppercase">
Authorities
</h3>
<ButtonCTA @click="addAuthority" class="h-8 w-52 text-xs uppercase font-semibold">
Add authority
</ButtonCTA>
</div>
<div
class="mt-4 p-8 border border-[#DBE5F4] rounded-[2px] grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-6"
>
<div
v-for="authority in authorities"
:key="authority"
class="border border-[#DBE5F4] text-[#9FB0C9] rounded-[2px] flex items-center px-6 py-7"
>
<div>
<svg
width="16"
height="18"
viewBox="0 0 16 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 9C10.525 9 12.5714 6.98555 12.5714 4.5C12.5714 2.01445 10.525 0 8 0C5.475 0 3.42857 2.01445 3.42857 4.5C3.42857 6.98555 5.475 9 8 9ZM11.4214 10.1461L9.71429 16.875L8.57143 12.0938L9.71429 10.125H6.28571L7.42857 12.0938L6.28571 16.875L4.57857 10.1461C2.03214 10.2656 0 12.3152 0 14.85V16.3125C0 17.2441 0.767857 18 1.71429 18H14.2857C15.2321 18 16 17.2441 16 16.3125V14.85C16 12.3152 13.9679 10.2656 11.4214 10.1461Z"
fill="#9FB0C9"
/>
</svg>
</div>
<a
:href="`${addressDetailsLink}/${authority}`"
target="_blank"
class="hover:underline flex-1 text-center font-medium text-lg text-primary-black truncate px-4"
>
{{ shortenHash(authority, 16) }}
</a>
<button @click="deleteAuthority(authority)">
<Icon name="trash" class="w-5"></Icon>
</button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, watch } from "@nuxtjs/composition-api";
import ButtonCTA from "~/components/common/input/ButtonCTA.vue";
import RemoveAuthorityDialog from "~/components/modal/authority/RemoveAuthorityDialog.vue";
import AddAuthorityDialog from "~/components/modal/authority/AddAuthorityDialog.vue";
import { useBalances } from "~/composables/useBalances";
import { useCopiedToClipboardUx } from "~/composables/useCopiedToClipboardUx";
import { useDSA } from "~/composables/useDSA";
import { useFormatting } from "~/composables/useFormatting";
import { useLink } from "~/composables/useLink";
import { useModal } from "~/composables/useModal";
export default defineComponent({
components: {},
components: { ButtonCTA },
setup() {
const {
accounts,
activeAccount,
setAccount,
createAccount,
creatingAccount
creatingAccount,
authorities
} = useDSA();
const { shortenHash } = useFormatting();
const { fetchBalances } = useBalances();
const { onCopy, tooltip, copied } = useCopiedToClipboardUx();
const { addressDetailsLink } = useLink();
const { showComponent } = useModal();
watch(activeAccount, val => val && fetchBalances(true));
const deleteAuthority = (authority: string) => {
showComponent(RemoveAuthorityDialog, { authority });
};
const addAuthority = () => {
showComponent(AddAuthorityDialog);
};
return {
addressDetailsLink,
shortenHash,
accounts,
activeAccount,
setAccount,
createAccount,
creatingAccount,
authorities,
onCopy,
tooltip,
copied
copied,
deleteAuthority,
addAuthority,
};
}
});