Skip to content

Commit

Permalink
feat: account selector improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandros Kalogerakis committed Nov 5, 2024
1 parent f0a96b3 commit 18d666e
Show file tree
Hide file tree
Showing 13 changed files with 422 additions and 44 deletions.
216 changes: 216 additions & 0 deletions src/composables/accountSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import { computed, ref, watch } from 'vue';

import {
IAddressBookEntry,
type ICommonTransaction,
type ITransaction,
} from '@/types';
import {
ACCOUNT_SELECT_TYPE_FILTER,
AccountSelectTypeFilter,
PROTOCOLS,
TX_DIRECTION,
} from '@/constants';
import { createCustomScopedComposable, getDefaultAccountLabel, pipe } from '@/utils';
import { useAccounts, useAddressBook, useLatestTransactionList } from '@/composables';

import { useAeMiddleware } from '@/protocols/aeternity/composables';
import { useAeNames } from '@/protocols/aeternity/composables/aeNames';
import {
getInnerTransaction,
getOwnershipStatus,
getTxDirection,
getTxOwnerAddress,
} from '@/protocols/aeternity/helpers';
import { AE_TRANSACTION_OWNERSHIP_STATUS } from '@/protocols/aeternity/config';

export const useAccountSelector = createCustomScopedComposable(() => {
const { getName } = useAeNames();
const { getMiddleware } = useAeMiddleware();
const latestTransactions = ref<IAddressBookEntry[]>([]);
const searchQuery = ref<string>('');
const {
addressBookFiltered,
addressBookFilteredByProtocol,
protocolFilter,
showBookmarked,
getAddressBookEntryByAddress,
setProtocolFilter,
setShowBookmarked,
clearFilters: addressBookClearFilters,
} = useAddressBook();
const {
accounts,
activeAccount,
accountsGroupedByProtocol,
getAccountByAddress,
} = useAccounts();
const { accountsTransactionsLatest } = useLatestTransactionList();
const accountSelectType = ref<AccountSelectTypeFilter>(ACCOUNT_SELECT_TYPE_FILTER.addressBook);
const prevAccountSelectType = ref<AccountSelectTypeFilter>(accountSelectType.value);
const ownAddresses = computed(() => {
if (protocolFilter.value) {
return accountsGroupedByProtocol.value[protocolFilter.value]?.map((account) => (
{
name: getName(account.address).value || getDefaultAccountLabel(account),
address: account.address,
isBookmarked: false,
protocol: protocolFilter.value ?? PROTOCOLS.aeternity,
isOwnAddress: true,
type: account.type,
}
));
}
return [];
});
const accountsFilteredByType = computed(
() => {
switch (accountSelectType.value) {
case ACCOUNT_SELECT_TYPE_FILTER.bookmarked:
return addressBookFiltered.value;
case ACCOUNT_SELECT_TYPE_FILTER.addressBook:
return addressBookFiltered.value;
case ACCOUNT_SELECT_TYPE_FILTER.recent:
return latestTransactions.value;
case ACCOUNT_SELECT_TYPE_FILTER.owned:
return ownAddresses.value;
case ACCOUNT_SELECT_TYPE_FILTER.all:
return [...(addressBookFiltered.value ?? []), ...(ownAddresses.value ?? [])];
default:
return [];
}
},
);
function filterAccountsBookBySearchPhrase(entries: IAddressBookEntry[]) {
const searchQueryLower = searchQuery.value.toLowerCase();
return entries.filter(({ name, address }) => (
[name, address].some((val) => val.toLowerCase().includes(searchQueryLower))
));
}
function sortAccounts(entries: IAddressBookEntry[]) {
return entries.sort((a, b) => {
if (a === b) return 0;
return a ? 1 : -1;
});
}
function removeDuplicates(entries: IAddressBookEntry[]) {
return entries.filter(
(addr1, i, addresses) => addresses.findIndex(
(addr2) => addr2.address === addr1.address,
) === i,
);
}
const accountsFiltered = computed(
() => pipe([
filterAccountsBookBySearchPhrase,
sortAccounts,
removeDuplicates,
])(accountsFilteredByType.value ?? []),
);

function setAccountSelectType(type: AccountSelectTypeFilter, resetProtocolFilter = false) {
accountSelectType.value = type;
setShowBookmarked(type === ACCOUNT_SELECT_TYPE_FILTER.bookmarked, resetProtocolFilter);
}
function clearFilters(resetProtocolFilter = false) {
accountSelectType.value = ACCOUNT_SELECT_TYPE_FILTER.addressBook;
addressBookClearFilters(resetProtocolFilter);
}

watch(
() => [protocolFilter.value],
async () => {
if (protocolFilter.value) {
setAccountSelectType(ACCOUNT_SELECT_TYPE_FILTER.addressBook);
}
},
);
watch(
() => [accountsTransactionsLatest.value, activeAccount.value.address],
async () => {
const filteredTransactions = (accountsTransactionsLatest
.value[activeAccount.value.address] || []).filter(
(transaction: ICommonTransaction) => {
const outerTx = transaction.tx!;
const innerTx = transaction.tx ? getInnerTransaction(transaction.tx) : null;
const txOwnerAddress = getTxOwnerAddress(innerTx);

const direction = getTxDirection(
outerTx?.payerId ? outerTx : innerTx,
(transaction as ITransaction).transactionOwner
|| (
(
getOwnershipStatus(activeAccount.value, accounts.value, innerTx)
!== AE_TRANSACTION_OWNERSHIP_STATUS.current
)
&& txOwnerAddress
)
|| activeAccount.value.address,
);

return (
direction === TX_DIRECTION.sent
&& (outerTx?.payerId ? outerTx : innerTx).recipientId
);
},
);

latestTransactions.value = await Promise.all(
filteredTransactions
.map(async (transaction: ICommonTransaction): Promise<IAddressBookEntry> => {
const outerTx = transaction.tx!;
const innerTx = transaction.tx ? getInnerTransaction(transaction.tx) : null;
const { recipientId } = outerTx?.payerId ? outerTx : innerTx;
const middleware = await getMiddleware();

let address = recipientId;
const accountFound = getAccountByAddress(recipientId!);
let name = getName(accountFound?.address).value
|| getDefaultAccountLabel(accountFound);
if (recipientId?.startsWith('nm_')) {
address = (await middleware.getName(recipientId)).name;
name = address;
}
const addressBookEntryByAddress = getAddressBookEntryByAddress(address);
if (addressBookEntryByAddress) {
return addressBookEntryByAddress;
}
return {
name,
address,
isBookmarked: false,
protocol: protocolFilter.value ?? PROTOCOLS.aeternity,
};
}),
);
},
{ immediate: true }, // Run immediately on initialization
);

// Storing the previous type in order to revert to it when the input is cleared
let savedPrevAccountSelectType = false;
watch(searchQuery, (newSearch) => {
if (newSearch !== '' && !savedPrevAccountSelectType) {
savedPrevAccountSelectType = true;
prevAccountSelectType.value = accountSelectType.value;
accountSelectType.value = ACCOUNT_SELECT_TYPE_FILTER.all;
} else if (newSearch === '') {
savedPrevAccountSelectType = false;
accountSelectType.value = prevAccountSelectType.value;
}
});

return {
accountSelectType,
accountsFiltered,
addressBookFilteredByProtocol,
protocolFilter,
showBookmarked,
searchQuery,

setAccountSelectType,
setProtocolFilter,
setShowBookmarked,
clearFilters,
};
});
1 change: 1 addition & 0 deletions src/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ export * from './walletConnect';
export * from './airGap';
export * from './addressBook';
export * from './addressBookEntryForm';
export * from './accountSelector';
11 changes: 10 additions & 1 deletion src/constants/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ICurrency, IPermission } from '@/types';
import type { ICurrency, IPermission, ObjectValues } from '@/types';
import { IS_MOBILE_APP } from './environment';

