diff --git a/CHANGELOG.md b/CHANGELOG.md index 34eaab466..ef888e0e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## **0.3.0** Boggart đź‘» + +### Feature: +- USDC engine integration +- New withdraw component + +### Enhancement: +- Implements RCN API v6 +- Improves dialog approve UI +- Estimate Fee amount + +### Fix: +- Fix "go back" button behavior +- Fix withdraw icon in loan history + ## **0.2.7** Boggart đź‘» ### Feature: diff --git a/package-lock.json b/package-lock.json index 1c8b04bf5..81e2e6c77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "rcn-angular-dapp", - "version": "0.2.7", + "version": "0.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2750,9 +2750,9 @@ } }, "abab": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.2.tgz", - "integrity": "sha512-2scffjvioEmNz0OyDSLGWDfKCVwaKc6l9Pm9kOIREU13ClXZvHpg/nRL5xyjSSSLhOnXqft2HpsAzNEEA8cFFg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", "dev": true }, "abbrev": { @@ -4027,9 +4027,9 @@ } }, "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, "browser-resolve": { @@ -7346,12 +7346,12 @@ } }, "fb-watchman": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", - "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", "dev": true, "requires": { - "bser": "^2.0.0" + "bser": "2.1.1" } }, "fd-slicer": { @@ -11793,9 +11793,9 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, "parse5": { @@ -13555,9 +13555,9 @@ } }, "nwsapi": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", - "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, "oargv": { @@ -15262,21 +15262,21 @@ } }, "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", "dev": true, "requires": { - "lodash": "^4.17.11" + "lodash": "^4.17.19" } }, "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", "dev": true, "requires": { - "request-promise-core": "1.1.2", + "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" } @@ -18276,12 +18276,12 @@ "dev": true }, "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", "dev": true, "requires": { - "browser-process-hrtime": "^0.1.2" + "browser-process-hrtime": "^1.0.0" } }, "walker": { diff --git a/package.json b/package.json index c980ef8e9..e8d0ef6f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rcn-angular-dapp", - "version": "0.2.7", + "version": "0.3.0", "version_name": "Boggart", "license": "MIT", "scripts": { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index f2bcc35e0..7eef0dcc3 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -3,7 +3,7 @@ import { Router, NavigationEnd } from '@angular/router'; import { MatDialog } from '@angular/material'; import { environment } from '../environments/environment'; // App services -import { ApiService } from './services/api.service'; +import { ProxyApiService } from './services/proxy-api.service'; import { EventsService } from './services/events.service'; import { WalletConnectService } from './services/wallet-connect.service'; // App component @@ -20,7 +20,7 @@ export class AppComponent implements OnInit { constructor( private router: Router, private dialog: MatDialog, - private apiService: ApiService, + private proxyApiService: ProxyApiService, private eventsService: EventsService, private walletConnectService: WalletConnectService ) {} @@ -66,8 +66,12 @@ export class AppComponent implements OnInit { } private async checkApiHealth() { - const synchronized: boolean = await this.apiService.isSynchronized(); - if (!synchronized) { + const { last_block: lastBlock, current_block: currentBlock } = + await this.proxyApiService.getApiStatus(); + const ALLOWABLE_BLOCK_DIFFERENCE = 6; + const blockDiff = currentBlock - lastBlock; + const isSynchronized: boolean = blockDiff <= ALLOWABLE_BLOCK_DIFFERENCE; + if (!isSynchronized) { this.dialog.open(DialogApiSyncComponent); } } diff --git a/src/app/core/balance/balance.component.html b/src/app/core/balance/balance.component.html index 0a0793a58..2253e6faa 100644 --- a/src/app/core/balance/balance.component.html +++ b/src/app/core/balance/balance.component.html @@ -1,10 +1,11 @@ +
@@ -16,10 +17,10 @@
- {{ displayAvailable || 0 }} + {{ usdcDisplayAvailable || 0 }}
- RCN + USDC
diff --git a/src/app/core/balance/balance.component.spec.ts b/src/app/core/balance/balance.component.spec.ts index 07301037d..6e157d084 100644 --- a/src/app/core/balance/balance.component.spec.ts +++ b/src/app/core/balance/balance.component.spec.ts @@ -31,16 +31,16 @@ describe('BalanceComponent', () => { it('should display balance', () => { // set balance amount - component.diasporeLoansWithBalance = [3000]; - component.rcnAvailable = 3000; + component.usdcLoansWithBalance = [3000]; + component.usdcAvailable = 3000; // update template component.updateDisplay(); fixture.detectChanges(); // logic expect - expect(component.displayAvailable).toBe('3,000.00'); - expect(component.canWithdraw).toBeTruthy(); + expect(component.usdcDisplayAvailable).toBe('3,000.00'); + expect(component.usdcCanWithdraw).toBeTruthy(); // ui expect const withdrawPayments = readComponent(fixture, '.balance-withdraw__amount'); diff --git a/src/app/core/balance/balance.component.ts b/src/app/core/balance/balance.component.ts index 23520bed7..c14f3190c 100644 --- a/src/app/core/balance/balance.component.ts +++ b/src/app/core/balance/balance.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit, OnChanges, OnDestroy, Input } from '@angular/core'; import { Subscription } from 'rxjs'; import { environment } from '../../../environments/environment'; +import { Engine } from '../../models/loan.model'; import { Utils } from '../../utils/utils'; -// App Services import { Web3Service } from '../../services/web3.service'; import { EventsService } from '../../services/events.service'; import { ContractsService } from '../../services/contracts.service'; @@ -16,13 +16,12 @@ import { Tx, Type, TxService } from '../../services/tx.service'; export class BalanceComponent implements OnInit, OnChanges, OnDestroy { @Input() account: string; - rcnAvailable: number; - diasporeLoansWithBalance: number[] = []; - ongoingDiasporeWithdraw: Tx; - - canWithdraw = false; - displayAvailable = ''; - txSubscription: boolean; + usdcAvailable: number; + usdcLoansWithBalance: number[] = []; + usdcOngoingWithdraw: Tx; + usdcCanWithdraw = false; + usdcDisplayAvailable = ''; + usdcTxSubscription: boolean; // subscriptions subscriptionBalance: Subscription; @@ -53,7 +52,7 @@ export class BalanceComponent implements OnInit, OnChanges, OnDestroy { if (this.subscriptionBalance) { this.subscriptionBalance.unsubscribe(); } - if (this.txSubscription) { + if (this.usdcTxSubscription) { this.txService.unsubscribeConfirmedTx(async (tx: Tx) => this.trackWithdrawTx(tx)); } } @@ -71,16 +70,16 @@ export class BalanceComponent implements OnInit, OnChanges, OnDestroy { * Update balance and withdraw amount */ updateDisplay() { - if (this.rcnAvailable) { - this.displayAvailable = Utils.formatAmount(this.rcnAvailable); + if (this.usdcAvailable) { + this.usdcDisplayAvailable = Utils.formatAmount(this.usdcAvailable); } else { - this.displayAvailable = '0'; + this.usdcDisplayAvailable = '0'; } - this.canWithdraw = - this.diasporeLoansWithBalance !== undefined && - this.diasporeLoansWithBalance.length > 0 && - this.ongoingDiasporeWithdraw === undefined; + this.usdcCanWithdraw = + this.usdcLoansWithBalance !== undefined && + this.usdcLoansWithBalance.length > 0 && + this.usdcOngoingWithdraw === undefined; } /** @@ -88,9 +87,9 @@ export class BalanceComponent implements OnInit, OnChanges, OnDestroy { * total available */ async loadWithdrawBalance() { - const pendingWithdraws = await this.contractService.getPendingWithdraws(); - this.rcnAvailable = pendingWithdraws[2] / 10 ** 18; - this.diasporeLoansWithBalance = pendingWithdraws[3]; + const pendingWithdraws = await this.contractService.getPendingWithdraws(Engine.UsdcEngine); + this.usdcAvailable = pendingWithdraws[2] / 10 ** 6; + this.usdcLoansWithBalance = pendingWithdraws[3]; this.loadOngoingWithdraw(); this.updateDisplay(); } @@ -99,18 +98,18 @@ export class BalanceComponent implements OnInit, OnChanges, OnDestroy { * Load the pending withdraw */ loadOngoingWithdraw() { - this.ongoingDiasporeWithdraw = this.txService.getLastWithdraw( - environment.contracts.diaspore.debtEngine, - this.diasporeLoansWithBalance + this.usdcOngoingWithdraw = this.txService.getLastWithdraw( + environment.contracts[Engine.UsdcEngine].diaspore.debtEngine, + this.usdcLoansWithBalance ); } /** - * Handle click on withdraw + * Handle click on withdraw USDC */ - async clickWithdraw() { + async clickWithdrawUsdc() { try { - await this.withdraw(); + await this.withdrawUsdc(); } catch (err) { if (err.stack.indexOf('User denied transaction signature') < 0) { this.eventsService.trackError(err); @@ -121,11 +120,11 @@ export class BalanceComponent implements OnInit, OnChanges, OnDestroy { /** * Withdraw diaspore funds */ - async withdraw() { - if (this.canWithdraw) { - if (this.diasporeLoansWithBalance.length > 0) { - const tx = await this.contractService.withdrawFundsDiaspore(this.diasporeLoansWithBalance); - this.txService.registerWithdrawTx(tx, environment.contracts.diaspore.debtEngine, this.diasporeLoansWithBalance); + private async withdrawUsdc() { + if (this.usdcCanWithdraw) { + if (this.usdcLoansWithBalance.length > 0) { + const tx = await this.contractService.withdrawFundsDiaspore(Engine.UsdcEngine, this.usdcLoansWithBalance); + this.txService.registerWithdrawTx(tx, environment.contracts[Engine.UsdcEngine].diaspore.debtEngine, this.usdcLoansWithBalance); } this.loadWithdrawBalance(); this.retrievePendingTx(); @@ -135,9 +134,9 @@ export class BalanceComponent implements OnInit, OnChanges, OnDestroy { /** * Retrieve pending Tx */ - retrievePendingTx() { - if (!this.txSubscription) { - this.txSubscription = true; + private retrievePendingTx() { + if (!this.usdcTxSubscription) { + this.usdcTxSubscription = true; this.txService.subscribeConfirmedTx(async (tx: Tx) => this.trackWithdrawTx(tx)); } } @@ -145,7 +144,7 @@ export class BalanceComponent implements OnInit, OnChanges, OnDestroy { /** * Track tx */ - trackWithdrawTx(tx: Tx) { + private trackWithdrawTx(tx: Tx) { if (tx.type === Type.withdraw) { this.web3Service.updateBalanceEvent.emit(); } diff --git a/src/app/core/content-wrapper/content-wrapper.component.ts b/src/app/core/content-wrapper/content-wrapper.component.ts index dba5ddc5a..3de66e68e 100644 --- a/src/app/core/content-wrapper/content-wrapper.component.ts +++ b/src/app/core/content-wrapper/content-wrapper.component.ts @@ -2,13 +2,16 @@ import { Component, OnInit, HostListener } from '@angular/core'; import { MatDialog } from '@angular/material'; import * as BN from 'bn.js'; import { Utils } from '../../utils/utils'; -import { Loan } from './../../models/loan.model'; +import { Loan, Engine } from './../../models/loan.model'; import { Status } from './../../models/collateral.model'; +import { LoanContentApi } from './../../interfaces/loan-api-diaspore'; +import { LoanUtils } from './../../utils/loan-utils'; // App Components import { DialogWrongCountryComponent } from '../../dialogs/dialog-wrong-country/dialog-wrong-country.component'; import { DialogNeedWithdrawComponent } from '../../dialogs/dialog-need-withdraw/dialog-need-withdraw.component'; // App Service import { environment } from '../../../environments/environment'; +import { ProxyApiService } from '../../services/proxy-api.service'; import { SidebarService } from '../../services/sidebar.service'; import { ApplicationAdsService } from '../../services/application-ads.service'; import { Web3Service } from '../../services/web3.service'; @@ -40,7 +43,7 @@ export class ContentWrapperComponent implements OnInit { get withdrawEnabled(): boolean { return this.diasporeLoansWithBalance !== undefined && this.diasporeLoansWithBalance.length > 0 && - this.pendingDiasporeWithdraw === undefined; + this.pendingRcnWithdraw === undefined; } winHeight: number = window.innerHeight; account: string; @@ -52,7 +55,7 @@ export class ContentWrapperComponent implements OnInit { rcnAvailable: BN; loansWithBalance: number[]; diasporeLoansWithBalance: number[]; - pendingDiasporeWithdraw: Tx; + pendingRcnWithdraw: Tx; navToggle: boolean; // Navbar toggled navmobileToggled = false; // Nav Mobile toggled @@ -62,6 +65,7 @@ export class ContentWrapperComponent implements OnInit { needWithdraw: boolean; constructor( + private proxyApiService: ProxyApiService, private sidebarService: SidebarService, // Navbar Service private applicationAdsService: ApplicationAdsService, private web3Service: Web3Service, @@ -141,8 +145,8 @@ export class ContentWrapperComponent implements OnInit { * Load all pending withdraw */ private loadPendingWithdraw() { - this.pendingDiasporeWithdraw = this.txService.getLastWithdraw( - environment.contracts.diaspore.debtEngine, + this.pendingRcnWithdraw = this.txService.getLastWithdraw( + environment.contracts[Engine.RcnEngine].diaspore.debtEngine, this.diasporeLoansWithBalance ); } @@ -159,7 +163,7 @@ export class ContentWrapperComponent implements OnInit { * Load withdraw balance adding diaspore amount */ private async loadWithdrawBalance() { - const pendingWithdraws = await this.contractService.getPendingWithdraws(); + const pendingWithdraws = await this.contractService.getPendingWithdraws(Engine.RcnEngine); this.rcnAvailable = Utils.bn(pendingWithdraws[2] / 10 ** 18); this.diasporeLoansWithBalance = pendingWithdraws[3]; this.loadPendingWithdraw(); @@ -177,9 +181,10 @@ export class ContentWrapperComponent implements OnInit { return; } + const { content } = await this.proxyApiService.getLent(account); + const loans: Loan[] = content.map((loanData: LoanContentApi) => LoanUtils.buildLoan(loanData)); const loansToWithdraw: Loan[] = - (await this.contractService.getLoansOfBorrower(account)) - .filter(({ collateral }) => collateral && collateral.status === Status.ToWithdraw); + loans.filter(({ collateral }) => collateral && collateral.status === Status.ToWithdraw); if (loansToWithdraw.length) { this.needWithdraw = true; diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 0d73f407e..dc460e2e2 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -12,6 +12,7 @@ import { NotificationsComponent } from './header/notifications/notifications.com import { NotificationItemComponent } from './header/notifications/notification-item/notification-item.component'; import { WalletBalancesComponent } from './header/wallet-balances/wallet-balances.component'; import { WalletAvatarComponent } from './header/wallet-avatar/wallet-avatar.component'; +import { WalletWithdrawComponent } from './header/wallet-withdraw/wallet-withdraw.component'; import { SocialContainerComponent } from './social-container/social-container.component'; // App Directives import { ClickOutsideDirective } from '../directives/click-outside.directive'; @@ -34,6 +35,7 @@ import { OpenEtherscanDirective } from '../directives/open-etherscan.directive'; NotificationItemComponent, WalletBalancesComponent, WalletAvatarComponent, + WalletWithdrawComponent, SocialContainerComponent ], exports: [ diff --git a/src/app/core/header/header.component.html b/src/app/core/header/header.component.html index cb8f5b14d..9522a0a40 100644 --- a/src/app/core/header/header.component.html +++ b/src/app/core/header/header.component.html @@ -62,9 +62,7 @@

RCN / Ripio credit network

class="align-items-center px-0" [ngClass]="hasAccount ? 'd-flex' : 'd-none'" > - - - + diff --git a/src/app/core/header/icon-group-header/icon-group-header.component.scss b/src/app/core/header/icon-group-header/icon-group-header.component.scss index 0da949939..5f53d8ede 100644 --- a/src/app/core/header/icon-group-header/icon-group-header.component.scss +++ b/src/app/core/header/icon-group-header/icon-group-header.component.scss @@ -18,7 +18,7 @@ align-items: center; justify-content: center; position: absolute; - right: 5.5em; + right: 10.65em; top: -5px; padding: 0 4px; font-size: 11px; diff --git a/src/app/core/header/icon-group-header/icon-group-header.component.ts b/src/app/core/header/icon-group-header/icon-group-header.component.ts index 98c808252..11999cdc9 100644 --- a/src/app/core/header/icon-group-header/icon-group-header.component.ts +++ b/src/app/core/header/icon-group-header/icon-group-header.component.ts @@ -1,51 +1,84 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { Subscription } from 'rxjs'; import { HeaderPopoverService } from '../../../services/header-popover.service'; +enum ViewType { + Notifications = 'notifications', + WalletBalance = 'balance', + WalletWithdraw = 'withdraw' +} + @Component({ selector: 'app-icon-group-header', templateUrl: './icon-group-header.component.html', styleUrls: ['./icon-group-header.component.scss'] }) -export class IconGroupHeaderComponent implements OnInit { +export class IconGroupHeaderComponent implements OnInit, OnDestroy { + @Input() account: string; viewDetail: string; selection: string; previousSelection: string; - notificationsCounter: number; + // subscriptions + subscriptionHeader: Subscription; constructor( public headerPopoverService: HeaderPopoverService - ) {} + ) { } + + ngOnInit() { + this.subscriptionHeader = + this.headerPopoverService.currentDetail.subscribe(detail => this.viewDetail = detail); + } + + ngOnDestroy() { + if (this.subscriptionHeader) { + this.subscriptionHeader.unsubscribe(); + } + } - isDetail(view: string): Boolean { // Check viewDetail state to open/close notifications Component + /** + * Check viewDetail state to open/close notifications Component + * @param view ViewType + */ + isDetail(view: ViewType | string): Boolean { return view === this.viewDetail; } - openDetail(selection: 'clickOutside' | 'notifications' | 'balance') { // Change viewDetail state to open/close notifications Component + + /** + * Change viewDetail state to open/close notifications Component + * @param selection ViewType (icon clicked) + */ + openDetail(selection: ViewType | string) { this.previousSelection = this.selection; this.selection = selection; - switch (selection) { - case 'notifications': - case 'balance': - if (selection !== this.previousSelection || this.viewDetail === undefined) { - this.headerPopoverService.changeDetail(selection); // Change value of viewDetail from Notifications Service - } else { - this.headerPopoverService.changeDetail(undefined); // Force to close notifications Component by ClickOutside Directive event - } - break; - default: - this.headerPopoverService.changeDetail(undefined); // Force to close notifications Component by ClickOutside Directive event - break; + + const { previousSelection, viewDetail } = this; + const isClickOutside = !Object.values(ViewType).includes(selection); + + // case clickOutside + if (isClickOutside) { + const isPreviousClickOutside = previousSelection === 'clickOutside'; + if (previousSelection && !isPreviousClickOutside) { + this.headerPopoverService.changeDetail(undefined); + } + return; + } + + // case ViewType + if (selection !== previousSelection || viewDetail === undefined) { + this.headerPopoverService.changeDetail(selection); + } else { + this.headerPopoverService.changeDetail(undefined); } } + /** + * Updates the notification counter when the child component emits an event + * @param counter New notifications counter number + */ updateCounter(counter: number) { this.notificationsCounter = counter; } - - ngOnInit() { - // Subscribe to detail from Notifications Service - this.headerPopoverService.currentDetail.subscribe(detail => this.viewDetail = detail); - } - } diff --git a/src/app/core/header/icon-group-header/icon-group.component.spec.ts b/src/app/core/header/icon-group-header/icon-group.component.spec.ts index 131539b46..298ce7e63 100644 --- a/src/app/core/header/icon-group-header/icon-group.component.spec.ts +++ b/src/app/core/header/icon-group-header/icon-group.component.spec.ts @@ -39,7 +39,7 @@ describe('IconGroupHeaderComponent', () => { }); it('should return true', () => { - const view = 'notifications'; + const view = 'notifications' as any; component.viewDetail = view; expect(component.isDetail(view)).toBeTruthy(); }); diff --git a/src/app/core/header/notifications/notifications.component.ts b/src/app/core/header/notifications/notifications.component.ts index fd9dea760..00ee97ce0 100644 --- a/src/app/core/header/notifications/notifications.component.ts +++ b/src/app/core/header/notifications/notifications.component.ts @@ -7,6 +7,7 @@ import { transition } from '@angular/animations'; import { environment } from '../../../../environments/environment'; +import { Engine } from '../../../models/loan.model'; import { Notification, TxObject } from '../../../models/notification.model'; import { HeaderPopoverService } from '../../../services/header-popover.service'; import { TxService, Tx } from '../../../services/tx.service'; @@ -62,16 +63,20 @@ export class NotificationsComponent implements OnInit { */ getContractName(contract: string) { switch (contract) { - case environment.contracts.diaspore.loanManager: + case environment.contracts[Engine.RcnEngine].diaspore.loanManager: + case environment.contracts[Engine.UsdcEngine].diaspore.loanManager: return 'Loan Manager Contract'; - case environment.contracts.diaspore.debtEngine: + case environment.contracts[Engine.RcnEngine].diaspore.debtEngine: + case environment.contracts[Engine.UsdcEngine].diaspore.debtEngine: return 'Debt Engine Contract'; - case environment.contracts.converter.converterRamp: + case environment.contracts[Engine.RcnEngine].converter.converterRamp: + case environment.contracts[Engine.UsdcEngine].converter.converterRamp: return 'Converter Ramp Contract'; - case environment.contracts.collateral.collateral: + case environment.contracts[Engine.RcnEngine].collateral.collateral: + case environment.contracts[Engine.UsdcEngine].collateral.collateral: return 'Collateral Contract'; default: diff --git a/src/app/core/header/wallet-balances/wallet-balances.component.ts b/src/app/core/header/wallet-balances/wallet-balances.component.ts index 9966a31cc..d923f7f02 100644 --- a/src/app/core/header/wallet-balances/wallet-balances.component.ts +++ b/src/app/core/header/wallet-balances/wallet-balances.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; +import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core'; import { trigger, state, @@ -6,6 +6,7 @@ import { animate, transition } from '@angular/animations'; +import { Subscription } from 'rxjs'; import * as BN from 'bn.js'; import { Currency } from './../../../utils/currencies'; import { HeaderPopoverService } from './../../../services/header-popover.service'; @@ -41,11 +42,13 @@ interface Balance { ]) ] }) -export class WalletBalancesComponent implements OnInit { +export class WalletBalancesComponent implements OnInit, OnDestroy { viewDetail: string; - rcnBalance: string; balances: Balance[]; + // subscriptions + subscriptionPopover: Subscription; + constructor( private cdRef: ChangeDetectorRef, public headerPopoverService: HeaderPopoverService, @@ -54,15 +57,22 @@ export class WalletBalancesComponent implements OnInit { ) { } ngOnInit() { - this.headerPopoverService.currentDetail.subscribe(async detail => { - this.viewDetail = detail; - this.cdRef.detectChanges(); - await this.loadBalances(); - }); + this.subscriptionPopover = + this.headerPopoverService.currentDetail.subscribe(async detail => { + this.viewDetail = detail; + this.cdRef.detectChanges(); + await this.loadBalances(); + }); this.loadBalances(); } + ngOnDestroy() { + if (this.subscriptionPopover) { + this.subscriptionPopover.unsubscribe(); + } + } + /** * Show the user balance in different tokens */ diff --git a/src/app/core/header/wallet-withdraw/wallet-withdraw.component.html b/src/app/core/header/wallet-withdraw/wallet-withdraw.component.html new file mode 100644 index 000000000..a87af6fc7 --- /dev/null +++ b/src/app/core/header/wallet-withdraw/wallet-withdraw.component.html @@ -0,0 +1,58 @@ +
+ +
diff --git a/src/app/core/header/wallet-withdraw/wallet-withdraw.component.scss b/src/app/core/header/wallet-withdraw/wallet-withdraw.component.scss new file mode 100644 index 000000000..5d0f58c2c --- /dev/null +++ b/src/app/core/header/wallet-withdraw/wallet-withdraw.component.scss @@ -0,0 +1,53 @@ +@import './../../../../scss/variables'; +@import './../../../../scss/fonts'; + +.notifications-container { + width: 212px; + max-height: 670px; + overflow: hidden; + position: absolute; + right: 14px; + @extend %roboto-regular; + @include transition(); + + ul { + li { + &:nth-child(1){ + display: flex; + align-items: center; + height: 30px; + padding: 10px; + background-color: var(--app-color-gray-500); + border-bottom: 1px solid var(--app-color-gray-600); + } + } + .item { + padding: 11px 9px 11px 7px; + border-bottom: 1px solid var(--app-color-gray-600); + background-color: var(--app-color-gray-400); + color: white; + &__image { + width: 30px; + margin-right: 6px; + } + &__text { + &-amount { + @include typography-caption(1); + } + &-currency { + @include styled-font('Roboto', 700, 11px); + color: var(--app-color-gray-50); + } + } + &__action { + .button { + @include styled-font('Roboto', 400, 10px); + min-width: inherit; + padding: 6px 8px; + line-height: 1em; + margin: 0; + } + } + } + } +} diff --git a/src/app/core/header/wallet-withdraw/wallet-withdraw.component.spec.ts b/src/app/core/header/wallet-withdraw/wallet-withdraw.component.spec.ts new file mode 100644 index 000000000..f61d0dd7f --- /dev/null +++ b/src/app/core/header/wallet-withdraw/wallet-withdraw.component.spec.ts @@ -0,0 +1,32 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { SharedModule } from './../../../shared/shared.module'; +import { WalletWithdrawComponent } from './wallet-withdraw.component'; + +describe('WalletWithdrawComponent', () => { + let component: WalletWithdrawComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + BrowserAnimationsModule, + HttpClientModule, + SharedModule + ], + declarations: [ WalletWithdrawComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WalletWithdrawComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/header/wallet-withdraw/wallet-withdraw.component.ts b/src/app/core/header/wallet-withdraw/wallet-withdraw.component.ts new file mode 100644 index 000000000..c4bef3e25 --- /dev/null +++ b/src/app/core/header/wallet-withdraw/wallet-withdraw.component.ts @@ -0,0 +1,258 @@ +import { Component, OnInit, OnChanges, OnDestroy, Input, ChangeDetectorRef } from '@angular/core'; +import { + trigger, + state, + style, + animate, + transition +} from '@angular/animations'; +import { Subscription, timer } from 'rxjs'; +import { environment } from '../../../../environments/environment'; +import { Engine } from './../../../models/loan.model'; +import { HeaderPopoverService } from './../../../services/header-popover.service'; +import { ContractsService } from './../../../services/contracts.service'; +import { Web3Service } from './../../../services/web3.service'; +import { EventsService } from './../../../services/events.service'; +import { Tx, Type, TxService } from './../../../services/tx.service'; +import { Utils } from './../../../utils/utils'; + +@Component({ + selector: 'app-wallet-withdraw', + templateUrl: './wallet-withdraw.component.html', + styleUrls: ['./wallet-withdraw.component.scss'], + animations: [ + trigger('anmNotifications', [ + state('open', style({ + opacity: 1, + display: 'block', + top: '43px' + })), + state('closed', style({ + opacity: 0, + display: 'none', + top: '48px' + })), + transition('open => closed', [ + animate('.3s') + ]), + transition('closed => open', [ + animate('.3s') + ]) + ]) + ] +}) +export class WalletWithdrawComponent implements OnInit, OnDestroy, OnChanges { + @Input() account: string; + viewDetail: string; + + rcnAvailable: number; + rcnLoansWithBalance: number[] = []; + rcnOngoingWithdraw: Tx; + rcnCanWithdraw = false; + rcnTxSubscription: boolean; + rcnDisplayAvailable = ''; + + usdcAvailable: number; + usdcLoansWithBalance: number[] = []; + usdcOngoingWithdraw: Tx; + usdcCanWithdraw = false; + usdcTxSubscription: boolean; + usdcDisplayAvailable = ''; + + // subscriptions + subscriptionPopover: Subscription; + subscriptionBalance: Subscription; + + constructor( + private cdRef: ChangeDetectorRef, + private headerPopoverService: HeaderPopoverService, + private txService: TxService, + private web3Service: Web3Service, + private eventsService: EventsService, + private contractsService: ContractsService + ) { } + + ngOnInit() { + this.subscriptionPopover = + this.headerPopoverService.currentDetail.subscribe(detail => { + this.viewDetail = detail; + this.cdRef.detectChanges(); + this.loadWithdrawBalance(); + }); + + this.retrievePendingTx(); + this.handleBalanceEvents(); + } + + ngOnChanges(changes) { + const web3: any = this.web3Service.web3; + const { account } = changes; + + if (account.currentValue) { + this.account = web3.utils.toChecksumAddress(account.currentValue); + this.loadWithdrawBalance(); + } + } + + ngOnDestroy() { + if (this.subscriptionPopover) { + this.subscriptionPopover.unsubscribe(); + } + if (this.subscriptionBalance) { + this.subscriptionBalance.unsubscribe(); + } + if (this.rcnTxSubscription) { + this.txService.unsubscribeConfirmedTx(async (tx: Tx) => this.trackWithdrawTx(tx)); + } + if (this.usdcTxSubscription) { + this.txService.unsubscribeConfirmedTx(async (tx: Tx) => this.trackWithdrawTx(tx)); + } + } + + /** + * Listen and handle balance events for update amounts + */ + handleBalanceEvents() { + this.subscriptionBalance = this.web3Service.updateBalanceEvent.subscribe(() => { + this.loadWithdrawBalance(); + }); + } + + /** + * Update balance and withdraw amount + */ + updateDisplay() { + // rcn engine + if (this.rcnAvailable) { + this.rcnDisplayAvailable = Utils.formatAmount(this.rcnAvailable); + } else { + this.rcnDisplayAvailable = '0'; + } + this.rcnCanWithdraw = + this.rcnLoansWithBalance !== undefined && + this.rcnLoansWithBalance.length > 0 && + this.rcnOngoingWithdraw === undefined; + + // usdc engine + if (this.usdcAvailable) { + this.usdcDisplayAvailable = Utils.formatAmount(this.usdcAvailable); + } else { + this.usdcDisplayAvailable = '0'; + } + this.usdcCanWithdraw = + this.usdcLoansWithBalance !== undefined && + this.usdcLoansWithBalance.length > 0 && + this.usdcOngoingWithdraw === undefined; + } + + /** + * Load balance to withdraw amounts. Then, add all the values ​​and show the + * total available + */ + async loadWithdrawBalance() { + // rcn engine + const rcnPendingWithdraws = await this.contractsService.getPendingWithdraws(Engine.RcnEngine); + this.rcnAvailable = rcnPendingWithdraws[2] / 10 ** 18; + this.rcnLoansWithBalance = rcnPendingWithdraws[3]; + + // usdc engine + const usdcPendingWithdraws = await this.contractsService.getPendingWithdraws(Engine.UsdcEngine); + this.usdcAvailable = usdcPendingWithdraws[2] / 10 ** 6; + this.usdcLoansWithBalance = usdcPendingWithdraws[3]; + + this.loadOngoingWithdraw(); + this.updateDisplay(); + } + + /** + * Load the pending withdraw + */ + loadOngoingWithdraw() { + this.rcnOngoingWithdraw = this.txService.getLastWithdraw( + environment.contracts[Engine.RcnEngine].diaspore.debtEngine, + this.rcnLoansWithBalance + ); + this.usdcOngoingWithdraw = this.txService.getLastWithdraw( + environment.contracts[Engine.UsdcEngine].diaspore.debtEngine, + this.usdcLoansWithBalance + ); + } + + /** + * Handle click on withdraw RCN + */ + async clickWithdrawRcn() { + try { + await this.withdrawRcn(); + } catch (err) { + if (err.stack.indexOf('User denied transaction signature') < 0) { + this.eventsService.trackError(err); + } + } + } + + /** + * Handle click on withdraw USDC + */ + async clickWithdrawUsdc() { + try { + await this.withdrawUsdc(); + } catch (err) { + if (err.stack.indexOf('User denied transaction signature') < 0) { + this.eventsService.trackError(err); + } + } + } + + /** + * Withdraw diaspore funds + */ + private async withdrawRcn() { + if (this.rcnCanWithdraw) { + if (this.rcnLoansWithBalance.length > 0) { + const tx = await this.contractsService.withdrawFundsDiaspore(Engine.RcnEngine, this.rcnLoansWithBalance); + this.txService.registerWithdrawTx(tx, environment.contracts[Engine.RcnEngine].diaspore.debtEngine, this.rcnLoansWithBalance); + } + this.loadWithdrawBalance(); + this.retrievePendingTx(); + } + } + + /** + * Withdraw diaspore funds + */ + private async withdrawUsdc() { + if (this.usdcCanWithdraw) { + if (this.usdcLoansWithBalance.length > 0) { + const tx = await this.contractsService.withdrawFundsDiaspore(Engine.UsdcEngine, this.usdcLoansWithBalance); + this.txService.registerWithdrawTx(tx, environment.contracts[Engine.UsdcEngine].diaspore.debtEngine, this.usdcLoansWithBalance); + } + this.loadWithdrawBalance(); + this.retrievePendingTx(); + } + } + + /** + * Retrieve pending Tx + */ + private retrievePendingTx() { + if (!this.rcnTxSubscription) { + this.rcnTxSubscription = true; + this.txService.subscribeConfirmedTx(async (tx: Tx) => this.trackWithdrawTx(tx)); + } + if (!this.usdcTxSubscription) { + this.usdcTxSubscription = true; + this.txService.subscribeConfirmedTx(async (tx: Tx) => this.trackWithdrawTx(tx)); + } + } + + /** + * Track tx + */ + private async trackWithdrawTx(tx: Tx) { + if (tx.type === Type.withdraw) { + await timer(12000).toPromise(); + this.loadWithdrawBalance(); + } + } +} diff --git a/src/app/core/social-container/social-container.component.ts b/src/app/core/social-container/social-container.component.ts index a72b8b854..31a90a5a9 100644 --- a/src/app/core/social-container/social-container.component.ts +++ b/src/app/core/social-container/social-container.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { Engine } from '../../models/loan.model'; import { environment } from '../../../environments/environment.prod'; @Component({ @@ -12,6 +13,10 @@ export class SocialContainerComponent implements OnInit { constructor() { } ngOnInit() { - this.linkContract = environment.network.explorer.address.replace('${address}', environment.contracts.diaspore.loanManager); + this.linkContract = environment + .network + .explorer + .address + .replace('${address}', environment.contracts[Engine.RcnEngine].diaspore.loanManager); } } diff --git a/src/app/dialogs/dialog-approve-contract/dialog-approve-contract.component.html b/src/app/dialogs/dialog-approve-contract/dialog-approve-contract.component.html index b4c4e034d..8d01e08f5 100644 --- a/src/app/dialogs/dialog-approve-contract/dialog-approve-contract.component.html +++ b/src/app/dialogs/dialog-approve-contract/dialog-approve-contract.component.html @@ -19,15 +19,43 @@
-
-
-
+ + +
+
+

+ Approvals +

+

{{ dialogDescription }} -

+

-