Skip to content

Commit

Permalink
feat: reference address/change address context in utxo, allow TagsSec…
Browse files Browse the repository at this point in the history
…tion to add/delete multiple correctly (#105)

* feat: reference address/change address context in utxo, allow TagsSection to add/delete multiple correctly

* feat: add transaction search

see #59
  • Loading branch information
KayBeSee authored Oct 26, 2022
1 parent 4be7105 commit 480b66c
Show file tree
Hide file tree
Showing 30 changed files with 561 additions and 332 deletions.
8 changes: 4 additions & 4 deletions apps/electron/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import {
promptpin,
sendpin,
createAddressTable,
addAddressLabel,
deleteAddressLabel,
addAddressTag,
deleteAddressTag,
getAllLabelsForAddress,
createTransactionTable,
addTransactionDescription,
Expand Down Expand Up @@ -693,7 +693,7 @@ ipcMain.handle('/add-address-label', async (event, args) => {
try {
const userDataPath = app.getPath('userData');
const db = await dbConnect(userDataPath);
const response = await addAddressLabel(db, address, label);
const response = await addAddressTag(db, address, label);
return Promise.resolve(response);
} catch (e) {
console.log(`error /add-address-label ${e}`);
Expand All @@ -706,7 +706,7 @@ ipcMain.handle('/delete-address-label', async (event, args) => {
try {
const userDataPath = app.getPath('userData');
const db = await dbConnect(userDataPath);
await deleteAddressLabel(db, id);
await deleteAddressTag(db, id);
return Promise.resolve();
} catch (e) {
console.log(`error /delete-address-label ${e}`);
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/components/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface Props {

export const Tabs = ({ currentTab, setCurrentTab, items }: Props) => {
return (
<div className='border-b border-gray-200 dark:border-gray-600'>
<div className='border-b border-gray-200 dark:border-gray-600 overflow-x-auto'>
<nav className='-mb-px flex space-x-8' data-cy='settings-tabs'>
{items.map(({ tabId, name }) => (
<button
Expand Down
50 changes: 46 additions & 4 deletions apps/frontend/src/context/AccountMapContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import {
accountMapReducer,
ACCOUNTMAP_UPDATE,
ACCOUNTMAP_SET,
ACCOUNT_TRANSACTION_UPDATE_DESCRIPTION
ACCOUNT_TRANSACTION_UPDATE_DESCRIPTION,
ACCOUNT_ADDRESS_ADD_TAG,
ACCOUNT_ADDRESS_DELETE_TAG
} from 'src/reducers/accountMap';

import { AccountMap, LilyAccount, LilyOnchainAccount } from '@lily/types';
import { AccountMap, AddressTag, LilyAccount, LilyOnchainAccount } from '@lily/types';

export const AccountMapContext = createContext({
setAccountMap: (accountMap: AccountMap) => {},
Expand All @@ -20,7 +22,9 @@ export const AccountMapContext = createContext({
account: LilyOnchainAccount,
txid: string,
description: string
) => {}
) => {},
addAddressTag: (accountId: string, address: string, tag: string) => {},
deleteAddressTag: (accountId: string, tag: AddressTag) => {}
});

export const AccountMapProvider = ({ children }: { children: React.ReactChild }) => {
Expand Down Expand Up @@ -75,13 +79,51 @@ export const AccountMapProvider = ({ children }: { children: React.ReactChild })
[dispatch]
);

const addAddressTag = useCallback(
async (accountId: string, address: string, tag: string) => {
const tagId = await platform.addAddressTag(address, tag);

const tagWithId: AddressTag = {
id: tagId,
address,
label: tag
};

dispatch({
type: ACCOUNT_ADDRESS_ADD_TAG,
payload: {
accountId,
tag: tagWithId
}
});
},
[dispatch]
);

const deleteAddressTag = useCallback(
async (accountId: string, tag: AddressTag) => {
await platform.deleteAddressTag(tag.id);

dispatch({
type: ACCOUNT_ADDRESS_DELETE_TAG,
payload: {
accountId,
tag
}
});
},
[dispatch]
);

const value = {
accountMap,
updateAccountMap,
setAccountMap,
currentAccount,
setCurrentAccountId,
updateTransactionDescription
updateTransactionDescription,
addAddressTag,
deleteAddressTag
};

return <AccountMapContext.Provider value={value}>{children}</AccountMapContext.Provider>;
Expand Down
18 changes: 7 additions & 11 deletions apps/frontend/src/frontend-middleware/BasePlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
DecoratedOpenStatusUpdate,
GenerateLightningInvoiceRequest,
LilyCloseChannelRequest,
AddressLabel,
AddressTag,
TransactionDescription
} from '@lily/types';

Expand Down Expand Up @@ -114,13 +114,9 @@ export interface PlatformInterface {

getWalletInfo(currentAccount: LilyAccount): Promise<WalletInfo>;

addAddressLabel(address: string, label: string): Promise<number>;
deleteAddressLabel(id: number): Promise<boolean>;
getAddressLabels(address: string): Promise<any[]>;

addAddressLabel(address: string, label: string): Promise<number>;
deleteAddressLabel(id: number): Promise<boolean>;
getAddressLabels(address: string): Promise<any[]>;
addAddressTag(address: string, label: string): Promise<number>;
deleteAddressTag(id: number): Promise<boolean>;
getAddressTags(address: string): Promise<AddressTag[]>;

addTransactionDescription(txid: string, description: string): Promise<boolean>;
getTransactionDescription(txid: string): Promise<TransactionDescription>;
Expand Down Expand Up @@ -236,9 +232,9 @@ export abstract class BasePlatform implements PlatformInterface {

abstract getWalletInfo(currentAccount: LilyAccount): Promise<WalletInfo>;

abstract addAddressLabel(address: string, label: string): Promise<number>;
abstract deleteAddressLabel(id: number): Promise<boolean>;
abstract getAddressLabels(address: string): Promise<AddressLabel[]>;
abstract addAddressTag(address: string, label: string): Promise<number>;
abstract deleteAddressTag(id: number): Promise<boolean>;
abstract getAddressTags(address: string): Promise<AddressTag[]>;

abstract addTransactionDescription(txid: string, description: string): Promise<boolean>;
abstract getTransactionDescription(txid: string): Promise<TransactionDescription>;
Expand Down
11 changes: 6 additions & 5 deletions apps/frontend/src/frontend-middleware/ElectronPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import {
OpenChannelRequestArgs,
GenerateLightningInvoiceRequest,
LilyAccount,
ICallback
ICallback,
AddressTag
} from '@lily/types';

export class ElectronPlatform extends BasePlatform {
Expand Down Expand Up @@ -313,23 +314,23 @@ export class ElectronPlatform extends BasePlatform {
return Promise.resolve(response);
}

async addAddressLabel(address: string, label: string) {
async addAddressTag(address: string, label: string) {
const response = await window.ipcRenderer.invoke('/add-address-label', {
address,
label
});
return Promise.resolve(response);
}

async deleteAddressLabel(id: number) {
async deleteAddressTag(id: number) {
const response = await window.ipcRenderer.invoke('/delete-address-label', {
id
});
return Promise.resolve(response);
}

async getAddressLabels(address: string) {
const response = await window.ipcRenderer.invoke('/get-address-labels', {
async getAddressTags(address: string) {
const response: AddressTag[] = await window.ipcRenderer.invoke('/get-address-labels', {
address
});
return Promise.resolve(response);
Expand Down
6 changes: 3 additions & 3 deletions apps/frontend/src/frontend-middleware/WebPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,15 +335,15 @@ export class WebPlatform extends BasePlatform {
}

// TODO: Implement these methods
async addAddressLabel(address: string, label: string) {
async addAddressTag(address: string, label: string) {
return Promise.resolve(1);
}

async deleteAddressLabel(id: number) {
async deleteAddressTag(id: number) {
return Promise.resolve(false);
}

async getAddressLabels(address: string) {
async getAddressTags(address: string) {
return Promise.resolve([]);
}

Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/pages/Receive/OnchainReceive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const OnchainReceive = ({ currentAccount }: Props) => {
</div>
</div>
<div className='col-span-4'>
<TagsSection addresses={[unusedAddresses[unusedAddressIndex].address]} />
<TagsSection addresses={[unusedAddresses[unusedAddressIndex]]} />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { useContext, useEffect, useState } from 'react';
import { useContext } from 'react';

import { Unit } from 'src/components';
import { LabelTag } from 'src/pages/Vault/Settings/Addresses/LabelTag';

import { PlatformContext } from 'src/context';
import { AccountMapContext } from 'src/context';

import { classNames } from 'src/utils/other';
import { createMap } from 'src/utils/accountMap';

import { UTXO, AddressLabel } from '@lily/types';
import { UTXO, LilyOnchainAccount } from '@lily/types';

interface Props {
id: string;
Expand All @@ -18,16 +19,10 @@ interface Props {
}

export const UtxoInputSelectRow = ({ id, isSelected, utxo, handleChange, showTags }: Props) => {
const [labels, setLabels] = useState<AddressLabel[]>([]);
const { platform } = useContext(PlatformContext);

useEffect(() => {
const retrieveLabels = async () => {
const retrievedLabels = await platform.getAddressLabels(utxo.address.address);
setLabels(retrievedLabels);
};
retrieveLabels();
}, []);
const {currentAccount } = useContext(AccountMapContext)
const { addresses, changeAddresses } = currentAccount as LilyOnchainAccount;
const addressMap = createMap([...addresses, ...changeAddresses], 'address')
const utxoAddress = addressMap[utxo.address.address]

return (
<label
Expand Down Expand Up @@ -69,8 +64,8 @@ export const UtxoInputSelectRow = ({ id, isSelected, utxo, handleChange, showTag
role='list'
className='mt-2 inline-flex leading-8 space-x-1 items-center justify-end flex-wrap'
>
{labels.length ? (
labels.map((label) => (
{utxoAddress.tags.length ? (
utxoAddress.tags.map((label) => (
<li className='inline' key={label.id}>
<LabelTag label={label} />
</li>
Expand Down
42 changes: 16 additions & 26 deletions apps/frontend/src/pages/Send/components/SelectInputsForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';

import { Unit } from 'src/components';
import { useSelected, useShiftSelected } from 'src/hocs';

import { UtxoInputSelectRow } from './UtxoInputSelectRow';
import { SearchToolbar } from './SearchToolbar';

import { PlatformContext } from 'src/context';
import { LilyOnchainAccount, UTXO } from '@lily/types';
import { createMap } from 'src/utils/accountMap';
import { normalizedIncludes } from 'src/utils/other';

export type SortOptions = 'asc' | 'desc' | null;

Expand All @@ -18,15 +19,8 @@ interface Props {
requiredSendAmount: number;
}

async function filter(arr, callback) {
const fail = Symbol();
return (
await Promise.all(arr.map(async (item) => ((await callback(item)) ? item : fail)))
).filter((i) => i !== fail);
}

export const SelectInputsForm = ({ currentAccount, onSave, cancel, requiredSendAmount }: Props) => {
const { availableUtxos } = currentAccount;
const { availableUtxos, addresses, changeAddresses } = currentAccount;

const { selected, change } = useSelected<UTXO>([]);
const { onChange, resetShiftSelectSelections } = useShiftSelected(availableUtxos, change);
Expand All @@ -35,28 +29,24 @@ export const SelectInputsForm = ({ currentAccount, onSave, cancel, requiredSendA
const [sort, setSort] = useState<SortOptions>(null);
const [searchQuery, setSearchQuery] = useState('');
const [filteredUtxos, setFilteredUtxos] = useState(availableUtxos);
const { platform } = useContext(PlatformContext);

useEffect(() => {
const filterUtxos = async () => {
const currentFilteredUtxos = await filter([...availableUtxos], async (utxo) => {
const retrievedLabels = await platform.getAddressLabels(utxo.address.address);
const currentFilteredUtxos = availableUtxos.filter((utxo) => {
const addressMatch = normalizedIncludes(utxo.address.address, searchQuery);
const txIdMatch = normalizedIncludes(utxo.txid, searchQuery);

const normalizedSearchQuery = searchQuery.toLowerCase();
const addressMap = createMap([...addresses, ...changeAddresses], 'address');
const utxoAddress = addressMap[utxo.address.address];

const labelMatch = retrievedLabels.some((label) =>
label.label.toLowerCase().includes(normalizedSearchQuery)
);
const addressMatch = utxo.address.address.includes(normalizedSearchQuery);
const txIdMatch = utxo.txid.includes(normalizedSearchQuery);
const labelMatch = utxoAddress.tags.some((tag) =>
tag.label.toLowerCase().includes(searchQuery)
);

return labelMatch || addressMatch || txIdMatch;
});
return labelMatch || addressMatch || txIdMatch;
});

setFilteredUtxos(currentFilteredUtxos);
resetShiftSelectSelections(currentFilteredUtxos);
};
filterUtxos();
setFilteredUtxos(currentFilteredUtxos);
resetShiftSelectSelections(currentFilteredUtxos);
}, [searchQuery]);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import DeadFlowerImage from 'src/assets/dead-flower.svg';

const NoFilteredTransactionsEmptyState = () => (
<div className='my-16 h-96 w-full bg-white dark:bg-gray-800 space-y-6 flex flex-col items-center justify-center rounded-2xl shadow dark:highlight-white/10'>
<h3 className='text-2xl text-gray-600 dark:text-gray-300 font-medium'>No Transactions</h3>
<img className='text-gray-600 w-24 h-24' src={DeadFlowerImage} />
<p className='text-gray-600 dark:text-gray-400 font-sm font-medium'>
No transactions match the search criteria.
</p>
</div>
);

export default NoFilteredTransactionsEmptyState;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import DeadFlowerImage from 'src/assets/dead-flower.svg';

const NoTransactionsEmptyState = () => (
<div className='my-16 h-96 w-full bg-white dark:bg-gray-800 space-y-6 flex flex-col items-center justify-center rounded-2xl shadow dark:highlight-white/10'>
<h3 className='text-2xl text-gray-600 dark:text-gray-300 font-medium'>No Transactions</h3>
<img className='text-gray-600 w-24 h-24' src={DeadFlowerImage} />
<p className='text-gray-600 dark:text-gray-400 font-sm font-medium'>
No activity has been detected on this account yet.
</p>
</div>
);

export default NoTransactionsEmptyState;
Loading

0 comments on commit 480b66c

Please sign in to comment.