export const APP_NAME = 'Superhero Wallet';
Expand Down Expand Up @@ -503,3 +503,12 @@ export const PASSWORD_STRENGTH = {
medium: 'medium',
strong: 'strong',
} as const;

export const ACCOUNT_SELECT_TYPE_FILTER = {
all: 'all',
bookmarked: 'bookmarked',
owned: 'owned',
addressBook: 'addressBook',
recent: 'recent',
} as const;
export type AccountSelectTypeFilter = ObjectValues<typeof ACCOUNT_SELECT_TYPE_FILTER>;
4 changes: 4 additions & 0 deletions src/icons/union.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 57 additions & 10 deletions src/popup/components/AddressBook/AddressBookFilters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,66 @@
<div class="address-book-filters">
<!-- All/Bookmarked Filters -->
<BtnFilter
:is-active="!showBookmarked && (!protocolFilter || isSelector)"
v-if="!isSelector"
:is-active="(
accountSelectType === ACCOUNT_SELECT_TYPE_FILTER.addressBook
&& (!protocolFilter || isSelector)
)"
:text="$t('common.all')"
data-cy="all-filter"
@click="clearFilters(!isSelector)"
/>

<BtnFilter
:is-active="showBookmarked"
v-if="hasBookmarkedEntries || !isSelector"
:is-active="accountSelectType === ACCOUNT_SELECT_TYPE_FILTER.bookmarked"
data-cy="bookmarked-filter"
@click="() => setShowBookmarked(true, !isSelector)"
@click="() => setAccountSelectType(ACCOUNT_SELECT_TYPE_FILTER.bookmarked, !isSelector)"
>
<IconWrapper
:icon="FavoritesIcon"
icon-size="rg"
/>
</BtnFilter>

