Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add clear history button #546

Merged
merged 11 commits into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 48 additions & 8 deletions frontend/src/components/AccountSentTable.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,47 @@
<template>
apbendi marked this conversation as resolved.
Show resolved Hide resolved
<div>
apbendi marked this conversation as resolved.
Show resolved Hide resolved
<div class="text-caption q-mb-sm">
{{ $t('AccountSentTable.stored-on-device') }}.
<router-link
class="cursor-pointer hyperlink"
:to="{ name: 'FAQ', hash: '#why-cant-I-see-my-send-history-on-different-devices' }"
>{{ $t('AccountSentTable.learn-more') }}</router-link
>.
<div class="flex row justify-between q-mb-sm">
<div class="text-caption self-end">
{{ $t('AccountSentTable.stored-on-device') }}.
<router-link
class="cursor-pointer hyperlink"
:to="{ name: 'FAQ', hash: '#why-cant-I-see-my-send-history-on-different-devices' }"
>{{ $t('AccountSentTable.learn-more') }}</router-link
>.
</div>
<div class="flex row items-center" v-if="formattedSendMetadata.length > 0">
<base-button
size="sm"
@click="showClearHistoryWarning = true"
icon="fa fa-trash"
:flat="true"
:label="$t('AccountSent.clear-history')"
/>
</div>
</div>
apbendi marked this conversation as resolved.
Show resolved Hide resolved
<q-dialog v-model="showClearHistoryWarning">
<q-card class="row justify-center q-my-none q-py-none border-top-thick">
<q-card-section>
<h5 class="text-bold text-center q-mt-none">
<q-icon name="fas fa-exclamation-triangle" color="warning" left /> {{ $t('Utils.Dialog.warning') }}
</h5>
</q-card-section>
<q-card-section>
<div v-html="$t('AccountSentTable.clear-history-warning')" />
</q-card-section>
<q-card-section class="q-pt-sm">
<div class="row justify-evenly">
<base-button
class="q-mr-sm"
:outline="true"
@click="showClearHistoryWarning = false"
:label="$t('AccountSentTable.cancel')"
/>
<base-button type="submit" @click="clearHistory()" :label="$t('AccountSentTable.clear-history')" />
</div>
</q-card-section>
</q-card>
</q-dialog>
<q-table
:grid="$q.screen.xs"
card-container-class="col q-col-gutter-md"
Expand Down Expand Up @@ -123,7 +157,7 @@
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { defineComponent, PropType, ref } from 'vue';
import { SendTableMetadataRow } from 'components/models';
import BaseTooltip from 'src/components/BaseTooltip.vue';
import useWalletStore from 'src/store/wallet';
Expand All @@ -138,10 +172,15 @@ export default defineComponent({
type: undefined as unknown as PropType<SendTableMetadataRow[]>,
required: true,
},
clearHistory: {
type: Function,
required: true,
},
},
setup(props, context) {
const { provider, chainId } = useWalletStore();
const paginationConfig = { rowsPerPage: 25 };
const showClearHistoryWarning = ref(false);
const mainTableColumns = [
{
align: 'left',
Expand Down Expand Up @@ -174,6 +213,7 @@ export default defineComponent({
openInEtherscan,
provider,
chainId,
showClearHistoryWarning,
};
},
});
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@
"account-empty": "No funds sent in this browser",
"advanced-mode-on": "Sent with advanced mode enabled",
"amount": "Amount",
"cancel": "Cancel",
"clear-history": "Clear History",
"clear-history-warning": "This action cannot be undone! Your encrypted send history is only stored locally on the device that <b>sent the transaction and cannot be recovered once cleared</b>. Are you sure you want to continue?",
"date-sent": "Date sent",
"learn-more": "Learn more",
"receiver": "Receiver",
Expand All @@ -321,6 +324,7 @@
"use-public-key-checked": "Sent using recipient's standard public key"
alexkeating marked this conversation as resolved.
Show resolved Hide resolved
},
"AccountSent": {
"clear-history": "Clear history",
"connect-wallet": "Connect Wallet",
"connect-your-wallet": "Connect your wallet to view sent funds",
"fetching-send-history": "Fetching send history...",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@
"account-empty": "此浏览器中未发送任何资金",
"advanced-mode-on": "有启用高级模式发送",
"amount": "金额",
"cancel": "取消",
"clear-history": "清除历史记录",
"clear-history-warning": "此操作无法撤消!您的加密发送历史记录仅存储在发送交易的本地设备上,一旦清除就无法恢复。您确定要继续吗?",
"date-sent": "发送日期",
"learn-more": "了解更多信息",
"receiver": "收款人",
Expand All @@ -322,6 +325,7 @@
},