<template v-if="isSelector">
<BtnFilter
:is-active="(
accountSelectType === ACCOUNT_SELECT_TYPE_FILTER.owned
&& (!protocolFilter || isSelector)
)"
:text="'Own'"
data-cy="own-filter"
@click="setAccountSelectType(ACCOUNT_SELECT_TYPE_FILTER.owned, !isSelector)"
/>

<BtnFilter
:is-active="(
accountSelectType === ACCOUNT_SELECT_TYPE_FILTER.addressBook
&& (!protocolFilter || isSelector)
)"
:text="'Address book'"
data-cy="address-book-filter"
@click="setAccountSelectType(ACCOUNT_SELECT_TYPE_FILTER.addressBook, !isSelector)"
/>

<BtnFilter
:is-active="(
accountSelectType === ACCOUNT_SELECT_TYPE_FILTER.recent
&& (!protocolFilter || isSelector)
)"
:text="'Recent'"
data-cy="recent-filter"
@click="setAccountSelectType(ACCOUNT_SELECT_TYPE_FILTER.recent, !isSelector)"
>
<IconWrapper
:icon="UnionIcon"
icon-size="rg"
/>
Recent
</BtnFilter>
</template>

<template v-if="!isSelector">
<div class="divider" />

Expand All @@ -45,13 +88,14 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { PROTOCOL_LIST } from '@/constants';
import { useAddressBook } from '@/composables';
import { ACCOUNT_SELECT_TYPE_FILTER, PROTOCOL_LIST } from '@/constants';
import { useAccountSelector } from '@/composables';
import BtnFilter from '@/popup/components/buttons/BtnFilter.vue';
import IconWrapper from '@/popup/components/IconWrapper.vue';
import FavoritesIcon from '@/icons/star-full.svg?vue-component';
import UnionIcon from '@/icons/union.svg?vue-component';
import HorizontalScroll from '../HorizontalScroll.vue';
export default defineComponent({
Expand All @@ -62,26 +106,29 @@ export default defineComponent({
},
props: {
isSelector: Boolean,
hasBookmarkedEntries: Boolean,
},
setup() {
const {
accountSelectType,
protocolFilter,
showBookmarked,
setAccountSelectType,
setProtocolFilter,
setShowBookmarked,
clearFilters,
} = useAddressBook();
} = useAccountSelector();
return {
accountSelectType,
protocolFilter,
showBookmarked,
setAccountSelectType,
setProtocolFilter,
setShowBookmarked,
clearFilters,
FavoritesIcon,
UnionIcon,
PROTOCOL_LIST,
ACCOUNT_SELECT_TYPE_FILTER,
};
},
});
Expand Down
Loading

0 comments on commit 18d666e

Please sign in to comment.