"AccountSent": {
"clear-history": "清除历史记录",
"connect-wallet": "连接钱包",
"connect-your-wallet": "连接您的钱包来查看已发送的资金",
"fetching-send-history": "正在获取发送历史记录...",
Expand Down
14 changes: 11 additions & 3 deletions frontend/src/pages/AccountSent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
<loading-spinner />
<div class="text-center text-italic">{{ $t('AccountSent.fetching-send-history') }}</div>
</div>
<div v-else-if="!needsSignature && !dataLoading" class="q-mx-auto" style="max-width: 800px">
<account-sent-table :sendMetadata="sendMetadata" />
<div v-else-if="!needsSignature && !dataLoading" class="q-mx-auto flex column" style="max-width: 800px">
<account-sent-table :sendMetadata="sendMetadata" :clearHistory="clearHistory" />
</div>
</div>
</q-page>
Expand All @@ -34,7 +34,7 @@ import { SendTableMetadataRow } from 'components/models';
import { BigNumber } from 'src/utils/ethers';
import { formatNameOrAddress } from 'src/utils/address';
import { formatDate, formatAmount, formatTime, getTokenSymbol, getTokenLogoUri } from 'src/utils/utils';
import { fetchAccountSends } from 'src/utils/account-send';
import { clearAccountSend, fetchAccountSends } from 'src/utils/account-send';

function useAccountSent() {
const { tokens, userAddress, chainId, viewingKeyPair, getPrivateKeys } = useWalletStore();
Expand Down Expand Up @@ -79,6 +79,13 @@ function useAccountSent() {
dataLoading.value = false;
};

const clearHistory = async () => {
dataLoading.value = true;
await clearAccountSend(userAddress.value!, chainId.value!);
sendMetadata.value = [];
dataLoading.value = false;
};

onMounted(async () => {
if (!needsSignature.value) {
await getData();
Expand All @@ -89,6 +96,7 @@ function useAccountSent() {
userAddress,
sendMetadata,
getData,
clearHistory,
viewingPrivateKey,
needsSignature,
dataLoading,
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/utils/account-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,9 @@ export const fetchAccountSends = async ({ address, viewingPrivateKey, chainId }:
});
return accountData.reverse();
};

export const clearAccountSend = async (address: string, chainId: number) => {
const localStorageKey = `${LOCALFORAGE_ACCOUNT_SEND_KEY_PREFIX}-${address}-${chainId}`;
const localStorageCountKey = `${LOCALFORAGE_ACCOUNT_SEND_KEY_PREFIX}-count-${address}-${chainId}`;
await Promise.all([localforage.removeItem(localStorageKey), localforage.removeItem(localStorageCountKey)]);
};
113 changes: 79 additions & 34 deletions frontend/test/account-send.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RandomNumber } from '@umbracash/umbra-js';

import {
buildAccountDataForEncryption,
clearAccountSend,
decryptData,
encryptAccountData,
fetchAccountSends,
Expand All @@ -24,6 +25,23 @@ jest.mock('src/utils/constants', () => ({
...jest.requireActual('src/utils/constants'),
MAINNET_PROVIDER: jest.fn(),
}));
window.logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
version: '',
_log: jest.fn(),
makeError: jest.fn(),
assert: jest.fn(),
assertArgument: jest.fn(),
checkNormalize: jest.fn(),
checkArgumentCount: jest.fn(),
checkNew: jest.fn(),
checkAbstract: jest.fn(),
checkSafeUint53: jest.fn(),
throwError: jest.fn() as never,
throwArgumentError: jest.fn() as never,
};

const NUM_RUNS = 100;

Expand Down Expand Up @@ -604,23 +622,6 @@ describe('storeSend', () => {
describe('fetchAccountSends', () => {
beforeEach(async () => {
await localforage.clear();
window.logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
version: '',
_log: jest.fn(),
makeError: jest.fn(),
assert: jest.fn(),
assertArgument: jest.fn(),
checkNormalize: jest.fn(),
checkArgumentCount: jest.fn(),
checkNew: jest.fn(),
checkAbstract: jest.fn(),
checkSafeUint53: jest.fn(),
throwError: jest.fn() as never,
throwArgumentError: jest.fn() as never,
};
});

it('Correctly fetch send data when there is a single send', async () => {
Expand Down Expand Up @@ -676,23 +677,6 @@ describe('fetchAccountSends', () => {
describe('End to end account tests', () => {
beforeEach(async () => {
await localforage.clear();
window.logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
version: '',
_log: jest.fn(),
makeError: jest.fn(),
assert: jest.fn(),
assertArgument: jest.fn(),
checkNormalize: jest.fn(),
checkArgumentCount: jest.fn(),
checkNew: jest.fn(),
checkAbstract: jest.fn(),
checkSafeUint53: jest.fn(),
throwError: jest.fn() as never,
throwArgumentError: jest.fn() as never,
};
});

it.each([randomInt(2, 10), randomInt(2, 10), randomInt(2, 10), randomInt(2, 10)])(
Expand Down Expand Up @@ -781,3 +765,64 @@ describe('End to end account tests', () => {
20000
);
});

describe('clearHistory', () => {
beforeEach(async () => {
await localforage.clear();
});

it.each([
randomInt(0, 50),
randomInt(0, 50),
randomInt(0, 50),
randomInt(0, 50),
randomInt(0, 50),
randomInt(0, 50),
randomInt(0, 50),
randomInt(0, 50),
])(
"Clear account send history '%s'",
async (num) => {
const sends = createAccountSend(num);

// Offset the account sends based on when local storage was cleared
for (const [, value] of sends.accountSends.entries()) {
const {
amount,
tokenAddress,
txHash,
recipientAddress: randomRecipientAddress,
advancedMode,
usePublicKeyChecked,
pubKey,
} = value;

const storeSendArgs = {
unencryptedAccountSendData: {
amount: amount,
tokenAddress,
txHash,
// Sender address needs to be static because it is part of the key used
// to fetch data from localforage.
senderAddress: recipientAddress,
},
accountDataToEncrypt: {
recipientAddress: randomRecipientAddress,
advancedMode,
usePublicKeyChecked,
pubKey,
},
};

await storeSend(5, viewingPrivateKey, storeSendArgs);
}
await clearAccountSend(recipientAddress, 5);

const existingCount = await localforage.getItem(localStorageCountKey);
const value = await localforage.getItem(localStorageValueKey);
expect(existingCount).toEqual(null);
expect(value).toEqual(null);
},
60000
);
});