From 235617649552262811bee5f896f695504fa5ec78 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 6 Dec 2023 09:34:53 +0200 Subject: [PATCH 01/48] MP-390 - Fix wording & winning icon for member stats --- .../MemberStatsBlock/MemberStatsBlock.tsx | 11 ++++++++--- .../StatsSummaryBlock/StatsSummaryBlock.tsx | 11 +++++++---- src/apps/profiles/src/lib/helpers.ts | 12 ++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx index ca76c380c..3964ec299 100644 --- a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx @@ -6,7 +6,7 @@ import { getRatingColor, MemberStats, UserProfile } from '~/libs/core' import { IconOutline } from '~/libs/ui' import { useFetchActiveTracks } from '../../../hooks' -import { WinnerIcon } from '../../../lib' +import { formatPlural, WinnerIcon } from '../../../lib' import { MemberProfileContextValue, useMemberProfileContext } from '../../../member-profile/MemberProfile.context' import styles from './MemberStatsBlock.module.scss' @@ -45,13 +45,18 @@ const MemberStatsBlock: FC = props => {
{!track.isDSTrack && ((track.submissions || track.wins) > 0) && ( <> - + {track.wins > 0 && ( + + )} {track.wins || track.submissions} - {track.wins > 0 ? 'Wins' : 'Submissions'} + {formatPlural( + track.wins || track.submissions || 0, + track.wins > 0 ? 'Win' : 'Submission', + )} diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx index 33b1749bc..692bc26a9 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx @@ -4,7 +4,7 @@ import { find, get } from 'lodash' import { getRatingColor } from '~/libs/core' -import { numberToFixed } from '../../../lib' +import { formatPlural, numberToFixed } from '../../../lib' import { TracksSummaryStats } from '../../../config' import styles from './StatsSummaryBlock.module.scss' @@ -49,7 +49,10 @@ const StatsSummaryBlock: FC = props => { - {props.trackTitle === 'Single Round Match' ? 'Competitions' : 'Challenges'} + {formatPlural( + props.challenges || 0, + props.trackTitle === 'Single Round Match' ? 'Competition' : 'Challenge', + )}
@@ -61,7 +64,7 @@ const StatsSummaryBlock: FC = props => { - Wins + {formatPlural(props.wins || 0, 'Win')} @@ -73,7 +76,7 @@ const StatsSummaryBlock: FC = props => { - Submissions + {formatPlural(props.submissions || 0, 'Submission')} diff --git a/src/apps/profiles/src/lib/helpers.ts b/src/apps/profiles/src/lib/helpers.ts index b80c17c5a..36bc840fd 100644 --- a/src/apps/profiles/src/lib/helpers.ts +++ b/src/apps/profiles/src/lib/helpers.ts @@ -113,3 +113,15 @@ export function isValidURL(urlToValidate: string): boolean { return true } + +/** + * Creates the string with the number of items and the word describing the item + * possibly in plural form. + * + * @param {number} count number of entities + * @param {string} baseWord base word that describes the entity + * @returns {string} + */ +export function formatPlural(count: number, baseWord: string): string { + return `${baseWord}${count === 1 ? '' : 's'}` +} From 49af1fdcdaaeac4907441fcbcaa58ffdf0cab25b Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 6 Dec 2023 09:37:55 +0200 Subject: [PATCH 02/48] MP-390 --- .../SubTrackSummaryCard/SubTrackSummaryCard.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx index 127744573..52c38e414 100644 --- a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx +++ b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames' import { IconSolid } from '~/libs/ui' -import { subTrackLabelToHumanName, WinnerIcon } from '../../../lib' +import { formatPlural, subTrackLabelToHumanName, WinnerIcon } from '../../../lib' import styles from './SubTrackSummaryCard.module.scss' @@ -29,7 +29,9 @@ const SubTrackSummaryCard: FC = props => ( {props.wins} - wins + + {formatPlural(props.wins || 0, 'Win')} + )} @@ -38,7 +40,9 @@ const SubTrackSummaryCard: FC = props => ( {props.submissions} - submissions + + {formatPlural(props.submissions || 0, 'Submission')} + From 503a433db1db8f72f09aa1f774c50b615f0e76db Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 6 Dec 2023 21:58:09 +0200 Subject: [PATCH 03/48] update comment --- src/apps/profiles/src/lib/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/profiles/src/lib/helpers.ts b/src/apps/profiles/src/lib/helpers.ts index 36bc840fd..d5306a1be 100644 --- a/src/apps/profiles/src/lib/helpers.ts +++ b/src/apps/profiles/src/lib/helpers.ts @@ -118,8 +118,8 @@ export function isValidURL(urlToValidate: string): boolean { * Creates the string with the number of items and the word describing the item * possibly in plural form. * - * @param {number} count number of entities - * @param {string} baseWord base word that describes the entity + * @param {number} count - The number of entities + * @param {string} baseWord - The base word that describes the entity * @returns {string} */ export function formatPlural(count: number, baseWord: string): string { From 2c12f9abc1221b69b600d6df4277673afc5813aa Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 13 Dec 2023 10:49:28 +0200 Subject: [PATCH 04/48] MP-391 - fix rating display for CP track --- .../MemberStatsBlock/MemberStatsBlock.tsx | 22 +++++++++---------- .../src/hooks/useFetchActiveTracks.tsx | 2 ++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx index ca76c380c..d870d89f0 100644 --- a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx @@ -58,17 +58,7 @@ const MemberStatsBlock: FC = props => { )} {/* competitive programming only */} {track.isDSTrack && ( - (track.percentile as number) >= 50 ? ( - - - {track.percentile} - % - - - Percentile - - - ) : ( + (track.isCPTrack || (track.percentile as number) < 50) ? ( <> = props => { + ) : ( + + + {track.percentile} + % + + + Percentile + + ) )} return { challenges: dataScienceSubTracks.SRM?.challenges ?? 0, isActive: (dataScienceSubTracks.SRM?.challenges ?? 0) > 0, + isCPTrack: true, isDSTrack: true, name: 'Competitive Programming', order: -2, From 506fccbe1cf243691779da8033225488a30b45fa Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 4 Jan 2024 18:19:27 +0600 Subject: [PATCH 05/48] feat: wallet app --- .prettierrc | 3 + .vscode/settings.json | 4 + craco.config.js | 1 + package.json | 1 + src/apps/platform/src/platform.routes.tsx | 7 +- src/apps/wallet/.prettierrc | 7 + src/apps/wallet/README.md | 0 src/apps/wallet/index.tsx | 1 + src/apps/wallet/src/WalletApp.tsx | 20 ++ src/apps/wallet/src/home/WalletHomePage.tsx | 19 ++ src/apps/wallet/src/home/index.ts | 1 + .../home/page-layout/WalletLayout.module.scss | 5 + .../src/home/page-layout/WalletLayout.tsx | 20 ++ src/apps/wallet/src/home/page-layout/index.ts | 1 + .../src/home/tabs/WalletTabs.module.scss | 11 + src/apps/wallet/src/home/tabs/WalletTabs.tsx | 51 ++++ src/apps/wallet/src/home/tabs/config/index.ts | 1 + .../home/tabs/config/wallet-tabs-config.ts | 55 ++++ .../src/home/tabs/home/Home.module.scss | 39 +++ .../wallet/src/home/tabs/home/HomeTab.tsx | 122 ++++++++ src/apps/wallet/src/home/tabs/home/index.ts | 1 + src/apps/wallet/src/home/tabs/index.ts | 1 + .../tabs/payments/PaymentsTab.module.scss | 117 ++++++++ .../src/home/tabs/payments/PaymentsTab.tsx | 281 ++++++++++++++++++ .../wallet/src/home/tabs/payments/index.ts | 1 + .../PaymentInfoModal.module.scss | 13 + .../payment-info-modal/PaymentInfoModal.tsx | 72 +++++ .../tabs/payments/payment-info-modal/index.ts | 1 + .../tabs/tax-forms/TaxFormsTab.module.scss | 45 +++ .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 194 ++++++++++++ .../wallet/src/home/tabs/tax-forms/index.ts | 1 + .../home/tabs/winnings/Winnings.module.scss | 29 ++ .../src/home/tabs/winnings/WinningsTab.tsx | 99 ++++++ .../wallet/src/home/tabs/winnings/index.ts | 1 + src/apps/wallet/src/index.ts | 1 + .../src/lib/assets/home/banner-image.svg | 11 + .../src/lib/assets/home/banner-text.svg | 5 + src/apps/wallet/src/lib/assets/home/index.ts | 4 + src/apps/wallet/src/lib/assets/index.ts | 5 + .../lib/assets/payments/PayPal_logo_gray.svg | 8 + .../lib/assets/payments/Payoneer_log_gray.svg | 4 + .../assets/payments/Payoneer_logo_color.svg | 15 + .../lib/assets/payments/Paypal_logo_color.svg | 14 + .../payments/Western_Union_Logo_gray.svg | 13 + .../src/lib/assets/payments/icon-dollar.svg | 3 + .../src/lib/assets/payments/icon-speed.svg | 3 + .../src/lib/assets/payments/icon-world.svg | 3 + .../wallet/src/lib/assets/payments/index.ts | 8 + .../src/lib/assets/preferences/email.svg | 4 + .../src/lib/assets/preferences/forum.svg | 4 + .../src/lib/assets/preferences/index.ts | 7 + .../src/lib/assets/security/apple-store.svg | 34 +++ .../src/lib/assets/security/credential.png | Bin 0 -> 47241 bytes .../src/lib/assets/security/dicelogo.png | Bin 0 -> 3067 bytes .../src/lib/assets/security/dicelogobig.png | Bin 0 -> 23747 bytes .../src/lib/assets/security/dicelogosmall.png | Bin 0 -> 6681 bytes .../src/lib/assets/security/google-play.png | Bin 0 -> 7000 bytes .../wallet/src/lib/assets/security/index.ts | 17 ++ .../wallet/src/lib/assets/security/mfa.svg | 4 + .../src/lib/assets/security/unsuccessful.svg | 3 + .../lib/assets/tax-forms/ic-check-circle.svg | 3 + .../src/lib/assets/tax-forms/ic-earth.svg | 30 ++ .../wallet/src/lib/assets/tax-forms/ic-us.svg | 25 ++ .../wallet/src/lib/assets/tax-forms/index.ts | 5 + .../src/lib/assets/tcandyou/data_science.svg | 11 + .../wallet/src/lib/assets/tcandyou/design.svg | 14 + .../src/lib/assets/tcandyou/develop.svg | 11 + .../src/lib/assets/tcandyou/ico-ethereum.png | Bin 0 -> 2649 bytes .../src/lib/assets/tcandyou/ico-ibmcloud.png | Bin 0 -> 3424 bytes .../src/lib/assets/tcandyou/ico-veteran.png | Bin 0 -> 16329 bytes .../wallet/src/lib/assets/tcandyou/index.ts | 15 + .../assets/tools/Financial Institution.svg | 6 + .../tools/Internet Service Provider.svg | 6 + .../src/lib/assets/tools/Mobile Carrier.svg | 6 + .../src/lib/assets/tools/Television.svg | 16 + .../wallet/src/lib/assets/tools/console.svg | 1 + .../wallet/src/lib/assets/tools/desktop.svg | 4 + src/apps/wallet/src/lib/assets/tools/index.ts | 31 ++ .../wallet/src/lib/assets/tools/laptop.svg | 1 + .../src/lib/assets/tools/other_device.svg | 9 + .../assets/tools/other_service_provider.svg | 17 ++ .../src/lib/assets/tools/smartphone.svg | 4 + .../wallet/src/lib/assets/tools/software.svg | 8 + .../src/lib/assets/tools/subscription.svg | 3 + .../wallet/src/lib/assets/tools/tablet.svg | 1 + .../wallet/src/lib/assets/tools/wearable.svg | 1 + .../src/lib/components/chip/Chip.module.scss | 18 ++ .../wallet/src/lib/components/chip/Chip.tsx | 11 + .../wallet/src/lib/components/chip/index.ts | 1 + src/apps/wallet/src/lib/components/index.ts | 3 + .../components/info-row/InfoRow.module.scss | 38 +++ .../src/lib/components/info-row/InfoRow.tsx | 23 ++ .../src/lib/components/info-row/index.ts | 1 + .../components/otp-modal/OtpModal.module.scss | 108 +++++++ .../src/lib/components/otp-modal/OtpModal.tsx | 88 ++++++ .../src/lib/components/otp-modal/index.ts | 1 + .../PaymentProviderCard.module.scss | 79 +++++ .../PaymentProviderCard.tsx | 82 +++++ .../components/payment-provider-card/index.ts | 1 + .../payments-table/PaymentTable.module.scss | 77 +++++ .../payments-table/PaymentTable.tsx | 111 +++++++ .../SettingSection.module.scss | 26 ++ .../setting-section/SettingSection.tsx | 30 ++ .../lib/components/setting-section/index.ts | 1 + .../tax-form-card/TaxFormCard.module.scss | 46 +++ .../components/tax-form-card/TaxFormCard.tsx | 82 +++++ .../src/lib/components/tax-form-card/index.ts | 1 + .../tax-form-detail/TaxFormDetail.module.scss | 57 ++++ .../tax-form-detail/TaxFormDetail.tsx | 36 +++ .../lib/components/tax-form-detail/index.ts | 1 + src/apps/wallet/src/lib/index.ts | 3 + src/apps/wallet/src/lib/models/ApiError.ts | 4 + src/apps/wallet/src/lib/models/ApiResponse.ts | 4 + .../wallet/src/lib/models/PaymentProvider.ts | 11 + src/apps/wallet/src/lib/models/TaxForm.ts | 18 ++ .../wallet/src/lib/models/WalletDetails.ts | 19 ++ .../wallet/src/lib/models/WinningDetail.ts | 35 +++ src/apps/wallet/src/lib/services/wallet.ts | 141 +++++++++ .../wallet/src/lib/wallet-swr/WalletSwr.tsx | 23 ++ src/apps/wallet/src/lib/wallet-swr/index.ts | 1 + src/apps/wallet/src/wallet.routes.tsx | 32 ++ src/config/constants.ts | 6 +- .../lib/xhr/xhr-functions/xhr.functions.ts | 88 ++++-- .../modals/base-modal/BaseModal.tsx | 57 ++-- tsconfig.paths.json | 3 + yarn.lock | 5 + 126 files changed, 2947 insertions(+), 48 deletions(-) create mode 100644 .prettierrc create mode 100644 .vscode/settings.json create mode 100644 src/apps/wallet/.prettierrc create mode 100644 src/apps/wallet/README.md create mode 100644 src/apps/wallet/index.tsx create mode 100644 src/apps/wallet/src/WalletApp.tsx create mode 100644 src/apps/wallet/src/home/WalletHomePage.tsx create mode 100644 src/apps/wallet/src/home/index.ts create mode 100644 src/apps/wallet/src/home/page-layout/WalletLayout.module.scss create mode 100644 src/apps/wallet/src/home/page-layout/WalletLayout.tsx create mode 100644 src/apps/wallet/src/home/page-layout/index.ts create mode 100644 src/apps/wallet/src/home/tabs/WalletTabs.module.scss create mode 100644 src/apps/wallet/src/home/tabs/WalletTabs.tsx create mode 100644 src/apps/wallet/src/home/tabs/config/index.ts create mode 100644 src/apps/wallet/src/home/tabs/config/wallet-tabs-config.ts create mode 100644 src/apps/wallet/src/home/tabs/home/Home.module.scss create mode 100644 src/apps/wallet/src/home/tabs/home/HomeTab.tsx create mode 100644 src/apps/wallet/src/home/tabs/home/index.ts create mode 100644 src/apps/wallet/src/home/tabs/index.ts create mode 100644 src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss create mode 100644 src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx create mode 100644 src/apps/wallet/src/home/tabs/payments/index.ts create mode 100644 src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.module.scss create mode 100644 src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx create mode 100644 src/apps/wallet/src/home/tabs/payments/payment-info-modal/index.ts create mode 100644 src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss create mode 100644 src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx create mode 100644 src/apps/wallet/src/home/tabs/tax-forms/index.ts create mode 100644 src/apps/wallet/src/home/tabs/winnings/Winnings.module.scss create mode 100644 src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx create mode 100644 src/apps/wallet/src/home/tabs/winnings/index.ts create mode 100644 src/apps/wallet/src/index.ts create mode 100644 src/apps/wallet/src/lib/assets/home/banner-image.svg create mode 100644 src/apps/wallet/src/lib/assets/home/banner-text.svg create mode 100644 src/apps/wallet/src/lib/assets/home/index.ts create mode 100644 src/apps/wallet/src/lib/assets/index.ts create mode 100644 src/apps/wallet/src/lib/assets/payments/PayPal_logo_gray.svg create mode 100644 src/apps/wallet/src/lib/assets/payments/Payoneer_log_gray.svg create mode 100644 src/apps/wallet/src/lib/assets/payments/Payoneer_logo_color.svg create mode 100644 src/apps/wallet/src/lib/assets/payments/Paypal_logo_color.svg create mode 100644 src/apps/wallet/src/lib/assets/payments/Western_Union_Logo_gray.svg create mode 100644 src/apps/wallet/src/lib/assets/payments/icon-dollar.svg create mode 100644 src/apps/wallet/src/lib/assets/payments/icon-speed.svg create mode 100644 src/apps/wallet/src/lib/assets/payments/icon-world.svg create mode 100644 src/apps/wallet/src/lib/assets/payments/index.ts create mode 100755 src/apps/wallet/src/lib/assets/preferences/email.svg create mode 100755 src/apps/wallet/src/lib/assets/preferences/forum.svg create mode 100644 src/apps/wallet/src/lib/assets/preferences/index.ts create mode 100644 src/apps/wallet/src/lib/assets/security/apple-store.svg create mode 100644 src/apps/wallet/src/lib/assets/security/credential.png create mode 100644 src/apps/wallet/src/lib/assets/security/dicelogo.png create mode 100644 src/apps/wallet/src/lib/assets/security/dicelogobig.png create mode 100644 src/apps/wallet/src/lib/assets/security/dicelogosmall.png create mode 100644 src/apps/wallet/src/lib/assets/security/google-play.png create mode 100644 src/apps/wallet/src/lib/assets/security/index.ts create mode 100644 src/apps/wallet/src/lib/assets/security/mfa.svg create mode 100644 src/apps/wallet/src/lib/assets/security/unsuccessful.svg create mode 100644 src/apps/wallet/src/lib/assets/tax-forms/ic-check-circle.svg create mode 100644 src/apps/wallet/src/lib/assets/tax-forms/ic-earth.svg create mode 100644 src/apps/wallet/src/lib/assets/tax-forms/ic-us.svg create mode 100644 src/apps/wallet/src/lib/assets/tax-forms/index.ts create mode 100644 src/apps/wallet/src/lib/assets/tcandyou/data_science.svg create mode 100644 src/apps/wallet/src/lib/assets/tcandyou/design.svg create mode 100644 src/apps/wallet/src/lib/assets/tcandyou/develop.svg create mode 100755 src/apps/wallet/src/lib/assets/tcandyou/ico-ethereum.png create mode 100755 src/apps/wallet/src/lib/assets/tcandyou/ico-ibmcloud.png create mode 100644 src/apps/wallet/src/lib/assets/tcandyou/ico-veteran.png create mode 100644 src/apps/wallet/src/lib/assets/tcandyou/index.ts create mode 100644 src/apps/wallet/src/lib/assets/tools/Financial Institution.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/Internet Service Provider.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/Mobile Carrier.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/Television.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/console.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/desktop.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/index.ts create mode 100644 src/apps/wallet/src/lib/assets/tools/laptop.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/other_device.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/other_service_provider.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/smartphone.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/software.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/subscription.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/tablet.svg create mode 100644 src/apps/wallet/src/lib/assets/tools/wearable.svg create mode 100644 src/apps/wallet/src/lib/components/chip/Chip.module.scss create mode 100644 src/apps/wallet/src/lib/components/chip/Chip.tsx create mode 100644 src/apps/wallet/src/lib/components/chip/index.ts create mode 100644 src/apps/wallet/src/lib/components/index.ts create mode 100644 src/apps/wallet/src/lib/components/info-row/InfoRow.module.scss create mode 100644 src/apps/wallet/src/lib/components/info-row/InfoRow.tsx create mode 100644 src/apps/wallet/src/lib/components/info-row/index.ts create mode 100644 src/apps/wallet/src/lib/components/otp-modal/OtpModal.module.scss create mode 100644 src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx create mode 100644 src/apps/wallet/src/lib/components/otp-modal/index.ts create mode 100644 src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss create mode 100644 src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx create mode 100644 src/apps/wallet/src/lib/components/payment-provider-card/index.ts create mode 100644 src/apps/wallet/src/lib/components/payments-table/PaymentTable.module.scss create mode 100644 src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx create mode 100644 src/apps/wallet/src/lib/components/setting-section/SettingSection.module.scss create mode 100644 src/apps/wallet/src/lib/components/setting-section/SettingSection.tsx create mode 100644 src/apps/wallet/src/lib/components/setting-section/index.ts create mode 100644 src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.module.scss create mode 100644 src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx create mode 100644 src/apps/wallet/src/lib/components/tax-form-card/index.ts create mode 100644 src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss create mode 100644 src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx create mode 100644 src/apps/wallet/src/lib/components/tax-form-detail/index.ts create mode 100644 src/apps/wallet/src/lib/index.ts create mode 100644 src/apps/wallet/src/lib/models/ApiError.ts create mode 100644 src/apps/wallet/src/lib/models/ApiResponse.ts create mode 100644 src/apps/wallet/src/lib/models/PaymentProvider.ts create mode 100644 src/apps/wallet/src/lib/models/TaxForm.ts create mode 100644 src/apps/wallet/src/lib/models/WalletDetails.ts create mode 100644 src/apps/wallet/src/lib/models/WinningDetail.ts create mode 100644 src/apps/wallet/src/lib/services/wallet.ts create mode 100644 src/apps/wallet/src/lib/wallet-swr/WalletSwr.tsx create mode 100644 src/apps/wallet/src/lib/wallet-swr/index.ts create mode 100644 src/apps/wallet/src/wallet.routes.tsx diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..86c23c727 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "semi": false +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..e0dc2a50b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "prettier.requireConfig": true +} diff --git a/craco.config.js b/craco.config.js index 8f8e6c8c3..1e2d52d01 100644 --- a/craco.config.js +++ b/craco.config.js @@ -43,6 +43,7 @@ module.exports = { '@gamificationAdmin': resolve('src/apps/gamification-admin/src'), '@talentSearch': resolve('src/apps/talent-search/src'), '@profiles': resolve('src/apps/profiles/src'), + '@wallet': resolve('src/apps/wallet/src'), '@platform': resolve('src/apps/platform/src'), // aliases used in SCSS files diff --git a/package.json b/package.json index 584a7f6a9..c53e8d108 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "react-helmet": "^6.1.0", "react-html-parser": "^2.0.2", "react-markdown": "8.0.6", + "react-otp-input": "^3.1.1", "react-popper": "^2.3.0", "react-redux": "^8.0.4", "react-redux-toastr": "^7.6.10", diff --git a/src/apps/platform/src/platform.routes.tsx b/src/apps/platform/src/platform.routes.tsx index eb69e5806..b6b3792da 100644 --- a/src/apps/platform/src/platform.routes.tsx +++ b/src/apps/platform/src/platform.routes.tsx @@ -9,8 +9,12 @@ import { profilesRoutes } from '~/apps/profiles' import { talentSearchRoutes } from '~/apps/talent-search' import { accountsRoutes } from '~/apps/accounts' import { onboardingRoutes } from '~/apps/onboarding' +import { walletRoutes } from '~/apps/wallet' -const Home: LazyLoadedComponent = lazyLoad(() => import('./routes/home'), 'HomePage') +const Home: LazyLoadedComponent = lazyLoad( + () => import('./routes/home'), + 'HomePage' +) const homeRoutes: ReadonlyArray = [ { @@ -32,6 +36,7 @@ export const platformRoutes: Array = [ ...gamificationAdminRoutes, ...talentSearchRoutes, ...profilesRoutes, + ...walletRoutes, ...accountsRoutes, ...homeRoutes, ] diff --git a/src/apps/wallet/.prettierrc b/src/apps/wallet/.prettierrc new file mode 100644 index 000000000..35288c5c7 --- /dev/null +++ b/src/apps/wallet/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "trailingComma": "all", + "jsxSingleQuote": true, + "jsxBracketSameLine": true, + "printWidth": 120 +} diff --git a/src/apps/wallet/README.md b/src/apps/wallet/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/src/apps/wallet/index.tsx b/src/apps/wallet/index.tsx new file mode 100644 index 000000000..6f39cd49b --- /dev/null +++ b/src/apps/wallet/index.tsx @@ -0,0 +1 @@ +export * from './src' diff --git a/src/apps/wallet/src/WalletApp.tsx b/src/apps/wallet/src/WalletApp.tsx new file mode 100644 index 000000000..aa9aaad7b --- /dev/null +++ b/src/apps/wallet/src/WalletApp.tsx @@ -0,0 +1,20 @@ +import { FC, useContext } from 'react' +import { Outlet, Routes } from 'react-router-dom' + +import { routerContext, RouterContextData } from '~/libs/core' + +import { toolTitle } from './wallet.routes' +import { WalletSwr } from './lib' + +const AccountsApp: FC<{}> = () => { + const { getChildRoutes }: RouterContextData = useContext(routerContext) + + return ( + + + {getChildRoutes(toolTitle)} + + ) +} + +export default AccountsApp diff --git a/src/apps/wallet/src/home/WalletHomePage.tsx b/src/apps/wallet/src/home/WalletHomePage.tsx new file mode 100644 index 000000000..0fafad41b --- /dev/null +++ b/src/apps/wallet/src/home/WalletHomePage.tsx @@ -0,0 +1,19 @@ +import { FC, useContext } from 'react' + +import { profileContext, ProfileContextData } from '~/libs/core' +import { LoadingSpinner } from '~/libs/ui' + +import { WalletLayout } from './page-layout' + +const AccountSettingsPage: FC<{}> = () => { + const { profile, initialized }: ProfileContextData = useContext(profileContext) + + return ( + <> + + {initialized && profile && } + + ) +} + +export default AccountSettingsPage diff --git a/src/apps/wallet/src/home/index.ts b/src/apps/wallet/src/home/index.ts new file mode 100644 index 000000000..f3370c004 --- /dev/null +++ b/src/apps/wallet/src/home/index.ts @@ -0,0 +1 @@ +export { default as WalletHomePage } from './WalletHomePage' diff --git a/src/apps/wallet/src/home/page-layout/WalletLayout.module.scss b/src/apps/wallet/src/home/page-layout/WalletLayout.module.scss new file mode 100644 index 000000000..6ff4b2df4 --- /dev/null +++ b/src/apps/wallet/src/home/page-layout/WalletLayout.module.scss @@ -0,0 +1,5 @@ +@import '@libs/ui/styles/includes'; + +.contentLayoutOuter { + margin: $sp-8 auto !important; +} \ No newline at end of file diff --git a/src/apps/wallet/src/home/page-layout/WalletLayout.tsx b/src/apps/wallet/src/home/page-layout/WalletLayout.tsx new file mode 100644 index 000000000..b7cdb2703 --- /dev/null +++ b/src/apps/wallet/src/home/page-layout/WalletLayout.tsx @@ -0,0 +1,20 @@ +import { FC } from 'react' + +import { UserProfile } from '~/libs/core' +import { ContentLayout } from '~/libs/ui' + +import { WalletTabs } from '../tabs' + +import styles from './WalletLayout.module.scss' + +interface WalletHomeLayoutProps { + profile: UserProfile +} + +const WalletLayout: FC = (props: WalletHomeLayoutProps) => ( + + + +) + +export default WalletLayout diff --git a/src/apps/wallet/src/home/page-layout/index.ts b/src/apps/wallet/src/home/page-layout/index.ts new file mode 100644 index 000000000..df2160a7c --- /dev/null +++ b/src/apps/wallet/src/home/page-layout/index.ts @@ -0,0 +1 @@ +export { default as WalletLayout } from './WalletLayout' diff --git a/src/apps/wallet/src/home/tabs/WalletTabs.module.scss b/src/apps/wallet/src/home/tabs/WalletTabs.module.scss new file mode 100644 index 000000000..30e3c9b0c --- /dev/null +++ b/src/apps/wallet/src/home/tabs/WalletTabs.module.scss @@ -0,0 +1,11 @@ +@import '@libs/ui/styles/includes'; + +.container { + form { + @include ltelg { + :global(.input-el) { + margin-bottom: $sp-4; + } + } + } +} diff --git a/src/apps/wallet/src/home/tabs/WalletTabs.tsx b/src/apps/wallet/src/home/tabs/WalletTabs.tsx new file mode 100644 index 000000000..39e241266 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/WalletTabs.tsx @@ -0,0 +1,51 @@ +import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' +import { useLocation } from 'react-router-dom' + +import { UserProfile } from '~/libs/core' +import { PageTitle, TabsNavbar, TabsNavItem } from '~/libs/ui' + +import { getHashFromTabId, getTabIdFromHash, WalletTabsConfig, WalletTabViews } from './config' +import { PaymentsTab } from './payments' +import { WinningsTab } from './winnings' +import { HomeTab } from './home' +import { TaxFormsTab } from './tax-forms' +import styles from './WalletTabs.module.scss' + +interface WalletHomeProps { + profile: UserProfile +} + +const WalletTabs: FC = (props: WalletHomeProps) => { + const { hash }: { hash: string } = useLocation() + + const activeTabHash: string = useMemo(() => getTabIdFromHash(hash), [hash]) + + const [activeTab, setActiveTab]: [string, Dispatch>] = useState(activeTabHash) + + function handleTabChange(tabId: string): void { + setActiveTab(tabId) + window.location.hash = getHashFromTabId(tabId) + } + + return ( +
+ + + + {[WalletTabsConfig.find((tab: TabsNavItem) => tab.id === activeTab)?.title, 'Wallet', 'Topcoder'].join( + ' | ', + )} + + + {activeTab === WalletTabViews.withdrawalmethods && } + + {activeTab === WalletTabViews.winnings && } + + {activeTab === WalletTabViews.home && } + + {activeTab === WalletTabViews.taxforms && } +
+ ) +} + +export default WalletTabs diff --git a/src/apps/wallet/src/home/tabs/config/index.ts b/src/apps/wallet/src/home/tabs/config/index.ts new file mode 100644 index 000000000..329aa026f --- /dev/null +++ b/src/apps/wallet/src/home/tabs/config/index.ts @@ -0,0 +1 @@ +export * from './wallet-tabs-config' diff --git a/src/apps/wallet/src/home/tabs/config/wallet-tabs-config.ts b/src/apps/wallet/src/home/tabs/config/wallet-tabs-config.ts new file mode 100644 index 000000000..44b46732e --- /dev/null +++ b/src/apps/wallet/src/home/tabs/config/wallet-tabs-config.ts @@ -0,0 +1,55 @@ +import { TabsNavItem } from '~/libs/ui' + +export enum WalletTabViews { + home = '0', + winnings = '1', + taxforms = '2', + withdrawalmethods = '3', +} + +export const WalletTabsConfig: TabsNavItem[] = [ + { + id: WalletTabViews.home, + title: 'Wallet', + }, + { + id: WalletTabViews.winnings, + title: 'Winnings', + }, + { + id: WalletTabViews.withdrawalmethods, + title: 'Withdrawal Methods', + }, + { + id: WalletTabViews.taxforms, + title: 'Tax Forms', + }, +] + +export function getHashFromTabId(tabId: string): string { + switch (tabId) { + case WalletTabViews.home: + return '#home' + case WalletTabViews.winnings: + return '#winnings' + case WalletTabViews.taxforms: + return '#tax-forms' + case WalletTabViews.withdrawalmethods: + return '#withdrawal-methods' + default: + return '#home' + } +} + +export function getTabIdFromHash(hash: string): string { + switch (hash) { + case '#dashboard': + return WalletTabViews.winnings + case '#tax-forms': + return WalletTabViews.taxforms + case '#withdrawal-methods': + return WalletTabViews.withdrawalmethods + default: + return WalletTabViews.home + } +} diff --git a/src/apps/wallet/src/home/tabs/home/Home.module.scss b/src/apps/wallet/src/home/tabs/home/Home.module.scss new file mode 100644 index 000000000..b58cd7ece --- /dev/null +++ b/src/apps/wallet/src/home/tabs/home/Home.module.scss @@ -0,0 +1,39 @@ +@import '@libs/ui/styles/includes'; + +.container { + background-color: $black-5; + padding: 100px 32px; + margin: $sp-8 0; + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 50px; + box-sizing: border-box; +} + +.banner { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + & > * { + flex: 1 1 auto; + display: flex; + justify-content: center; + align-items: center; + } +} + +@media (max-width: 768px) { + .banner { + flex-direction: column; + } +} + +.info-row-container { + display: flex; + flex-direction: column; + gap: 20px; + padding-left: 50px; + padding-right: 50px; +} diff --git a/src/apps/wallet/src/home/tabs/home/HomeTab.tsx b/src/apps/wallet/src/home/tabs/home/HomeTab.tsx new file mode 100644 index 000000000..e37eefb93 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/home/HomeTab.tsx @@ -0,0 +1,122 @@ +/* eslint-disable react/jsx-wrap-multilines */ +import { FC, useEffect, useState } from 'react' + +import { UserProfile } from '~/libs/core' +import { IconOutline, LinkButton, LoadingCircles } from '~/libs/ui' + +import { Balance, WalletDetails } from '../../../lib/models/WalletDetails' +import { getWalletDetails } from '../../../lib/services/wallet' +import { InfoRow } from '../../../lib' +import { BannerImage, BannerText } from '../../../lib/assets/home' +import Chip from '../../../lib/components/chip/Chip' + +import styles from './Home.module.scss' + +interface HomeTabProps { + profile: UserProfile +} + +const HomeTab: FC = () => { + const [walletDetails, setWalletDetails] = useState(undefined) + const [isLoading, setIsLoading] = useState(false) + const [balanceSum, setBalanceSum] = useState(0) + const [error, setError] = useState(undefined) + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const fetchWalletDetails = async () => { + setIsLoading(true) + try { + const details = await getWalletDetails() + setWalletDetails(details) + } catch (apiError) { + setError('Error fetching wallet details') + } + + setIsLoading(false) + } + + fetchWalletDetails() + }, []) + + useEffect(() => { + if (walletDetails) { + setBalanceSum( + walletDetails.account.balances.reduce((sum: number, balance: Balance) => sum + balance.amount, 0), + ) + } + }, [walletDetails]) + + if (error) { + return
{error}
+ } + + return ( +
+
+ + +
+ {isLoading && } + {!isLoading && ( +
+ + } + /> + {!walletDetails?.withdrawalMethod.isSetupComplete && ( + + ) + } + action={ + + } + /> + )} + + {!walletDetails?.taxForm.isSetupComplete && ( + } + action={ + + } + /> + )} +
+ )} +
+ ) +} + +export default HomeTab diff --git a/src/apps/wallet/src/home/tabs/home/index.ts b/src/apps/wallet/src/home/tabs/home/index.ts new file mode 100644 index 000000000..fff571480 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/home/index.ts @@ -0,0 +1 @@ +export { default as HomeTab } from './HomeTab' diff --git a/src/apps/wallet/src/home/tabs/index.ts b/src/apps/wallet/src/home/tabs/index.ts new file mode 100644 index 000000000..40e188685 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/index.ts @@ -0,0 +1 @@ +export { default as WalletTabs } from './WalletTabs' diff --git a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss new file mode 100644 index 000000000..11c8d3aa7 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss @@ -0,0 +1,117 @@ +@import '@libs/ui/styles/includes'; + +.container { + background-color: $black-5; + padding: $sp-6; + margin: $sp-8 0; + border-radius: 6px; + + @include ltelg { + padding: $sp-4; + } + + .paymentsHeader { + display: flex; + justify-content: flex-start; + gap: 5px; + align-items: center; + + @include ltelg { + flex-direction: column; + } + + .managePaymentsLink { + font-weight: $font-weight-bold; + color: $turq-160; + display: flex; + align-items: center; + + @include ltelg { + margin-top: $sp-4; + } + + svg { + margin-left: $sp-2; + max-width: 100%; + } + } + } + + .content { + background-color: $tc-white; + border-radius: 4px; + margin-top: $sp-8; + + .confirmSelectionReset { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: $sp-8; + + @include ltelg { + flex-direction: column; + align-items: flex-start; + + button { + margin-top: $sp-4; + } + } + } + + .providerContainer { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-top: $sp-8; + margin-bottom: $sp-4; + + .alternateProviderButton { + padding-left: 0px !important; + padding-right: 0px !important; + } + } + + .providersSingleRow { + display: grid; + grid-template-columns: repeat(1, 1fr); + width: 100%; + } + + .providersStacked { + display: grid; + gap: $sp-4; + grid-template-columns: repeat(2, 1fr); + } + + .providerSubmitted { + margin-top: $sp-8; + + @include ltelg { + flex-direction: column; + } + + .providerSubmittedIcon { + background: linear-gradient(264.69deg, #198807 2.17%, #017c6d 97.49%); + padding: $sp-4; + border-radius: 4px; + width: 64px; + height: 64px; + color: $tc-white; + margin-right: $sp-4; + + @include ltelg { + margin-bottom: $sp-4; + } + } + + button { + align-self: center; + + @include ltelg { + align-self: flex-start; + margin-top: $sp-4; + } + } + } + } +} diff --git a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx new file mode 100644 index 000000000..fae0e2dbc --- /dev/null +++ b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx @@ -0,0 +1,281 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable react/jsx-no-bind */ +import { FC, useEffect, useState } from 'react' + +import { Button, Collapsible, LoadingCircles } from '~/libs/ui' +import { UserProfile } from '~/libs/core' +import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/solid' + +import { Chip, IconDollar, IconSpeed, IconWorld, PayoneerLogo, PayPalLogo } from '../../../lib' +import { confirmPaymentProvider, getUserPaymentProviders, setPaymentProvider } from '../../../lib/services/wallet' +import { PaymentProvider, SetPaymentProviderResponse } from '../../../lib/models/PaymentProvider' +import { PaymentProviderCard } from '../../../lib/components/payment-provider-card' +import { OtpModal } from '../../../lib/components/otp-modal' + +import { PaymentInfoModal } from './payment-info-modal' +import styles from './PaymentsTab.module.scss' + +const PAYMENT_PROVIDER_DETAILS = { + Payoneer: { + details: [ + { + icon: , + label: 'FEES', + value: '$0-$3 + Currency Conversion Rates May Apply', + }, + { + icon: , + label: 'COUNTRIES', + value: 'Available in 150+ countries', + }, + { + icon: , + label: 'SPEED', + value: 'Up to 1 Business Day', + }, + ], + logo: , + }, + Paypal: { + details: [ + { + icon: , + label: 'FEES', + value: '3.49% + an international fee (non US) + a fixed fee depending upon currency', + }, + { + icon: , + label: 'COUNTRIES', + value: 'Available in 200+ countries', + }, + { + icon: , + label: 'SPEED', + value: 'Up to 1 Business Day', + }, + ], + logo: , + }, +} + +interface PaymentsTabProps { + profile: UserProfile +} + +const PaymentsTab: FC = (props: PaymentsTabProps) => { + const [userPaymentProvider, setUserPaymentProvider] = useState(undefined) + const [setupRequired, setSetupRequired] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(undefined) + const [selectedPaymentProvider, setSelectedPaymentProvider] = useState(undefined) + const [providerToSet, setProviderToSet] = useState(undefined) + const [transactionId, setTransactionId] = useState(undefined) + const [registrationLink, setRegistrationLink] = useState(undefined) + const [providerStatus, setProviderStatus] = useState(undefined) + const [showAlternateProvider, setShowAlternateProvider] = useState(false) + + const fetchPaymentProviders = async () => { + setIsLoading(true) + setError(undefined) + + try { + const providers = await getUserPaymentProviders() + + const status = providers && providers.length > 0 ? providers[0].status : undefined + + setSetupRequired(providers.length === 0) + setUserPaymentProvider(status !== undefined ? providers[0] : undefined) + setProviderStatus(status) + } catch (apiError) { + setError('Error fetching payment providers') + setUserPaymentProvider(undefined) + } + + setIsLoading(false) + } + + useEffect(() => { + fetchPaymentProviders() + }, []) + + useEffect(() => { + if (providerStatus === 'OTP_VERIFIED') { + const queryParams = new URLSearchParams(window.location.search) + const code = queryParams.get('code') + + if (code) { + const storedTransactionId = localStorage.getItem('transactionId') + if (storedTransactionId) { + confirmPaymentProvider('Paypal', code, storedTransactionId) + .then((response: any) => { + console.log(response) + fetchPaymentProviders() + }) + .catch((err: any) => { + console.log(err) + }) + .finally(() => { + localStorage.removeItem('transactionId') + }) + } + } + } + }, [providerStatus]) + + function onProviderSelected(provider: string): void { + setSelectedPaymentProvider(provider) + } + + function renderProviders(): JSX.Element { + return ( +
+ onProviderSelected('Payoneer')} + /> + onProviderSelected('Paypal')} + /> +
+ ) + } + + function renderConnectedProvider(): JSX.Element | undefined { + if (userPaymentProvider) { + return ( +
+

Chosen Payment Provider

+
+ +
+
+ ) + } + + return undefined + } + + return ( +
+
+

WITHDRAWAL METHODS

+ {!isLoading && setupRequired && } +
+ +
+ PAYMENT PROVIDER}> +

+ Topcoder is partnered with several payment providers to send payments to our community members. + Once a provider is set up, payments will be routed to your selected payment provider at the + completion of work. Currently, members can be paid through one of the following providers: + Payoneer® or PayPal®. +

+ + {isLoading && } + + {!isLoading && setupRequired && renderProviders()} + {!isLoading && !setupRequired && renderConnectedProvider()} + +

+ Provider details are based on the latest information from their official sites; we advise + confirming the current terms directly before finalizing your payment option. +

+
+
+ + {selectedPaymentProvider && ( + { + setSelectedPaymentProvider(undefined) + }} + handlePaymentSelection={(provider: string) => { + setSelectedPaymentProvider(undefined) + const details: any = {} + + if (provider === 'Payoneer') { + details.payeeId = `${props.profile.userId}` + } + + setPaymentProvider(details, provider, true) + .then((response: SetPaymentProviderResponse) => { + localStorage.setItem('transactionId', response.transactionId) + setTransactionId(response.transactionId) + setRegistrationLink(response.registrationLink) + setProviderToSet(provider) + }) + .catch((err: any) => { + console.log(err) + }) + }} + /> + )} + {providerToSet && transactionId && ( + { + setProviderToSet(undefined) + }} + onResendClick={() => {}} + onOtpVerified={() => { + window.open(registrationLink, '_blank') + + setProviderToSet(undefined) + setRegistrationLink(undefined) + + fetchPaymentProviders() + }} + /> + )} +
+ ) +} + +export default PaymentsTab diff --git a/src/apps/wallet/src/home/tabs/payments/index.ts b/src/apps/wallet/src/home/tabs/payments/index.ts new file mode 100644 index 000000000..8dc41bc69 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/payments/index.ts @@ -0,0 +1 @@ +export { default as PaymentsTab } from './PaymentsTab' diff --git a/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.module.scss b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.module.scss new file mode 100644 index 000000000..3917b7e01 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.module.scss @@ -0,0 +1,13 @@ +@import '@libs/ui/styles/includes'; + +.infoModal { + :global(.react-responsive-modal-closeButton) { + display: flex; + } + + .modalContent { + display: flex; + flex-direction: column; + gap: 25px; + } +} diff --git a/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx new file mode 100644 index 000000000..43fb17f0c --- /dev/null +++ b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx @@ -0,0 +1,72 @@ +/* eslint-disable react/jsx-wrap-multilines */ +/* eslint-disable react/jsx-no-bind */ +import { FC } from 'react' + +import { BaseModal, Button } from '~/libs/ui' +import { PayoneerLogo, PayPalLogo } from '~/apps/accounts/src/lib' + +import styles from './PaymentInfoModal.module.scss' + +interface PaymentInfoModalProps { + selectedPaymentProvider: string + handlePaymentSelection: (provider: string) => void + handleModalClose: () => void +} + +function renderPayoneer(): JSX.Element { + return ( + <> + +

+ You can elect to receive payments through Payoneer either to your Payoneer prepaid MasterCard or by + using their Global Bank Transfer service. The Payoneer Bank Transfer Service offers a local bank + transfer option (where available) and a wire transfer option. Certain fees may apply. +

+

+ You will be directed to Payoneer's website in a new tab to complete your the connection. Please make + sure your account is fully verified to ensure withdrawal success.{' '} + You can return here after finishing up on Payoneer's site. +

+ + ) +} + +function renderPaypal(): JSX.Element { + return ( + <> + +

You can elect to receive payments deposited directly to your PayPal account. Certain fees may apply.

+

+ You will be directed to PayPal's website in a new tab to complete your the connection. Please make sure + your account is fully verified to ensure withdrawal success. You can return here after finishing up on + PayPal's site. +

+ + ) +} + +const PaymentInfoModal: FC = (props: PaymentInfoModalProps) => ( + { + props.handlePaymentSelection(props.selectedPaymentProvider) + }} + /> + } + onClose={props.handleModalClose} + open + size='body' + title='CONNECT PAYMENT PROVIDER ACCOUNT' + classNames={{ modal: styles.infoModal }}> +
+ {props.selectedPaymentProvider === 'Payoneer' ? renderPayoneer() : renderPaypal()} +
+
+) + +export default PaymentInfoModal diff --git a/src/apps/wallet/src/home/tabs/payments/payment-info-modal/index.ts b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/index.ts new file mode 100644 index 000000000..430ebb622 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/index.ts @@ -0,0 +1 @@ +export { default as PaymentInfoModal } from './PaymentInfoModal' diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss new file mode 100644 index 000000000..c553f634a --- /dev/null +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss @@ -0,0 +1,45 @@ +@import '@libs/ui/styles/includes'; + +.container { + background-color: $black-5; + padding: $sp-6; + margin: $sp-8 0; + border-radius: 6px; + + @include ltelg { + padding: $sp-4; + } + + .header { + display: flex; + justify-content: flex-start; + gap: 5px; + align-items: center; + + @include ltelg { + flex-direction: column; + } + } + + .content { + background-color: $tc-white; + border-radius: 4px; + margin-top: $sp-8; + + .contentTitle { + margin-bottom: $sp-8; + } + + .singleRow { + display: grid; + grid-template-columns: repeat(1, 1fr); + width: 100%; + } + + .stacked { + display: grid; + gap: $sp-4; + grid-template-columns: repeat(2, 1fr); + } + } +} diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx new file mode 100644 index 000000000..40ca36647 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx @@ -0,0 +1,194 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable react/jsx-no-bind */ +import { FC, useEffect, useState } from 'react' + +import { Collapsible, LoadingCircles } from '~/libs/ui' +import { UserProfile } from '~/libs/core' + +import { Chip } from '../../../lib' +import { OtpModal } from '../../../lib/components/otp-modal' +import { TaxFormCard } from '../../../lib/components/tax-form-card' +import { IconUS, IconWorld } from '../../../lib/assets/tax-forms' +import { getUserTaxFormDetails, setupTaxForm } from '../../../lib/services/wallet' +import { SetupTaxFormResponse, TaxForm } from '../../../lib/models/TaxForm' +import { TaxFormDetail } from '../../../lib/components/tax-form-detail' + +import styles from './TaxFormsTab.module.scss' + +const TAX_FORM_DETAILS = [ + { + completionLabel: 'Complete Form W-9', + completionLink: 'https://www.irs.gov/pub/irs-pdf/fw9.pdf', + formDescription: 'For individuals who are a US citizen or other US person (such as a resident alien).', + formTitle: 'TAX FORM W-9', + icon: , + id: 'W-9', + instructionsLabel: 'Instructions', + instructionsLink: 'https://www.irs.gov/pub/irs-pdf/iw9.pdf', + reasonDescription: + // eslint-disable-next-line max-len + 'Topcoder must receive your correctly completed W-9 to report to the IRS income paid to you. Topcoder’s policy is not to issue payments to US members until a properly completed Form W-9 is received from the member.', + reasonTitle: 'Why do I need to complete Form W-9?', + }, + { + additionalInfo: { + // eslint-disable-next-line max-len + note: 'Topcoder’s policy is not to issue payments to foreign members until a properly completed Form W-8BEN is received from the member.', + purpose: { + points: [ + 'Establish that you are not a U.S. person', + 'Claim that you are the beneficial owner of the income for which Form W-8BEN is being provided', + ], + title: 'The W-8BEN is required to', + }, + }, + completionLabel: 'Complete Form W-8BEN', + completionLink: 'https://www.irs.gov/pub/irs-pdf/fw8ben.pdf', + formDescription: + // eslint-disable-next-line max-len + 'For individuals who are NOT a US citizen or other US person (such as a foreign person, non-resident alien or foreign national).', + formTitle: 'TAX FORM W-8BEN', + icon: , + id: 'W-8BEN', + instructionsLabel: 'Instructions', + instructionsLink: 'https://www.irs.gov/pub/irs-pdf/iw8ben.pdf', + reasonDescription: + // eslint-disable-next-line max-len + 'Under current IRS guidance, foreign persons performing services outside of the U.S. are not subject to income tax withholding. However, Topcoder requires all such members to provide a properly filled out W-8BEN prior to issuing payment. In addition, prize money paid to foreign persons who are not performing services (such as winning an SRM competition) is subject to withholding taxes.', + reasonTitle: 'Why do I need to complete Form W-8BEN?', + }, + // Add more tax forms if needed +] + +interface TaxFormsTabProps { + profile: UserProfile +} + +const PaymentsTab: FC = (props: TaxFormsTabProps) => { + const [setupRequired, setSetupRequired] = useState(undefined) + const [taxForm, setTaxForm] = useState(undefined) + const [isLoading, setIsLoading] = useState(false) + const [taxFormToSetup, setTaxFormToSetup] = useState(undefined) + const [taxFormSetupData, setTaxFormSetupData] = useState(undefined) + + const fetchUserTaxForms = async () => { + setIsLoading(true) + + try { + const taxForms = await getUserTaxFormDetails() + + if (taxForms.length === 0) { + setSetupRequired(true) + } else { + setSetupRequired(false) + setTaxForm(taxForms[0]) + } + } catch (apiError) { + // setUserPaymentProvider(undefined) + } + + setIsLoading(false) + } + + useEffect(() => { + fetchUserTaxForms() + }, []) + + function renderAllTaxForms(): JSX.Element { + return ( +
+ {TAX_FORM_DETAILS.map((taxForm) => ( + { + try { + const setupTaxFormResponse = await setupTaxForm(`${props.profile.userId}`, taxForm.id) + setTaxFormSetupData(setupTaxFormResponse) + + fetchUserTaxForms() + } catch (err) { + console.log('Error setting up tax form', err) + } + }} + /> + ))} +
+ ) + } + + function renderSubmittedTaxForm(): JSX.Element | undefined { + if (taxForm === undefined) { + return undefined + } + + const key = taxForm.taxForm.name + + const date = new Date(taxForm.dateFiled) + date.setFullYear(date.getFullYear() + 3) + const options: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' } + + const formattedDate = date.toLocaleDateString('en-US', options) + + return ( + + ) + } + + return ( +
+
+

TAX FORM SUBMISSION

+ {!isLoading && setupRequired === true && } +
+ +
+ TAX FORM REQUIREMENTS}> +

+ All members must have a tax form on file before they can be paid. There are two options: a W-9 + or a W-8BEN. We will walk you through completing your tax form. +

+ + {isLoading && } + + {!isLoading && setupRequired === true && renderAllTaxForms()} + {!isLoading && setupRequired === false && renderSubmittedTaxForm()} +
+
+ + {taxFormSetupData !== undefined && ( + { + setTaxFormToSetup(undefined) + }} + onResendClick={() => { + // TODO: Call resend OTP API + }} + onOtpVerified={(eSignLink: string) => { + console.log('eSignLink', eSignLink) + window.open(taxFormSetupData.eSignLink, '_blank') + setTaxFormSetupData(undefined) + }} + /> + )} +
+ ) +} + +export default PaymentsTab diff --git a/src/apps/wallet/src/home/tabs/tax-forms/index.ts b/src/apps/wallet/src/home/tabs/tax-forms/index.ts new file mode 100644 index 000000000..08eb9c245 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/tax-forms/index.ts @@ -0,0 +1 @@ +export { default as TaxFormsTab } from './TaxFormsTab' diff --git a/src/apps/wallet/src/home/tabs/winnings/Winnings.module.scss b/src/apps/wallet/src/home/tabs/winnings/Winnings.module.scss new file mode 100644 index 000000000..a728de933 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/winnings/Winnings.module.scss @@ -0,0 +1,29 @@ +@import '@libs/ui/styles/includes'; + +.container { + background-color: $black-5; + padding: $sp-6; + margin: $sp-8 0; + border-radius: 6px; + + @include ltelg { + padding: $sp-4; + } + + .header { + display: flex; + justify-content: flex-start; + gap: 5px; + align-items: center; + + @include ltelg { + flex-direction: column; + } + } + + .content { + background-color: $tc-white; + border-radius: 4px; + margin-top: $sp-4; + } +} diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx new file mode 100644 index 000000000..63ce177e6 --- /dev/null +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -0,0 +1,99 @@ +import React, { FC, useEffect, useMemo } from 'react' + +import { LoadingCircles, Table, TableColumn } from '~/libs/ui' +import { UserProfile } from '~/libs/core' +import { getPayments, processPayments } from '../../../lib/services/wallet' +import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' + +import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' +import styles from './Winnings.module.scss' + +interface ListViewProps { + profile: UserProfile +} + +function formatIOSDateString(iosDateString: string): string { + const date = new Date(iosDateString) + + if (Number.isNaN(date.getTime())) { + throw new Error('Invalid date string') + } + + const options: Intl.DateTimeFormatOptions = { + day: 'numeric', + hour: '2-digit', + hour12: true, + minute: '2-digit', + month: 'long', + second: '2-digit', + year: 'numeric', + } + + return date.toLocaleDateString(undefined, options) +} + +const ListView: FC = (props: ListViewProps) => { + const [winnings, setWinnings] = React.useState>([]) + const [isLoading, setIsLoading] = React.useState(false) + + const fetchWinnings = async () => { + setIsLoading(true) + try { + const payments = await getPayments(`${props.profile.userId}`) + setWinnings(convertToWinnings(payments)) + } catch (apiError) {} + + setIsLoading(false) + } + + useEffect(() => { + fetchWinnings() + }, [props.profile.userId]) + + function convertToWinnings(payments: WinningDetail[]): Winning[] { + const tempWinnings: Winning[] = [] + + payments.forEach((payment: WinningDetail) => { + payment.details.forEach((detail) => { + const winning: Winning = { + createDate: formatIOSDateString(payment.createdAt), + datePaid: '', + description: payment.description, + id: payment.id, + installment: detail.installmentNumber, + netPayment: `${detail.netAmount}`, + releaseDate: '', + status: detail.status, + type: payment.type, + } + tempWinnings.push(winning) + }) + }) + + return tempWinnings + } + + return ( +
+
+

Winnings

+
+
+ {isLoading && } + {!isLoading && ( + { + const ids = Object.keys(paymentIds) + await processPayments(ids) + + fetchWinnings() + }} + /> + )} +
+
+ ) +} + +export default ListView diff --git a/src/apps/wallet/src/home/tabs/winnings/index.ts b/src/apps/wallet/src/home/tabs/winnings/index.ts new file mode 100644 index 000000000..615080cbb --- /dev/null +++ b/src/apps/wallet/src/home/tabs/winnings/index.ts @@ -0,0 +1 @@ +export { default as WinningsTab } from './WinningsTab' diff --git a/src/apps/wallet/src/index.ts b/src/apps/wallet/src/index.ts new file mode 100644 index 000000000..eaeccab12 --- /dev/null +++ b/src/apps/wallet/src/index.ts @@ -0,0 +1 @@ +export { walletRoutes, rootRoute as walletRootRoute } from './wallet.routes' diff --git a/src/apps/wallet/src/lib/assets/home/banner-image.svg b/src/apps/wallet/src/lib/assets/home/banner-image.svg new file mode 100644 index 000000000..91a984724 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/home/banner-image.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/home/banner-text.svg b/src/apps/wallet/src/lib/assets/home/banner-text.svg new file mode 100644 index 000000000..3953d78eb --- /dev/null +++ b/src/apps/wallet/src/lib/assets/home/banner-text.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/wallet/src/lib/assets/home/index.ts b/src/apps/wallet/src/lib/assets/home/index.ts new file mode 100644 index 000000000..5e4f6eac1 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/home/index.ts @@ -0,0 +1,4 @@ +import { ReactComponent as BannerText } from './banner-text.svg' +import { ReactComponent as BannerImage } from './banner-image.svg' + +export { BannerText, BannerImage } diff --git a/src/apps/wallet/src/lib/assets/index.ts b/src/apps/wallet/src/lib/assets/index.ts new file mode 100644 index 000000000..3dc6095bc --- /dev/null +++ b/src/apps/wallet/src/lib/assets/index.ts @@ -0,0 +1,5 @@ +export * from './security' +export * from './preferences' +export * from './payments' +export * from './tools' +export * from './tcandyou' diff --git a/src/apps/wallet/src/lib/assets/payments/PayPal_logo_gray.svg b/src/apps/wallet/src/lib/assets/payments/PayPal_logo_gray.svg new file mode 100644 index 000000000..8ed6edcec --- /dev/null +++ b/src/apps/wallet/src/lib/assets/payments/PayPal_logo_gray.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/payments/Payoneer_log_gray.svg b/src/apps/wallet/src/lib/assets/payments/Payoneer_log_gray.svg new file mode 100644 index 000000000..13a13fa00 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/payments/Payoneer_log_gray.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/wallet/src/lib/assets/payments/Payoneer_logo_color.svg b/src/apps/wallet/src/lib/assets/payments/Payoneer_logo_color.svg new file mode 100644 index 000000000..866975133 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/payments/Payoneer_logo_color.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/payments/Paypal_logo_color.svg b/src/apps/wallet/src/lib/assets/payments/Paypal_logo_color.svg new file mode 100644 index 000000000..957a29773 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/payments/Paypal_logo_color.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/payments/Western_Union_Logo_gray.svg b/src/apps/wallet/src/lib/assets/payments/Western_Union_Logo_gray.svg new file mode 100644 index 000000000..8cb8fcaf8 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/payments/Western_Union_Logo_gray.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/payments/icon-dollar.svg b/src/apps/wallet/src/lib/assets/payments/icon-dollar.svg new file mode 100644 index 000000000..ac96c7b15 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/payments/icon-dollar.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/wallet/src/lib/assets/payments/icon-speed.svg b/src/apps/wallet/src/lib/assets/payments/icon-speed.svg new file mode 100644 index 000000000..6d782e8fa --- /dev/null +++ b/src/apps/wallet/src/lib/assets/payments/icon-speed.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/wallet/src/lib/assets/payments/icon-world.svg b/src/apps/wallet/src/lib/assets/payments/icon-world.svg new file mode 100644 index 000000000..a1d05cdfe --- /dev/null +++ b/src/apps/wallet/src/lib/assets/payments/icon-world.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/wallet/src/lib/assets/payments/index.ts b/src/apps/wallet/src/lib/assets/payments/index.ts new file mode 100644 index 000000000..cd439a908 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/payments/index.ts @@ -0,0 +1,8 @@ +import { ReactComponent as PayoneerLogo } from './Payoneer_logo_color.svg' +import { ReactComponent as PayPalLogo } from './Paypal_logo_color.svg' +import { ReactComponent as WesternUnionLogo } from './Western_Union_Logo_gray.svg' +import { ReactComponent as IconDollar } from './icon-dollar.svg' +import { ReactComponent as IconWorld } from './icon-world.svg' +import { ReactComponent as IconSpeed } from './icon-speed.svg' + +export { IconDollar, IconWorld, IconSpeed, PayoneerLogo, PayPalLogo, WesternUnionLogo } diff --git a/src/apps/wallet/src/lib/assets/preferences/email.svg b/src/apps/wallet/src/lib/assets/preferences/email.svg new file mode 100755 index 000000000..84a793d3b --- /dev/null +++ b/src/apps/wallet/src/lib/assets/preferences/email.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/wallet/src/lib/assets/preferences/forum.svg b/src/apps/wallet/src/lib/assets/preferences/forum.svg new file mode 100755 index 000000000..92e12622e --- /dev/null +++ b/src/apps/wallet/src/lib/assets/preferences/forum.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/wallet/src/lib/assets/preferences/index.ts b/src/apps/wallet/src/lib/assets/preferences/index.ts new file mode 100644 index 000000000..bf54f01b2 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/preferences/index.ts @@ -0,0 +1,7 @@ +import { ReactComponent as ForumIcon } from './forum.svg' +import { ReactComponent as EmailIcon } from './email.svg' + +export { + EmailIcon, + ForumIcon, +} diff --git a/src/apps/wallet/src/lib/assets/security/apple-store.svg b/src/apps/wallet/src/lib/assets/security/apple-store.svg new file mode 100644 index 000000000..e0ab6869e --- /dev/null +++ b/src/apps/wallet/src/lib/assets/security/apple-store.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/wallet/src/lib/assets/security/credential.png b/src/apps/wallet/src/lib/assets/security/credential.png new file mode 100644 index 0000000000000000000000000000000000000000..914e8d04f84699f81b11476072af3b1788b1750e GIT binary patch literal 47241 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 literal 0 HcmV?d00001 diff --git a/src/apps/wallet/src/lib/assets/security/dicelogo.png b/src/apps/wallet/src/lib/assets/security/dicelogo.png new file mode 100644 index 0000000000000000000000000000000000000000..723b63b2e6750ae6584a86cb94ba3af2661f00da GIT binary patch literal 3067 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

Pt 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 ZmE{2L6bCHqzs&JBa?;A5swGW={|Azy8DRhb literal 0 HcmV?d00001 diff --git a/src/apps/wallet/src/lib/assets/security/dicelogosmall.png b/src/apps/wallet/src/lib/assets/security/dicelogosmall.png new file mode 100644 index 0000000000000000000000000000000000000000..c2be4e4a804a6d5327d68c4ba816e25f5a1c469c GIT binary patch literal 6681 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 literal 0 HcmV?d00001 diff --git a/src/apps/wallet/src/lib/assets/security/google-play.png b/src/apps/wallet/src/lib/assets/security/google-play.png new file mode 100644 index 0000000000000000000000000000000000000000..5311fbf0314dff6dd64b120666457eb7eaadc86e GIT binary patch literal 7000 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 q85kH83aVt~BN>0000 + + + \ No newline at end of file diff --git a/src/apps/wallet/src/lib/assets/security/unsuccessful.svg b/src/apps/wallet/src/lib/assets/security/unsuccessful.svg new file mode 100644 index 000000000..29ae859bd --- /dev/null +++ b/src/apps/wallet/src/lib/assets/security/unsuccessful.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/apps/wallet/src/lib/assets/tax-forms/ic-check-circle.svg b/src/apps/wallet/src/lib/assets/tax-forms/ic-check-circle.svg new file mode 100644 index 000000000..d64ba990e --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tax-forms/ic-check-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/wallet/src/lib/assets/tax-forms/ic-earth.svg b/src/apps/wallet/src/lib/assets/tax-forms/ic-earth.svg new file mode 100644 index 000000000..998adac47 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tax-forms/ic-earth.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/tax-forms/ic-us.svg b/src/apps/wallet/src/lib/assets/tax-forms/ic-us.svg new file mode 100644 index 000000000..08c6c8a09 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tax-forms/ic-us.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/tax-forms/index.ts b/src/apps/wallet/src/lib/assets/tax-forms/index.ts new file mode 100644 index 000000000..8fe4627a1 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tax-forms/index.ts @@ -0,0 +1,5 @@ +import { ReactComponent as IconWorld } from './ic-earth.svg' +import { ReactComponent as IconUS } from './ic-us.svg' +import { ReactComponent as IconCheckCircle } from './ic-check-circle.svg' + +export { IconUS, IconWorld, IconCheckCircle } diff --git a/src/apps/wallet/src/lib/assets/tcandyou/data_science.svg b/src/apps/wallet/src/lib/assets/tcandyou/data_science.svg new file mode 100644 index 000000000..4a5565e0e --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tcandyou/data_science.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/wallet/src/lib/assets/tcandyou/design.svg b/src/apps/wallet/src/lib/assets/tcandyou/design.svg new file mode 100644 index 000000000..2e344e11b --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tcandyou/design.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/wallet/src/lib/assets/tcandyou/develop.svg b/src/apps/wallet/src/lib/assets/tcandyou/develop.svg new file mode 100644 index 000000000..fe60ce444 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tcandyou/develop.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/wallet/src/lib/assets/tcandyou/ico-ethereum.png b/src/apps/wallet/src/lib/assets/tcandyou/ico-ethereum.png new file mode 100755 index 0000000000000000000000000000000000000000..907388abf20f521e040aaa6251f1b9e3aa2cb8fe GIT binary patch literal 2649 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 LNkvXXu0mjfJk8&7 literal 0 HcmV?d00001 diff --git a/src/apps/wallet/src/lib/assets/tcandyou/index.ts b/src/apps/wallet/src/lib/assets/tcandyou/index.ts new file mode 100644 index 000000000..7e4b90ac6 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tcandyou/index.ts @@ -0,0 +1,15 @@ +import { ReactComponent as DevelopmentTrackIcon } from './develop.svg' +import { ReactComponent as DesignTrackIcon } from './design.svg' +import { ReactComponent as DataScienceTrackIcon } from './data_science.svg' +import ethereumCommunityImage from './ico-ethereum.png' +import ibmCommunityImage from './ico-ibmcloud.png' +import veteransCommunityImage from './ico-veteran.png' + +export { + ethereumCommunityImage, + ibmCommunityImage, + veteransCommunityImage, + DesignTrackIcon, + DataScienceTrackIcon, + DevelopmentTrackIcon, +} diff --git a/src/apps/wallet/src/lib/assets/tools/Financial Institution.svg b/src/apps/wallet/src/lib/assets/tools/Financial Institution.svg new file mode 100644 index 000000000..5736f8c0f --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/Financial Institution.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/apps/wallet/src/lib/assets/tools/Internet Service Provider.svg b/src/apps/wallet/src/lib/assets/tools/Internet Service Provider.svg new file mode 100644 index 000000000..5736f8c0f --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/Internet Service Provider.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/apps/wallet/src/lib/assets/tools/Mobile Carrier.svg b/src/apps/wallet/src/lib/assets/tools/Mobile Carrier.svg new file mode 100644 index 000000000..eb23554ab --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/Mobile Carrier.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/apps/wallet/src/lib/assets/tools/Television.svg b/src/apps/wallet/src/lib/assets/tools/Television.svg new file mode 100644 index 000000000..671a335c5 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/Television.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/tools/console.svg b/src/apps/wallet/src/lib/assets/tools/console.svg new file mode 100644 index 000000000..237abfc97 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/console.svg @@ -0,0 +1 @@ +004-consoleCreated with Sketch. diff --git a/src/apps/wallet/src/lib/assets/tools/desktop.svg b/src/apps/wallet/src/lib/assets/tools/desktop.svg new file mode 100644 index 000000000..1755a7581 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/desktop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/wallet/src/lib/assets/tools/index.ts b/src/apps/wallet/src/lib/assets/tools/index.ts new file mode 100644 index 000000000..176aaa7f5 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/index.ts @@ -0,0 +1,31 @@ +import { ReactComponent as SoftwareIcon } from './software.svg' +import { ReactComponent as DesktopIncon } from './desktop.svg' +import { ReactComponent as SmartphoneIcon } from './smartphone.svg' +import { ReactComponent as OtherDeviceIcon } from './other_device.svg' +import { ReactComponent as LaptopIcon } from './laptop.svg' +import { ReactComponent as ConsoleIcon } from './console.svg' +import { ReactComponent as TabletIcon } from './tablet.svg' +import { ReactComponent as WearableIcon } from './wearable.svg' +import { ReactComponent as FinancialInstitutionIcon } from './Financial Institution.svg' +import { ReactComponent as OtherServiceProviderIcon } from './other_service_provider.svg' +import { ReactComponent as TelevisionServiceProviderIcon } from './Television.svg' +import { ReactComponent as MobileCarrierServiceProviderIcon } from './Mobile Carrier.svg' +import { ReactComponent as InternetServiceProviderIcon } from './Internet Service Provider.svg' +import { ReactComponent as SubscriptionsIcon } from './subscription.svg' + +export { + ConsoleIcon, + DesktopIncon, + FinancialInstitutionIcon, + InternetServiceProviderIcon, + MobileCarrierServiceProviderIcon, + LaptopIcon, + OtherDeviceIcon, + OtherServiceProviderIcon, + SmartphoneIcon, + SoftwareIcon, + SubscriptionsIcon, + TabletIcon, + TelevisionServiceProviderIcon, + WearableIcon, +} diff --git a/src/apps/wallet/src/lib/assets/tools/laptop.svg b/src/apps/wallet/src/lib/assets/tools/laptop.svg new file mode 100644 index 000000000..d54366f39 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/laptop.svg @@ -0,0 +1 @@ +notebookCreated with Sketch. diff --git a/src/apps/wallet/src/lib/assets/tools/other_device.svg b/src/apps/wallet/src/lib/assets/tools/other_device.svg new file mode 100644 index 000000000..1fca19866 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/other_device.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/tools/other_service_provider.svg b/src/apps/wallet/src/lib/assets/tools/other_service_provider.svg new file mode 100644 index 000000000..659b14dae --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/other_service_provider.svg @@ -0,0 +1,17 @@ + + + + + +64px_wifi off + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/tools/smartphone.svg b/src/apps/wallet/src/lib/assets/tools/smartphone.svg new file mode 100644 index 000000000..c8b32fa44 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/smartphone.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/wallet/src/lib/assets/tools/software.svg b/src/apps/wallet/src/lib/assets/tools/software.svg new file mode 100644 index 000000000..266fc1aaf --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/software.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/apps/wallet/src/lib/assets/tools/subscription.svg b/src/apps/wallet/src/lib/assets/tools/subscription.svg new file mode 100644 index 000000000..eafb3cfa9 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/subscription.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/wallet/src/lib/assets/tools/tablet.svg b/src/apps/wallet/src/lib/assets/tools/tablet.svg new file mode 100644 index 000000000..fa1a0d651 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/tablet.svg @@ -0,0 +1 @@ + diff --git a/src/apps/wallet/src/lib/assets/tools/wearable.svg b/src/apps/wallet/src/lib/assets/tools/wearable.svg new file mode 100644 index 000000000..646ae6854 --- /dev/null +++ b/src/apps/wallet/src/lib/assets/tools/wearable.svg @@ -0,0 +1 @@ +005-smart-watchCreated with Sketch. diff --git a/src/apps/wallet/src/lib/components/chip/Chip.module.scss b/src/apps/wallet/src/lib/components/chip/Chip.module.scss new file mode 100644 index 000000000..7a78c4b52 --- /dev/null +++ b/src/apps/wallet/src/lib/components/chip/Chip.module.scss @@ -0,0 +1,18 @@ +@import '@libs/ui/styles/includes'; + +.container { + background: #ef476f; + width: 84px; + height: 16px; + padding: 3px 4px 3px 4px; + border-radius: 2px; + gap: 2px; + display: inline-flex; + align-items: center; + justify-content: center; + font-family: $font-roboto; + color: white; + font-size: 11px; + line-height: normal; + text-align: center; +} diff --git a/src/apps/wallet/src/lib/components/chip/Chip.tsx b/src/apps/wallet/src/lib/components/chip/Chip.tsx new file mode 100644 index 000000000..ce17c080a --- /dev/null +++ b/src/apps/wallet/src/lib/components/chip/Chip.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +import styles from './Chip.module.scss' + +interface ChipProps { + text: string +} + +const Chip: React.FC = (props: ChipProps) =>

{props.text}
+ +export default Chip diff --git a/src/apps/wallet/src/lib/components/chip/index.ts b/src/apps/wallet/src/lib/components/chip/index.ts new file mode 100644 index 000000000..877198b11 --- /dev/null +++ b/src/apps/wallet/src/lib/components/chip/index.ts @@ -0,0 +1 @@ +export { default as Chip } from './Chip' diff --git a/src/apps/wallet/src/lib/components/index.ts b/src/apps/wallet/src/lib/components/index.ts new file mode 100644 index 000000000..24a2b7711 --- /dev/null +++ b/src/apps/wallet/src/lib/components/index.ts @@ -0,0 +1,3 @@ +export * from './setting-section' +export * from './info-row' +export * from './chip' diff --git a/src/apps/wallet/src/lib/components/info-row/InfoRow.module.scss b/src/apps/wallet/src/lib/components/info-row/InfoRow.module.scss new file mode 100644 index 000000000..f44d056e0 --- /dev/null +++ b/src/apps/wallet/src/lib/components/info-row/InfoRow.module.scss @@ -0,0 +1,38 @@ +@import '@libs/ui/styles/includes'; + +.info-row { + display: flex; + align-items: center; + background-color: $tc-white; + padding: $sp-4 $sp-8; + border-radius: 8px; +} + +.title { + font-family: $font-roboto; + font-style: normal; + font-weight: 400; + font-size: 20px; + line-height: 26px; + color: $black-100; +} + +.value-action-container { + display: flex; + justify-content: flex-end; + align-items: center; + flex-grow: 1; + padding-left: 10px; + gap: 200px; +} + +.value { + display: flex; + justify-content: flex-end; +} + +.action { + display: flex; + min-width: 200px; + justify-content: flex-end; +} diff --git a/src/apps/wallet/src/lib/components/info-row/InfoRow.tsx b/src/apps/wallet/src/lib/components/info-row/InfoRow.tsx new file mode 100644 index 000000000..8a881a719 --- /dev/null +++ b/src/apps/wallet/src/lib/components/info-row/InfoRow.tsx @@ -0,0 +1,23 @@ +import React from 'react' + +import styles from './InfoRow.module.scss' + +interface InfoRowProps { + title: string + value: React.ReactNode + action?: React.ReactNode +} + +const InfoRow: React.FC = ({ title, value, action }) => { + return ( +
+
{title}
+
+
{value}
+ {action &&
{action}
} +
+
+ ) +} + +export default InfoRow diff --git a/src/apps/wallet/src/lib/components/info-row/index.ts b/src/apps/wallet/src/lib/components/info-row/index.ts new file mode 100644 index 000000000..3d72e3e11 --- /dev/null +++ b/src/apps/wallet/src/lib/components/info-row/index.ts @@ -0,0 +1 @@ +export { default as InfoRow } from './InfoRow' diff --git a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.module.scss b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.module.scss new file mode 100644 index 000000000..e7cfee6f5 --- /dev/null +++ b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.module.scss @@ -0,0 +1,108 @@ +.otp-modal { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; + + .error { + color: red; + } + + .otpInput { + width: 48px !important; + height: 48px; + margin: 5px; + text-align: center; + font-size: 1rem; + border: 2px solid #ccc; + border-radius: 4px; + + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } + + &::-moz-focus-inner { + border: 0; + } + + &[type='number'] { + -moz-appearance: textfield; + } + + &::-ms-clear, + &::-ms-reveal { + display: none; + width: 0; + height: 0; + } + + &[type='number']::placeholder { + color: transparent; + } + + &:focus { + outline: none; + border-color: #007bff; + } + } + + h3 { + font-size: 1.5rem; + text-align: center; + margin-bottom: 20px; + } + + .otpInputStyle { + width: 40px; + height: 50px; + margin: 0 5px; + text-align: center; + font-size: 2rem; + border: 1px solid #ccc; + border-radius: 4px; + + &:focus { + outline: none; + border-color: blue; + } + } + + .otpInputFocusStyle { + border-color: #007bff; + } + + p { + font-size: 0.9rem; + color: #666; + text-align: center; + margin-bottom: 20px; + } + + .resend-btn, + .submit-btn { + padding: 10px 20px; + border: none; + border-radius: 4px; + font-size: 1rem; + cursor: pointer; + margin-bottom: 10px; + transition: background-color 0.3s; + + &:hover { + opacity: 0.8; + } + } + + .resend-btn { + background-color: transparent; + color: #007bff; + } + + .submit-btn { + background-color: #007bff; + color: white; + } +} diff --git a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx new file mode 100644 index 000000000..08b62e994 --- /dev/null +++ b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx @@ -0,0 +1,88 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable react/no-unstable-nested-components */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable react/destructuring-assignment */ +import OTPInput, { InputProps } from 'react-otp-input' +import React from 'react' + +import { BaseModal, LoadingCircles } from '~/libs/ui' +import { verifyOtp } from '~/apps/wallet/src/lib/services/wallet' + +import styles from './OtpModal.module.scss' + +const OtpModal = ({ + isOpen, + key, + transactionId, + onClose, + onResendClick, + onOtpVerified, +}: { + isOpen: boolean + key: string + transactionId: string + onClose: () => void + onResendClick: () => void + onOtpVerified: (key: string) => void +}) => { + const [otp, setOtp] = React.useState('') + const [loading, setLoading] = React.useState(false) + const [error, setError] = React.useState('') + + React.useEffect(() => { + if (!isOpen) { + setOtp('') + setError('') + } + }, [isOpen]) + + const handleChange = (code: string) => { + setOtp(code) + if (code.length === 6) { + setLoading(true) + verifyOtp(transactionId, code) + .then(() => { + setLoading(false) + onOtpVerified(key) + }) + .catch((err: Error) => { + setLoading(false) + setError(err.message) + }) + } else if (code.length < 6) { + setError('') + } + } + + return ( + +
+ {error &&

{error}

} +

+ For added security we’ve sent a 6-digit code to your ***@gmail.com email. The code + expires shortly, so please enter it soon. +

+ } + onChange={handleChange} + inputType='number' + shouldAutoFocus + inputStyle={styles.otpInput} + /> + +

Can't find the code? Check your spam folder.

+ {loading && } + {!loading && ( + + )} +
+
+ ) +} + +export default OtpModal diff --git a/src/apps/wallet/src/lib/components/otp-modal/index.ts b/src/apps/wallet/src/lib/components/otp-modal/index.ts new file mode 100644 index 000000000..9d973de1e --- /dev/null +++ b/src/apps/wallet/src/lib/components/otp-modal/index.ts @@ -0,0 +1 @@ +export { default as OtpModal } from './OtpModal' diff --git a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss new file mode 100644 index 000000000..121018a15 --- /dev/null +++ b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss @@ -0,0 +1,79 @@ +@import '@libs/ui/styles/includes'; + +.card { + display: flex; + flex-direction: column; + align-items: flex-start; + background-color: white; + padding: 16px; + border-radius: 8px; + border: 1px solid #eaeaea; + margin: 16px 0; + + .header { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + .status { + .verifying { + color: $legacy-120; + } + } + } + + .detailContainer { + display: grid; + gap: 4px; + width: 100%; + margin-bottom: 16px; + + &.singleRow { + grid-template-columns: repeat(3, 1fr); + } + + &.stackedRows { + grid-template-columns: 1fr; + .detail:not(:last-child) { + margin-bottom: 8px; + } + } + + .detail { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + + .iconLabelContainer { + display: flex; + } + + .label { + margin-left: 8px; + color: $teal-140; + font-family: Roboto; + font-size: 12px; + font-weight: 700; + line-height: 16px; + letter-spacing: 1px; + text-align: left; + } + + .value { + font-family: Roboto; + font-size: 16px; + font-weight: 400; + line-height: 24px; + letter-spacing: 0px; + text-align: left; + } + } + } + + .footer { + margin-top: 24px; + } +} diff --git a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx new file mode 100644 index 000000000..6f20d40a5 --- /dev/null +++ b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx @@ -0,0 +1,82 @@ +import { Button, PageDivider } from '~/libs/ui' +import { CheckCircleIcon, ExclamationCircleIcon } from '@heroicons/react/solid' + +import { PaymentProvider } from '../../models/PaymentProvider' + +import styles from './PaymentProviderCard.module.scss' + +interface Detail { + icon: React.ReactNode + label: string + value: string +} + +interface PaymentProviderProps { + provider: PaymentProvider + logo: React.ReactNode + details: Detail[] + onConnectClick?: () => void +} + +const PaymentProviderCard: React.FC = (props: PaymentProviderProps) => { + const canConnect = props.provider.status === 'NOT_CONNECTED' + const isVerified = props.provider.status === 'VERIFIED' + const isPending = ['PENDING', 'OTP_VERIFIED'].includes(props.provider.status) + + const PENDING_TO_LABEL_MAP: { + [key: string]: string + } = { + OTP_VERIFIED: 'Verifying', + PENDING: 'Pending OTP Verification', + } + + const detailContainerStyle = { + marginTop: canConnect ? '0px' : '32px', + } + + return ( +
+
+
{props.logo}
+ {(isVerified || isPending) && ( +
+ {isVerified &&
+ )} +
+ + {canConnect && } + +
+ {props.details.map((detail: Detail) => ( +
+
+ {detail.icon} + {detail.label} +
+

{detail.value}

+
+ ))} +
+ + {canConnect && ( +
+
+ )} +
+ ) +} + +export default PaymentProviderCard diff --git a/src/apps/wallet/src/lib/components/payment-provider-card/index.ts b/src/apps/wallet/src/lib/components/payment-provider-card/index.ts new file mode 100644 index 000000000..a3a1c4984 --- /dev/null +++ b/src/apps/wallet/src/lib/components/payment-provider-card/index.ts @@ -0,0 +1 @@ +export { default as PaymentProviderCard } from './PaymentProviderCard' diff --git a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.module.scss b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.module.scss new file mode 100644 index 000000000..8757b2164 --- /dev/null +++ b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.module.scss @@ -0,0 +1,77 @@ +@import '@libs/ui/styles/includes'; + +.tableContainer { + width: 100%; + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + margin: 16px 0; + + th, + td { + text-align: left; + padding: 8px; + border-bottom: 1px solid #eaeaea; + + &:last-child { + width: 50px; + } + } + + th { + background-color: #f5f5f5; + position: sticky; + top: 0; + } + + tbody { + tr { + &:nth-child(odd) { + background-color: #f9f9f9; + } + &:nth-child(even) { + background-color: #ffffff; + } + } + } +} + +.selected { + background-color: #e7f4ff; +} + +.paymentFooter { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + margin-top: 16px; + border-radius: 8px; + background-color: white; + border: 1px solid #eaeaea; + + .total { + font-size: 18px; + font-weight: bold; + + color: #333; + } +} + +@media (max-width: 768px) { + .paymentFooter { + flex-direction: column; + align-items: stretch; + + .total { + margin-bottom: 10px; + } + + .payMeButton { + width: 100%; + } + } +} diff --git a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx new file mode 100644 index 000000000..3cbd7057c --- /dev/null +++ b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx @@ -0,0 +1,111 @@ +import React, { useState } from 'react' + +import { Button } from '~/libs/ui' + +import { Winning } from '../../models/WinningDetail' + +import styles from './PaymentTable.module.scss' + +interface PaymentTableProps { + payments: ReadonlyArray + onPayMeClick: (paymentIds: { [paymentId: string]: boolean }) => void +} +const PaymentsTable: React.FC = (props: PaymentTableProps) => { + const [selectedPayments, setSelectedPayments] = useState<{ [paymentId: string]: boolean }>({}) + + const togglePaymentSelection = (paymentId: string) => { + setSelectedPayments((prevSelected) => ({ + ...prevSelected, + [paymentId]: !prevSelected[paymentId], + })) + } + + const isAllSelected = props.payments.length > 0 && props.payments.every((payment) => selectedPayments[payment.id]) + + const toggleAllPayments = () => { + if (isAllSelected) { + setSelectedPayments({}) + } else { + const newSelections: { [paymentId: string]: boolean } = {} + props.payments.forEach((payment) => { + newSelections[payment.id] = true + }) + setSelectedPayments(newSelections) + } + } + + const calculateTotal = () => + props.payments.reduce((acc: number, payment: Winning) => { + if (selectedPayments[payment.id]) { + return acc + parseFloat(payment.netPayment.replace(/[^0-9.-]+/g, '')) + } + + return acc + }, 0) + + const total = calculateTotal() + + return ( + <> +
+ + + + + + + + + + + + + + + + {props.payments.map((payment) => ( + + + + + + + + + + + + ))} + +
DescriptionTypeInstallmentCreate DateNet PaymentStatusRelease DateDate Paid + +
{payment.description}{payment.type}{payment.installment}{payment.createDate}{payment.netPayment}{payment.status}{payment.releaseDate}{payment.datePaid} + togglePaymentSelection(payment.id)} + aria-label={`Select payment ${payment.id}`} + /> +
+
+
+
Total: ${total.toFixed(2)}
+ +
+ + ) +} + +export default PaymentsTable diff --git a/src/apps/wallet/src/lib/components/setting-section/SettingSection.module.scss b/src/apps/wallet/src/lib/components/setting-section/SettingSection.module.scss new file mode 100644 index 000000000..dd4087c74 --- /dev/null +++ b/src/apps/wallet/src/lib/components/setting-section/SettingSection.module.scss @@ -0,0 +1,26 @@ +@import '@libs/ui/styles/includes'; + +.container { + display: flex; + padding: $sp-4; + border: 1px solid $black-20; + border-radius: 8px; + margin-top: $sp-4; + flex-wrap: wrap; + + .contentMiddle { + display: flex; + flex-direction: column; + flex: 1; + align-self: center; + + @include ltelg { + margin-right: $sp-4; + } + + .infoText { + color: #767676; + max-width: 85%; + } + } +} \ No newline at end of file diff --git a/src/apps/wallet/src/lib/components/setting-section/SettingSection.tsx b/src/apps/wallet/src/lib/components/setting-section/SettingSection.tsx new file mode 100644 index 000000000..380286fdf --- /dev/null +++ b/src/apps/wallet/src/lib/components/setting-section/SettingSection.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react' +import classNames from 'classnames' + +import styles from './SettingSection.module.scss' + +interface SettingSectionProps { + containerClassName?: string + readonly title: string + readonly infoText?: string + actionElement?: React.ReactNode + leftElement?: React.ReactNode +} + +const SettingSection: FC = (props: SettingSectionProps) => ( +
+ {props.leftElement} + +
+

{props.title}

+

+

+ + {props.actionElement} +
+) + +export default SettingSection diff --git a/src/apps/wallet/src/lib/components/setting-section/index.ts b/src/apps/wallet/src/lib/components/setting-section/index.ts new file mode 100644 index 000000000..a2a2fec21 --- /dev/null +++ b/src/apps/wallet/src/lib/components/setting-section/index.ts @@ -0,0 +1 @@ +export { default as SettingSection } from './SettingSection' diff --git a/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.module.scss b/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.module.scss new file mode 100644 index 000000000..391fff62e --- /dev/null +++ b/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.module.scss @@ -0,0 +1,46 @@ +@import '@libs/ui/styles/includes'; + +.card { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + background-color: white; + padding: 16px; + border-radius: 8px; + border: 1px solid #eaeaea; + margin: 16px 0; + + .header { + display: flex; + align-items: center; + justify-content: start; + gap: 24px; + + .icon { + max-width: 48px; + max-height: 48px; + } + } + + .content { + display: flex; + flex-direction: column; + gap: 16px; + flex: 1; + + .additionalInfoPurpose { + ul { + list-style-type: disc; + padding: 10px 24px; + } + } + } + + .footer { + display: flex; + width: 100%; + justify-content: space-between; + margin-top: 24px; + } +} diff --git a/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx b/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx new file mode 100644 index 000000000..6c5025df7 --- /dev/null +++ b/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx @@ -0,0 +1,82 @@ +import React from 'react' + +import { Button, PageDivider } from '~/libs/ui' + +import styles from './TaxFormCard.module.scss' + +interface TaxFormCardProps { + formTitle: string + formDescription: string + reasonTitle: string + reasonDescription: string + instructionsLink: string + instructionsLabel: string + completionLabel: string + additionalInfo?: { + link?: { + text: string + href: string + } + purpose?: { + title: string + points: string[] + } + note?: string + } + icon: React.ReactNode + onSetupClick: () => void +} + +const TaxFormCard: React.FC = (props: TaxFormCardProps) => ( +
+) + +export default TaxFormCard diff --git a/src/apps/wallet/src/lib/components/tax-form-card/index.ts b/src/apps/wallet/src/lib/components/tax-form-card/index.ts new file mode 100644 index 000000000..4b7cccbab --- /dev/null +++ b/src/apps/wallet/src/lib/components/tax-form-card/index.ts @@ -0,0 +1 @@ +export { default as TaxFormCard } from './TaxFormCard' diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss new file mode 100644 index 000000000..f56c0adbb --- /dev/null +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss @@ -0,0 +1,57 @@ +@import '@libs/ui/styles/includes'; + +.card { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + background-color: white; + padding: 16px; + border-radius: 8px; + gap: $sp-4; + border: 1px solid #eaeaea; + margin: 16px 0; + + .iconContainer { + width: 64px; + height: 64px; + display: flex; + justify-content: center; + align-items: center; + + background: linear-gradient(264.69deg, #198807 2.17%, #017c6d 97.49%); + border-radius: 4px; + + .icon { + width: 26.67px; + height: 26.67px; + top: 2.67px; + left: 2.67px; + fill: white; + } + } + + .content { + display: flex; + flex-direction: column; + flex: 1; + } + + .actionItems { + display: flex; + align-items: center; + width: 64px; + justify-content: space-between; + + .actionButton { + width: 32px; + height: 32px; + background-color: transparent; // Change as necessary + border: none; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + } + } +} diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx new file mode 100644 index 000000000..bce010ffd --- /dev/null +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx @@ -0,0 +1,36 @@ +import React from 'react' + +import { DownloadIcon, ExclamationCircleIcon, TrashIcon } from '@heroicons/react/solid' + +import { IconCheckCircle } from '../../assets/tax-forms' + +import styles from './TaxFormDetail.module.scss' + +interface TaxFormDetailProps { + title: string + description: string + status: string + // onDownloadClick: () => void + // onDeleteClick: () => void +} + +const TaxFormDetail: React.FC = (props: TaxFormDetailProps) => ( +
+
+ {props.status === 'ACTIVE' && } + {props.status !== 'ACTIVE' && } +
+ +
+
{props.title}
+
{props.description}
+
+ +
+ + +
+
+) + +export default TaxFormDetail diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/index.ts b/src/apps/wallet/src/lib/components/tax-form-detail/index.ts new file mode 100644 index 000000000..bfe5dc606 --- /dev/null +++ b/src/apps/wallet/src/lib/components/tax-form-detail/index.ts @@ -0,0 +1 @@ +export { default as TaxFormDetail } from './TaxFormDetail' diff --git a/src/apps/wallet/src/lib/index.ts b/src/apps/wallet/src/lib/index.ts new file mode 100644 index 000000000..0f5e459b8 --- /dev/null +++ b/src/apps/wallet/src/lib/index.ts @@ -0,0 +1,3 @@ +export * from './wallet-swr' +export * from './assets' +export * from './components' diff --git a/src/apps/wallet/src/lib/models/ApiError.ts b/src/apps/wallet/src/lib/models/ApiError.ts new file mode 100644 index 000000000..9b0135c9e --- /dev/null +++ b/src/apps/wallet/src/lib/models/ApiError.ts @@ -0,0 +1,4 @@ +export interface ApiError { + code: string + message: string +} diff --git a/src/apps/wallet/src/lib/models/ApiResponse.ts b/src/apps/wallet/src/lib/models/ApiResponse.ts new file mode 100644 index 000000000..3221e7793 --- /dev/null +++ b/src/apps/wallet/src/lib/models/ApiResponse.ts @@ -0,0 +1,4 @@ +export default interface ApiResponse { + status: 'success' | 'error' + data: T +} diff --git a/src/apps/wallet/src/lib/models/PaymentProvider.ts b/src/apps/wallet/src/lib/models/PaymentProvider.ts new file mode 100644 index 000000000..172cb0c52 --- /dev/null +++ b/src/apps/wallet/src/lib/models/PaymentProvider.ts @@ -0,0 +1,11 @@ +export interface PaymentProvider { + type: 'Payoneer' | 'Paypal' + name: 'Payoneer' | 'Paypal' + description: string + status: string +} + +export interface SetPaymentProviderResponse { + transactionId: string + registrationLink: string +} diff --git a/src/apps/wallet/src/lib/models/TaxForm.ts b/src/apps/wallet/src/lib/models/TaxForm.ts new file mode 100644 index 000000000..05f543c61 --- /dev/null +++ b/src/apps/wallet/src/lib/models/TaxForm.ts @@ -0,0 +1,18 @@ +export interface TaxForm { + id: string + userId: string + dateFiled: string + withholdingAmount: string + withholdingPercentage: string + taxForm: { + name: string + text: string + description: string + } + status: string +} + +export interface SetupTaxFormResponse { + transactionId: string + eSignLink: string +} diff --git a/src/apps/wallet/src/lib/models/WalletDetails.ts b/src/apps/wallet/src/lib/models/WalletDetails.ts new file mode 100644 index 000000000..b7baf2aeb --- /dev/null +++ b/src/apps/wallet/src/lib/models/WalletDetails.ts @@ -0,0 +1,19 @@ +export interface Balance { + amount: number + type: string + unit: string +} + +export interface AccountDetails { + balances: Balance[] +} + +export interface WalletDetails { + account: AccountDetails + withdrawalMethod: { + isSetupComplete: boolean + } + taxForm: { + isSetupComplete: boolean + } +} diff --git a/src/apps/wallet/src/lib/models/WinningDetail.ts b/src/apps/wallet/src/lib/models/WinningDetail.ts new file mode 100644 index 000000000..16c61bd7c --- /dev/null +++ b/src/apps/wallet/src/lib/models/WinningDetail.ts @@ -0,0 +1,35 @@ +export interface Winning { + id: string + description: string + type: string + createDate: string + netPayment: string + status: string + releaseDate: string + datePaid: string + installment: number +} + +export interface WinningDetail { + id: string + type: string + winnerId: string + origin: string + category: string + title: string + description: string + externalId: string + attributes: { + url: string + } + details: { + id: string + netAmount: string + grossAmount: string + totalAmount: string + installmentNumber: number + status: string + currency: string + }[] + createdAt: string +} diff --git a/src/apps/wallet/src/lib/services/wallet.ts b/src/apps/wallet/src/lib/services/wallet.ts new file mode 100644 index 000000000..67dc6fe1e --- /dev/null +++ b/src/apps/wallet/src/lib/services/wallet.ts @@ -0,0 +1,141 @@ +import { EnvironmentConfig } from '~/config' +import { xhrGetAsync, xhrPostAsync } from '~/libs/core' + +import { WalletDetails } from '../models/WalletDetails' +import { PaymentProvider, SetPaymentProviderResponse } from '../models/PaymentProvider' +import { ApiError } from '../models/ApiError' +import { WinningDetail } from '../models/WinningDetail' +import { SetupTaxFormResponse, TaxForm } from '../models/TaxForm' +import ApiResponse from '../models/ApiResponse' + +const baseUrl = `${EnvironmentConfig.API.V5}/payments` + +export async function getWalletDetails(): Promise { + const response = await xhrGetAsync>(`${baseUrl}/wallet`) + + if (response.status === 'error') { + throw new Error('Error fetching wallet details') + } + + return response.data +} + +export async function getUserPaymentProviders(): Promise { + const response = await xhrGetAsync>(`${baseUrl}/user/payment-methods`) + + if (response.status === 'error') { + throw new Error('Error fetching user payment providers') + } + + return response.data +} + +export async function getUserTaxFormDetails(): Promise { + const response = await xhrGetAsync>(`${baseUrl}/user/tax-forms`) + if (response.status === 'error') { + throw new Error('Error fetching user tax form details') + } + + return response.data +} + +export async function getPayments(userId: string): Promise { + const body = JSON.stringify({ + winnerId: userId, + }) + + const url = `${baseUrl}/user/winnings` + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Error fetching payments') + } + + return response.data +} + +export async function setPaymentProvider( + details: any, + type: string, + setDefault: boolean, +): Promise { + const body = JSON.stringify({ + details, + setDefault, + type, + }) + + const url = `${baseUrl}/user/payment-method` + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Error setting payment provider') + } + + return response.data +} + +export async function confirmPaymentProvider(provider: string, code: string, transactionId: string): Promise { + const body = JSON.stringify({ + code, + provider, + transactionId, + }) + + const url = `${baseUrl}/payment-provider/paypal/confirm` + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Error confirming payment provider') + } + + return response.data +} + +export async function setupTaxForm(userId: string, taxForm: string): Promise { + const body = JSON.stringify({ + taxForm, + userId, + }) + + const url = `${baseUrl}/user/tax-form` + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Error setting tax form') + } + + return response.data +} + +export async function processPayments(paymentIds: string[]): Promise<{ processed: boolean }> { + const body = JSON.stringify({ + paymentIds, + }) + const url = `${baseUrl}/withdraw` + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Error processing payments') + } + + return response.data +} + +export async function verifyOtp(transactionId: string, code: string): Promise { + const body = JSON.stringify({ + otpCode: code, + transactionId, + }) + + const url = `${baseUrl}/otp/verify` + try { + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('OTP verification failed or OTP has expired') + } + } catch (err) { + throw new Error('OTP verification failed or OTP has expired') + } +} diff --git a/src/apps/wallet/src/lib/wallet-swr/WalletSwr.tsx b/src/apps/wallet/src/lib/wallet-swr/WalletSwr.tsx new file mode 100644 index 000000000..ae683103f --- /dev/null +++ b/src/apps/wallet/src/lib/wallet-swr/WalletSwr.tsx @@ -0,0 +1,23 @@ +import { FC, ReactNode } from 'react' +import { SWRConfig } from 'swr' + +import { xhrGetAsync } from '~/libs/core' + +interface WalletSwrProps { + children: ReactNode +} + +const WalletSwr: FC = (props: WalletSwrProps) => ( + xhrGetAsync(resource), + refreshInterval: 0, + revalidateOnFocus: false, + revalidateOnMount: true, + }} + > + {props.children} + +) + +export default WalletSwr diff --git a/src/apps/wallet/src/lib/wallet-swr/index.ts b/src/apps/wallet/src/lib/wallet-swr/index.ts new file mode 100644 index 000000000..31c20cbba --- /dev/null +++ b/src/apps/wallet/src/lib/wallet-swr/index.ts @@ -0,0 +1 @@ +export { default as WalletSwr } from './WalletSwr' diff --git a/src/apps/wallet/src/wallet.routes.tsx b/src/apps/wallet/src/wallet.routes.tsx new file mode 100644 index 000000000..44449a4dc --- /dev/null +++ b/src/apps/wallet/src/wallet.routes.tsx @@ -0,0 +1,32 @@ +import { lazyLoad, LazyLoadedComponent, PlatformRoute } from '~/libs/core' +import { AppSubdomain, EnvironmentConfig, ToolTitle } from '~/config' + +const WalletApp: LazyLoadedComponent = lazyLoad(() => import('./WalletApp')) +const WalletHomePage: LazyLoadedComponent = lazyLoad( + () => import('./home'), + 'WalletHomePage' +) + +// prettier-ignore +export const rootRoute: string = EnvironmentConfig.SUBDOMAIN === AppSubdomain.wallet ? '' : `/${AppSubdomain.wallet}` + +export const toolTitle = ToolTitle.wallet +export const absoluteRootRoute: string = `${window.location.origin}/${rootRoute}` + +export const walletRoutes: ReadonlyArray = [ + { + authRequired: true, + children: [ + { + children: [], + element: , + id: 'Dashboard', + route: '', + }, + ], + domain: AppSubdomain.wallet, + element: , + id: toolTitle, + route: rootRoute, + }, +] diff --git a/src/config/constants.ts b/src/config/constants.ts index 48a4103c0..2c7640e03 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -7,7 +7,8 @@ export enum AppSubdomain { tcAcademy = 'academy', onboarding = 'onboarding', work = 'work', - talentSearch = 'talent-search' + talentSearch = 'talent-search', + wallet = 'wallet', } export enum ToolTitle { @@ -19,7 +20,8 @@ export enum ToolTitle { tcAcademy = 'Topcoder Academy', selfService = 'Self Service Challenges', onboarding = ' ', - talentSearch = 'Expert Talent' + talentSearch = 'Expert Talent', + wallet = 'Wallet', } export const PageSubheaderPortalId: string = 'page-subheader-portal-el' diff --git a/src/libs/core/lib/xhr/xhr-functions/xhr.functions.ts b/src/libs/core/lib/xhr/xhr-functions/xhr.functions.ts index 742902710..b639fd01b 100644 --- a/src/libs/core/lib/xhr/xhr-functions/xhr.functions.ts +++ b/src/libs/core/lib/xhr/xhr-functions/xhr.functions.ts @@ -1,5 +1,10 @@ import { identity } from 'lodash' -import axios, { AxiosHeaders, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' +import axios, { + AxiosHeaders, + AxiosInstance, + AxiosRequestConfig, + AxiosResponse, +} from 'axios' import { tokenGetAsync, TokenModel } from '../../auth' @@ -11,11 +16,10 @@ export const getResonseXHeader = ( headerName: string, // eslint-disable-next-line default-param-last parser: any = identity, - defaultValue?: T | undefined, + defaultValue?: T | undefined ): T => (parser(headers.get(headerName)) ?? defaultValue) as T export function createInstance(): AxiosInstance { - // create the instance const created: AxiosInstance = axios.create({ headers: { @@ -30,45 +34,80 @@ export function createInstance(): AxiosInstance { return created } -export async function deleteAsync(url: string, xhrInstance: AxiosInstance = globalInstance): Promise { +export async function deleteAsync( + url: string, + xhrInstance: AxiosInstance = globalInstance +): Promise { const output: AxiosResponse = await xhrInstance.delete(url) return output.data } -export async function getAsync(url: string, xhrInstance: AxiosInstance = globalInstance): Promise { +export async function getAsync( + url: string, + xhrInstance: AxiosInstance = globalInstance +): Promise { const output: AxiosResponse = await xhrInstance.get(url) return output.data } export interface PaginatedResponse { - data: T; - total: number; - page: number; - perPage: number; - totalPages: number; + data: T + total: number + page: number + perPage: number + totalPages: number } export async function getPaginatedAsync( url: string, - xhrInstance: AxiosInstance = globalInstance, + xhrInstance: AxiosInstance = globalInstance ): Promise> { const output: AxiosResponse = await xhrInstance.get(url) return { data: output.data, - page: getResonseXHeader(output.headers as AxiosHeaders, 'x-page', Number, 0), - perPage: getResonseXHeader(output.headers as AxiosHeaders, 'x-per-page', Number, 0), - total: getResonseXHeader(output.headers as AxiosHeaders, 'x-total', Number, 0), - totalPages: getResonseXHeader(output.headers as AxiosHeaders, 'x-total-pages', Number, 0), + page: getResonseXHeader( + output.headers as AxiosHeaders, + 'x-page', + Number, + 0 + ), + perPage: getResonseXHeader( + output.headers as AxiosHeaders, + 'x-per-page', + Number, + 0 + ), + total: getResonseXHeader( + output.headers as AxiosHeaders, + 'x-total', + Number, + 0 + ), + totalPages: getResonseXHeader( + output.headers as AxiosHeaders, + 'x-total-pages', + Number, + 0 + ), } } -export async function getBlobAsync(url: string, xhrInstance: AxiosInstance = globalInstance): Promise { - const output: AxiosResponse = await xhrInstance.get(url, { responseType: 'blob' }) +export async function getBlobAsync( + url: string, + xhrInstance: AxiosInstance = globalInstance +): Promise { + const output: AxiosResponse = await xhrInstance.get(url, { + responseType: 'blob', + }) return output.data } -export async function patchAsync(url: string, data: T, xhrInstance: AxiosInstance = globalInstance): Promise { +export async function patchAsync( + url: string, + data: T, + xhrInstance: AxiosInstance = globalInstance +): Promise { const output: AxiosResponse = await xhrInstance.patch(url, data) return output.data } @@ -77,7 +116,7 @@ export async function postAsync( url: string, data: T, config?: AxiosRequestConfig, - xhrInstance: AxiosInstance = globalInstance, + xhrInstance: AxiosInstance = globalInstance ): Promise { const output: AxiosResponse = await xhrInstance.post(url, data, config) return output.data @@ -87,16 +126,15 @@ export async function putAsync( url: string, data: T, config?: AxiosRequestConfig, - xhrInstance: AxiosInstance = globalInstance, + xhrInstance: AxiosInstance = globalInstance ): Promise { const output: AxiosResponse = await xhrInstance.put(url, data, config) return output.data } function interceptAuth(instance: AxiosInstance): void { - // add the auth token to all xhr calls - instance.interceptors.request.use(async config => { + instance.interceptors.request.use(async (config) => { const tokenData: TokenModel = await tokenGetAsync() if (tokenData.token) { @@ -109,18 +147,16 @@ function interceptAuth(instance: AxiosInstance): void { } function interceptError(instance: AxiosInstance): void { - // handle all http errors instance.interceptors.response.use( - config => config, + (config) => config, (error: any) => { - // if there is server error message, then return it inside `message` property of error error.message = error?.response?.data?.message || error.message // if there is server errors data, then return it inside `errors` property of error error.errors = error?.response?.data?.errors return Promise.reject(error) - }, + } ) } diff --git a/src/libs/ui/lib/components/modals/base-modal/BaseModal.tsx b/src/libs/ui/lib/components/modals/base-modal/BaseModal.tsx index 0c8d11476..252955a61 100644 --- a/src/libs/ui/lib/components/modals/base-modal/BaseModal.tsx +++ b/src/libs/ui/lib/components/modals/base-modal/BaseModal.tsx @@ -5,7 +5,10 @@ import classNames from 'classnames' import { LoadingSpinner } from '../../loading-spinner' import { IconOutline } from '../../svgs' -import { ModalContentResponse, useFetchModalContent } from './use-fetch-modal-content' +import { + ModalContentResponse, + useFetchModalContent, +} from './use-fetch-modal-content' import styles from './BaseModal.module.scss' export interface BaseModalProps extends ModalProps { @@ -15,12 +18,15 @@ export interface BaseModalProps extends ModalProps { theme?: 'danger' | 'clear' size?: 'body' | 'lg' | 'md' | 'sm' title?: string | ReactNode + spacer?: boolean buttons?: ReactNode } const BaseModal: FC = (props: BaseModalProps) => { - - const { content }: ModalContentResponse = useFetchModalContent(props.contentUrl, props.open) + const { content }: ModalContentResponse = useFetchModalContent( + props.contentUrl, + props.open + ) const renterContent: () => ReactNode = () => { if (props.children || !props.contentUrl) { @@ -39,11 +45,14 @@ const BaseModal: FC = (props: BaseModalProps) => { ) } - const handleBodyScroll = useCallback((force?: boolean) => { - const isOpen = force ?? props.open - document.documentElement.style.overflow = isOpen ? 'hidden' : '' - document.body.style.overflow = isOpen ? 'hidden' : '' - }, [props.open]) + const handleBodyScroll = useCallback( + (force?: boolean) => { + const isOpen = force ?? props.open + document.documentElement.style.overflow = isOpen ? 'hidden' : '' + document.body.style.overflow = isOpen ? 'hidden' : '' + }, + [props.open] + ) useEffect(() => { if (props.blockScroll) { @@ -62,30 +71,40 @@ const BaseModal: FC = (props: BaseModalProps) => { modal: classNames( props.classNames?.modal, `modal-${props.size || 'md'}`, - props.theme && styles[`theme-${props.theme}`], + props.theme && styles[`theme-${props.theme}`] ), }} - closeIcon={} + closeIcon={ + + } // send blockScroll as false unless we get a specific true from props blockScroll={props.blockScroll === true} > {props.title && ( <>
- { - typeof props.title === 'string' ? ( -

{props.title}

- ) : ( - props.title - ) - } + {typeof props.title === 'string' ? ( +

{props.title}

+ ) : ( + props.title + )}
-
+ {props.spacer !== false &&
} )} -
+
{renterContent()} {props.children}
diff --git a/tsconfig.paths.json b/tsconfig.paths.json index 97684204d..3f83bc357 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -27,6 +27,9 @@ "@profiles/*": [ "./src/apps/profiles/src/*" ], + "@wallet/*": [ + "./src/apps/wallet/src/*" + ], "@libs/ui/styles/*": [ "./src/libs/ui/lib/styles/*" ] diff --git a/yarn.lock b/yarn.lock index 8954e2c9d..84f76413b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16295,6 +16295,11 @@ react-only-when@^1.0.2: resolved "https://registry.yarnpkg.com/react-only-when/-/react-only-when-1.0.2.tgz#a8a79b48dd6cfbd91ddc710674a94153e88039d3" integrity sha512-agE6l3L6bqaVuwNtjihTQ36M+VBfPS63KOzcNL4ZTmlwSxQPvhzIqmBWfiol0/wLYmKxCcBqgXkEJpvj5Kob8Q== +react-otp-input@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/react-otp-input/-/react-otp-input-3.1.1.tgz#910169629812c40a614e6c175cc2c5f36102bb61" + integrity sha512-bjPavgJ0/Zmf/AYi4onj8FbH93IjeD+e8pWwxIJreDEWsU1ILR5fs8jEJmMGWSBe/yyvPP6X/W6Mk9UkOCkTPw== + react-popper@^2.2.5, react-popper@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" From 2bb8abde8e6ce1de0609cb11a96592d7316f8dd8 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 4 Jan 2024 18:31:41 +0600 Subject: [PATCH 06/48] fix: lint errors * temporarily suppress some lint issues * but these will need to be addressed in a future * PR Signed-off-by: Rakib Ansary --- .editorconfig | 1 + src/apps/platform/src/platform.routes.tsx | 2 +- .../src/home/tabs/payments/PaymentsTab.tsx | 3 +- .../payment-info-modal/PaymentInfoModal.tsx | 15 ++++----- .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 26 ++++++++-------- .../src/home/tabs/winnings/WinningsTab.tsx | 13 +++++--- .../src/lib/components/info-row/InfoRow.tsx | 18 +++++------ .../src/lib/components/otp-modal/OtpModal.tsx | 8 +++-- .../PaymentProviderCard.tsx | 3 +- .../payments-table/PaymentTable.tsx | 31 +++++++++++-------- .../components/tax-form-card/TaxFormCard.tsx | 1 + src/apps/wallet/src/wallet.routes.tsx | 2 +- .../lib/xhr/xhr-functions/xhr.functions.ts | 30 +++++++++--------- .../modals/base-modal/BaseModal.tsx | 12 +++---- 14 files changed, 89 insertions(+), 76 deletions(-) diff --git a/.editorconfig b/.editorconfig index c1e2c6435..1aa1cc721 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,3 +10,4 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +quote_type = single diff --git a/src/apps/platform/src/platform.routes.tsx b/src/apps/platform/src/platform.routes.tsx index b6b3792da..6dd61530c 100644 --- a/src/apps/platform/src/platform.routes.tsx +++ b/src/apps/platform/src/platform.routes.tsx @@ -13,7 +13,7 @@ import { walletRoutes } from '~/apps/wallet' const Home: LazyLoadedComponent = lazyLoad( () => import('./routes/home'), - 'HomePage' + 'HomePage', ) const homeRoutes: ReadonlyArray = [ diff --git a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx index fae0e2dbc..9060f1ebf 100644 --- a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx +++ b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx @@ -66,7 +66,7 @@ const PaymentsTab: FC = (props: PaymentsTabProps) => { const [userPaymentProvider, setUserPaymentProvider] = useState(undefined) const [setupRequired, setSetupRequired] = useState(false) const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(undefined) + const [, setError] = useState(undefined) const [selectedPaymentProvider, setSelectedPaymentProvider] = useState(undefined) const [providerToSet, setProviderToSet] = useState(undefined) const [transactionId, setTransactionId] = useState(undefined) @@ -263,7 +263,6 @@ const PaymentsTab: FC = (props: PaymentsTabProps) => { onClose={() => { setProviderToSet(undefined) }} - onResendClick={() => {}} onOtpVerified={() => { window.open(registrationLink, '_blank') diff --git a/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx index 43fb17f0c..ffc19803a 100644 --- a/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx +++ b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx @@ -23,9 +23,9 @@ function renderPayoneer(): JSX.Element { transfer option (where available) and a wire transfer option. Certain fees may apply.

- You will be directed to Payoneer's website in a new tab to complete your the connection. Please make - sure your account is fully verified to ensure withdrawal success.{' '} - You can return here after finishing up on Payoneer's site. + You will be directed to Payoneer's website in a new tab to complete your the connection. Please + make sure your account is fully verified to ensure withdrawal success. + You can return here after finishing up on Payoneer's site.

) @@ -37,9 +37,9 @@ function renderPaypal(): JSX.Element {

You can elect to receive payments deposited directly to your PayPal account. Certain fees may apply.

- You will be directed to PayPal's website in a new tab to complete your the connection. Please make sure - your account is fully verified to ensure withdrawal success. You can return here after finishing up on - PayPal's site. + You will be directed to PayPal's website in a new tab to complete your the connection. Please make + sure your account is fully verified to ensure withdrawal success. You can return here after finishing up + on PayPal's site.

) @@ -62,7 +62,8 @@ const PaymentInfoModal: FC = (props: PaymentInfoModalProp open size='body' title='CONNECT PAYMENT PROVIDER ACCOUNT' - classNames={{ modal: styles.infoModal }}> + classNames={{ modal: styles.infoModal }} + >
{props.selectedPaymentProvider === 'Payoneer' ? renderPayoneer() : renderPaypal()}
diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx index 40ca36647..9af1179e6 100644 --- a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx @@ -68,7 +68,7 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { const [setupRequired, setSetupRequired] = useState(undefined) const [taxForm, setTaxForm] = useState(undefined) const [isLoading, setIsLoading] = useState(false) - const [taxFormToSetup, setTaxFormToSetup] = useState(undefined) + const [, setTaxFormToSetup] = useState(undefined) const [taxFormSetupData, setTaxFormSetupData] = useState(undefined) const fetchUserTaxForms = async () => { @@ -97,21 +97,21 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { function renderAllTaxForms(): JSX.Element { return (
- {TAX_FORM_DETAILS.map((taxForm) => ( + {TAX_FORM_DETAILS.map(form => ( { try { - const setupTaxFormResponse = await setupTaxForm(`${props.profile.userId}`, taxForm.id) + const setupTaxFormResponse = await setupTaxForm(`${props.profile.userId}`, form.id) setTaxFormSetupData(setupTaxFormResponse) fetchUserTaxForms() diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index 63ce177e6..b903fdcad 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -1,11 +1,13 @@ -import React, { FC, useEffect, useMemo } from 'react' +/* eslint-disable react/jsx-no-bind */ +import React, { FC, useEffect } from 'react' -import { LoadingCircles, Table, TableColumn } from '~/libs/ui' +import { LoadingCircles } from '~/libs/ui' import { UserProfile } from '~/libs/core' + import { getPayments, processPayments } from '../../../lib/services/wallet' import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' - import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' + import styles from './Winnings.module.scss' interface ListViewProps { @@ -36,6 +38,7 @@ const ListView: FC = (props: ListViewProps) => { const [winnings, setWinnings] = React.useState>([]) const [isLoading, setIsLoading] = React.useState(false) + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const fetchWinnings = async () => { setIsLoading(true) try { @@ -54,7 +57,7 @@ const ListView: FC = (props: ListViewProps) => { const tempWinnings: Winning[] = [] payments.forEach((payment: WinningDetail) => { - payment.details.forEach((detail) => { + payment.details.forEach(detail => { const winning: Winning = { createDate: formatIOSDateString(payment.createdAt), datePaid: '', @@ -83,7 +86,7 @@ const ListView: FC = (props: ListViewProps) => { {!isLoading && ( { + onPayMeClick={async paymentIds => { const ids = Object.keys(paymentIds) await processPayments(ids) diff --git a/src/apps/wallet/src/lib/components/info-row/InfoRow.tsx b/src/apps/wallet/src/lib/components/info-row/InfoRow.tsx index 8a881a719..104cf01a5 100644 --- a/src/apps/wallet/src/lib/components/info-row/InfoRow.tsx +++ b/src/apps/wallet/src/lib/components/info-row/InfoRow.tsx @@ -8,16 +8,14 @@ interface InfoRowProps { action?: React.ReactNode } -const InfoRow: React.FC = ({ title, value, action }) => { - return ( -
-
{title}
-
-
{value}
- {action &&
{action}
} -
+const InfoRow: React.FC = (props: InfoRowProps) => ( +
+
{props.title}
+
+
{props.value}
+ {props.action &&
{props.action}
}
- ) -} +
+) export default InfoRow diff --git a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx index 08b62e994..e76f8d3eb 100644 --- a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx +++ b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx @@ -23,7 +23,7 @@ const OtpModal = ({ key: string transactionId: string onClose: () => void - onResendClick: () => void + onResendClick?: () => void onOtpVerified: (key: string) => void }) => { const [otp, setOtp] = React.useState('') @@ -60,7 +60,11 @@ const OtpModal = ({
{error &&

{error}

}

- For added security we’ve sent a 6-digit code to your ***@gmail.com email. The code + For added security we’ve sent a 6-digit code to your + {' '} + ***@gmail.com + {' '} + email. The code expires shortly, so please enter it soon.

= (props: PaymentProvi
+ style={detailContainerStyle} + > {props.details.map((detail: Detail) => (
diff --git a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx index 3cbd7057c..401bc8a49 100644 --- a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx +++ b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx @@ -1,3 +1,5 @@ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import React, { useState } from 'react' import { Button } from '~/libs/ui' @@ -14,34 +16,33 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => const [selectedPayments, setSelectedPayments] = useState<{ [paymentId: string]: boolean }>({}) const togglePaymentSelection = (paymentId: string) => { - setSelectedPayments((prevSelected) => ({ + setSelectedPayments(prevSelected => ({ ...prevSelected, [paymentId]: !prevSelected[paymentId], })) } - const isAllSelected = props.payments.length > 0 && props.payments.every((payment) => selectedPayments[payment.id]) + const isAllSelected = props.payments.length > 0 && props.payments.every(payment => selectedPayments[payment.id]) const toggleAllPayments = () => { if (isAllSelected) { setSelectedPayments({}) } else { const newSelections: { [paymentId: string]: boolean } = {} - props.payments.forEach((payment) => { + props.payments.forEach(payment => { newSelections[payment.id] = true }) setSelectedPayments(newSelections) } } - const calculateTotal = () => - props.payments.reduce((acc: number, payment: Winning) => { - if (selectedPayments[payment.id]) { - return acc + parseFloat(payment.netPayment.replace(/[^0-9.-]+/g, '')) - } + const calculateTotal = () => props.payments.reduce((acc: number, payment: Winning) => { + if (selectedPayments[payment.id]) { + return acc + parseFloat(payment.netPayment.replace(/[^0-9.-]+/g, '')) + } - return acc - }, 0) + return acc + }, 0) const total = calculateTotal() @@ -70,7 +71,7 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => - {props.payments.map((payment) => ( + {props.payments.map(payment => ( {payment.description} {payment.type} @@ -95,12 +96,16 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) =>
-
Total: ${total.toFixed(2)}
+
+ Total: $ + {total.toFixed(2)} +
diff --git a/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx b/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx index 6c5025df7..855021793 100644 --- a/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx +++ b/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx @@ -70,6 +70,7 @@ const TaxFormCard: React.FC = (props: TaxFormCardProps) => ( From c1cf54c0cf1db44b4f32271dd7a9d1f7a41d5642 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Wed, 10 Jan 2024 20:03:28 +0600 Subject: [PATCH 11/48] fix(core-184): tax forms tab layout Signed-off-by: Rakib Ansary --- .vscode/settings.json | 3 +- .../tabs/tax-forms/TaxFormsTab.module.scss | 10 +++++++ .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 28 ++++++++++++++++++- .../tax-form-detail/TaxFormDetail.module.scss | 2 +- .../tax-form-detail/TaxFormDetail.tsx | 28 +++++++++++++++---- 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e0dc2a50b..5cf3fb350 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", - "prettier.requireConfig": true + "prettier.requireConfig": true, + "editor.formatOnSave": false } diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss index c553f634a..2cd540383 100644 --- a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss @@ -40,6 +40,16 @@ display: grid; gap: $sp-4; grid-template-columns: repeat(2, 1fr); + + @media (min-width: '768px') { + grid-template-columns: repeat(2, 1fr); + grid-template-rows: auto auto; + + & > :first-child { + grid-column: 1 / -1; + } + } } + } } diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx index 9af1179e6..432e638a9 100644 --- a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx @@ -57,7 +57,33 @@ const TAX_FORM_DETAILS = [ 'Under current IRS guidance, foreign persons performing services outside of the U.S. are not subject to income tax withholding. However, Topcoder requires all such members to provide a properly filled out W-8BEN prior to issuing payment. In addition, prize money paid to foreign persons who are not performing services (such as winning an SRM competition) is subject to withholding taxes.', reasonTitle: 'Why do I need to complete Form W-8BEN?', }, - // Add more tax forms if needed + { + additionalInfo: { + // eslint-disable-next-line max-len + note: 'Topcoder’s policy is not to issue payments to foreign businesses until a properly completed Form W-8BEN-E is received from the member.', + purpose: { + points: [ + 'Establish that you are not a U.S. business', + 'Claim that you are the beneficial owner of the income for which Form W-8BEN-E is being provided', + ], + title: 'The W-8BEN-E is required to', + }, + }, + completionLabel: 'Complete Form W-8BEN-E', + completionLink: 'https://www.irs.gov/pub/irs-pdf/fw8ben.pdf', + formDescription: + // eslint-disable-next-line max-len + 'For businesses who are NOT a US business or other US person (such as a foreign business, non-resident alien or foreign national).', + formTitle: 'TAX FORM W-8BEN-E', + icon: , + id: 'W-8BEN-E', + instructionsLabel: 'Instructions', + instructionsLink: 'https://www.irs.gov/pub/irs-pdf/iw8ben.pdf', + reasonDescription: + // eslint-disable-next-line max-len + 'Under current IRS guidance, foreign persons performing services outside of the U.S. are not subject to income tax withholding. However, Topcoder requires all such members to provide a properly filled out W-8BEN prior to issuing payment. In addition, prize money paid to foreign persons who are not performing services (such as winning an SRM competition) is subject to withholding taxes.', + reasonTitle: 'Why do I need to complete Form W-8BEN-E?', + }, ] interface TaxFormsTabProps { diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss index f56c0adbb..4a0872c01 100644 --- a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss @@ -46,7 +46,7 @@ .actionButton { width: 32px; height: 32px; - background-color: transparent; // Change as necessary + background-color: transparent; border: none; cursor: pointer; display: flex; diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx index bce010ffd..cd6c96625 100644 --- a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx @@ -2,6 +2,7 @@ import React from 'react' import { DownloadIcon, ExclamationCircleIcon, TrashIcon } from '@heroicons/react/solid' +import { Button, IconOutline, LinkButton } from '~/libs/ui' import { IconCheckCircle } from '../../assets/tax-forms' import styles from './TaxFormDetail.module.scss' @@ -10,8 +11,8 @@ interface TaxFormDetailProps { title: string description: string status: string - // onDownloadClick: () => void - // onDeleteClick: () => void + onDownloadClick?: () => void + onDeleteClick?: () => void } const TaxFormDetail: React.FC = (props: TaxFormDetailProps) => ( @@ -26,10 +27,25 @@ const TaxFormDetail: React.FC = (props: TaxFormDetailProps)
{props.description}
-
- - -
+ {props.status === 'OTP_PENDING' && ( +
+ +
+ )} + + {props.status === 'ACTIVE' && ( +
+ + +
+ )}
) From 5e9515f8bf2a2fa55e2306d59fc3de172d5e233a Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Wed, 10 Jan 2024 20:20:29 +0600 Subject: [PATCH 12/48] fix(core-184): add resend otp flow Signed-off-by: Rakib Ansary --- .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 3 +++ .../tax-form-detail/TaxFormDetail.module.scss | 10 ++++++++++ .../tax-form-detail/TaxFormDetail.tsx | 19 +++++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx index 432e638a9..ccf15eb30 100644 --- a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx @@ -170,6 +170,9 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { // eslint-disable-next-line max-len description={`You have submitted a ${key} Tax Form via DocuSign. Resubmission of forms required on ${formattedDate}`} status={taxForm.status} + onResendOtpClick={function resendOtp() { + // TODO: Use transactionID to resend OTP + }} /> ) } diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss index 4a0872c01..abe39c511 100644 --- a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss @@ -54,4 +54,14 @@ align-items: center; } } + + .actionItemsStacked { + display: flex; + flex-direction: column; + align-items: flex-end; + + .warningLabel { + color: var(--btn-variant); + } + } } diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx index cd6c96625..7b44a4ea9 100644 --- a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx @@ -1,8 +1,8 @@ import React from 'react' import { DownloadIcon, ExclamationCircleIcon, TrashIcon } from '@heroicons/react/solid' +import { IconOutline, LinkButton } from '~/libs/ui' -import { Button, IconOutline, LinkButton } from '~/libs/ui' import { IconCheckCircle } from '../../assets/tax-forms' import styles from './TaxFormDetail.module.scss' @@ -11,6 +11,7 @@ interface TaxFormDetailProps { title: string description: string status: string + onResendOtpClick?: () => void onDownloadClick?: () => void onDeleteClick?: () => void } @@ -28,13 +29,27 @@ const TaxFormDetail: React.FC = (props: TaxFormDetailProps)
{props.status === 'OTP_PENDING' && ( -
+
+
From 360395781ed8b9863faf7ff949cb2bab704f97bf Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Fri, 12 Jan 2024 20:43:44 +0600 Subject: [PATCH 13/48] fix(core-184): tax form flow * correctly handle delete flow * correctly handle resend otp flow Signed-off-by: Rakib Ansary --- .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 77 +++++--- .../action-bar-item/ActionBarItem.tsx | 59 +++++++ .../lib/components/action-bar-item/index.ts | 1 + .../src/lib/components/otp-modal/OtpModal.tsx | 51 +++--- .../tax-form-detail/TaxFormDetail.module.scss | 16 +- .../tax-form-detail/TaxFormDetail.tsx | 165 +++++++++++++----- .../src/lib/models/OtpVerificationResponse.ts | 3 + .../src/lib/models/ResendOtpResponse.ts | 3 + src/apps/wallet/src/lib/models/TaxForm.ts | 2 +- .../wallet/src/lib/models/TransactionId.ts | 5 + src/apps/wallet/src/lib/services/wallet.ts | 47 ++++- 11 files changed, 325 insertions(+), 104 deletions(-) create mode 100644 src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx create mode 100644 src/apps/wallet/src/lib/components/action-bar-item/index.ts create mode 100644 src/apps/wallet/src/lib/models/OtpVerificationResponse.ts create mode 100644 src/apps/wallet/src/lib/models/ResendOtpResponse.ts create mode 100644 src/apps/wallet/src/lib/models/TransactionId.ts diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx index ccf15eb30..070eac903 100644 --- a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable react/jsx-no-bind */ import { FC, useEffect, useState } from 'react' import { Collapsible, LoadingCircles } from '~/libs/ui' @@ -9,9 +7,10 @@ import { Chip } from '../../../lib' import { OtpModal } from '../../../lib/components/otp-modal' import { TaxFormCard } from '../../../lib/components/tax-form-card' import { IconUS, IconWorld } from '../../../lib/assets/tax-forms' -import { getUserTaxFormDetails, setupTaxForm } from '../../../lib/services/wallet' -import { SetupTaxFormResponse, TaxForm } from '../../../lib/models/TaxForm' +import { getUserTaxFormDetails, removeTaxForm, resendOtp, setupTaxForm } from '../../../lib/services/wallet' +import { TaxForm } from '../../../lib/models/TaxForm' import { TaxFormDetail } from '../../../lib/components/tax-form-detail' +import { TransactionResponse } from '../../../lib/models/TransactionId' import styles from './TaxFormsTab.module.scss' @@ -94,10 +93,9 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { const [setupRequired, setSetupRequired] = useState(undefined) const [taxForm, setTaxForm] = useState(undefined) const [isLoading, setIsLoading] = useState(false) - const [, setTaxFormToSetup] = useState(undefined) - const [taxFormSetupData, setTaxFormSetupData] = useState(undefined) + const [otpFlow, setOtpFlow] = useState(undefined) - const fetchUserTaxForms = async () => { + async function fetchUserTaxForms(): Promise { setIsLoading(true) try { @@ -135,15 +133,19 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { instructionsLink={form.instructionsLink} additionalInfo={form.additionalInfo} icon={form.icon} - onSetupClick={async () => { + onSetupClick={async function onSetupTaxFormClick() { try { - const setupTaxFormResponse = await setupTaxForm(`${props.profile.userId}`, form.id) - setTaxFormSetupData(setupTaxFormResponse) + const transaction = await setupTaxForm(`${props.profile.userId}`, form.id) + setOtpFlow({ + ...transaction, + type: 'SETUP_TAX_FORM', + }) fetchUserTaxForms() } catch (err) { console.log('Error setting up tax form', err) } + }} /> ))} @@ -170,8 +172,21 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { // eslint-disable-next-line max-len description={`You have submitted a ${key} Tax Form via DocuSign. Resubmission of forms required on ${formattedDate}`} status={taxForm.status} - onResendOtpClick={function resendOtp() { - // TODO: Use transactionID to resend OTP + onDeleteClick={async function onDeleteClick() { + removeTaxForm(taxForm.id) + .then((transaction: TransactionResponse) => { + setOtpFlow({ ...transaction, type: 'REMOVE_TAX_FORM' }) + }) + }} + onResendOtpClick={async function onResendOtpClick() { + const response: TransactionResponse = await resendOtp(taxForm.transactionId) + setTaxForm({ + ...taxForm, + status: 'OTP_VERIFICATION_IN_PROGRESS', + }) + setOtpFlow({ + ...response, + }) }} /> ) @@ -187,8 +202,8 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => {
TAX FORM REQUIREMENTS}>

- All members must have a tax form on file before they can be paid. There are two options: a W-9 - or a W-8BEN. We will walk you through completing your tax form. + All members must have a tax form on file before they can be paid. The options are: + a W-9, W-8BEN or a W-8BEN-E.

{isLoading && } @@ -198,21 +213,31 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => {
- {taxFormSetupData !== undefined && ( + {otpFlow !== undefined && ( { - setTaxFormToSetup(undefined) + transactionId={otpFlow.transactionId} + key={otpFlow.transactionId} + userEmail={otpFlow.email} + isOpen={otpFlow !== undefined} + onClose={function onOtpModalClose() { + setOtpFlow(undefined) }} - onResendClick={() => { - // TODO: Call resend OTP API + onResendClick={function onResendClick() { + resendOtp(otpFlow.transactionId) }} - onOtpVerified={(eSignLink: string) => { - console.log('eSignLink', eSignLink) - window.open(taxFormSetupData.eSignLink, '_blank') - setTaxFormSetupData(undefined) + onOtpVerified={function onOtpVerified(data: unknown) { + switch (otpFlow.type) { + case 'REMOVE_TAX_FORM': + fetchUserTaxForms() + break + case 'SETUP_TAX_FORM': + window.open((data as { eSignLink: string })?.eSignLink, '_blank') + break + default: + break + } + + setOtpFlow(undefined) }} /> )} diff --git a/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx b/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx new file mode 100644 index 000000000..a9eefb6db --- /dev/null +++ b/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx @@ -0,0 +1,59 @@ +import React, { SVGProps } from 'react' + +import { LinkButton } from '~/libs/ui' + +interface ActionBarItemProps { + containerClassName: string; + action: { + text: string; + className: string; + icon: React.FC>; + isButtonDisabled?: boolean; + }; + info: { + text: string; + icon: React.FC>; + className: string; + }, + onConfirm?: () => void; +} + +const ActionBarItem: React.FC = (props: ActionBarItemProps) => { + const handleActionClick = React.useCallback(() => { + props.onConfirm?.() + }, [props]) + + + return ( + <> +
+ + +
+ + ) + +} + +export default ActionBarItem diff --git a/src/apps/wallet/src/lib/components/action-bar-item/index.ts b/src/apps/wallet/src/lib/components/action-bar-item/index.ts new file mode 100644 index 000000000..07d166714 --- /dev/null +++ b/src/apps/wallet/src/lib/components/action-bar-item/index.ts @@ -0,0 +1 @@ +export { default as ActionBarItem } from './ActionBarItem' diff --git a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx index e76f8d3eb..a76ce66d6 100644 --- a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx +++ b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx @@ -1,50 +1,43 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable react/jsx-no-bind */ -/* eslint-disable react/no-unstable-nested-components */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable react/destructuring-assignment */ import OTPInput, { InputProps } from 'react-otp-input' -import React from 'react' +import React, { FC } from 'react' import { BaseModal, LoadingCircles } from '~/libs/ui' import { verifyOtp } from '~/apps/wallet/src/lib/services/wallet' +import { OtpVerificationResponse } from '../../models/OtpVerificationResponse' + import styles from './OtpModal.module.scss' -const OtpModal = ({ - isOpen, - key, - transactionId, - onClose, - onResendClick, - onOtpVerified, -}: { +interface OtpModalProps { isOpen: boolean key: string transactionId: string + userEmail?: string onClose: () => void onResendClick?: () => void - onOtpVerified: (key: string) => void -}) => { + onOtpVerified: (data: unknown) => void +} + +const OtpModal: FC = (props: OtpModalProps) => { const [otp, setOtp] = React.useState('') const [loading, setLoading] = React.useState(false) const [error, setError] = React.useState('') React.useEffect(() => { - if (!isOpen) { + if (!props.isOpen) { setOtp('') setError('') } - }, [isOpen]) + }, [props.isOpen]) - const handleChange = (code: string) => { + function handleChange(code: string): void { setOtp(code) if (code.length === 6) { setLoading(true) - verifyOtp(transactionId, code) - .then(() => { + verifyOtp(props.transactionId, code) + .then((response: OtpVerificationResponse) => { + props.onOtpVerified(response) setLoading(false) - onOtpVerified(key) }) .catch((err: Error) => { setLoading(false) @@ -56,13 +49,20 @@ const OtpModal = ({ } return ( - +
{error &&

{error}

}

For added security we’ve sent a 6-digit code to your {' '} - ***@gmail.com + {props.userEmail ?? '***@gmail.com'} {' '} email. The code expires shortly, so please enter it soon. @@ -70,6 +70,7 @@ const OtpModal = ({ } onChange={handleChange} inputType='number' @@ -80,7 +81,7 @@ const OtpModal = ({

Can't find the code? Check your spam folder.

{loading && } {!loading && ( - )} diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss index abe39c511..a5d744c33 100644 --- a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss @@ -61,7 +61,21 @@ align-items: flex-end; .warningLabel { - color: var(--btn-variant); + color: $legacy-120; } } + + .actionButton, + .loadingWrapper { + transition: opacity 0.3s ease, transform 0.3s ease; + opacity: 1; + transform: scale(1); + } + + .hidden { + opacity: 0; + display: none; + transform: scale(0.95); + pointer-events: none; + } } diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx index 7b44a4ea9..5b1e96137 100644 --- a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx @@ -1,12 +1,20 @@ -import React from 'react' +import React, { useMemo } from 'react' import { DownloadIcon, ExclamationCircleIcon, TrashIcon } from '@heroicons/react/solid' -import { IconOutline, LinkButton } from '~/libs/ui' +import { ConfirmModal, IconSolid } from '~/libs/ui' import { IconCheckCircle } from '../../assets/tax-forms' +import { ActionBarItem } from '../action-bar-item' import styles from './TaxFormDetail.module.scss' +interface ConfirmFlowData { + title: string; + action: string; + content: React.ReactNode | (() => React.ReactNode) + callback?: () => void; +} + interface TaxFormDetailProps { title: string description: string @@ -16,52 +24,121 @@ interface TaxFormDetailProps { onDeleteClick?: () => void } -const TaxFormDetail: React.FC = (props: TaxFormDetailProps) => ( -
-
- {props.status === 'ACTIVE' && } - {props.status !== 'ACTIVE' && } -
+const TaxFormDetail: React.FC = (props: TaxFormDetailProps) => { + const [confirmFlow, setConfirmFlow] = React.useState(undefined) + + const renderConfirmModalContent = useMemo(() => { + if (confirmFlow?.content === undefined) { + return undefined + } + + if (typeof confirmFlow?.content === 'function') { + return confirmFlow?.content() + } + + return confirmFlow?.content + }, [confirmFlow]) -
-
{props.title}
-
{props.description}
+ const renderOtpPending = (): JSX.Element => ( + + ) + + const renderOtpVerified = (): JSX.Element => ( + + ) + + const renderActive = (): JSX.Element => ( +
+ +
+ ) - {props.status === 'OTP_PENDING' && ( -
- - -
- )} + return ( + <> +
+
+ {props.status === 'ACTIVE' && } + {props.status !== 'ACTIVE' && } +
- {props.status === 'ACTIVE' && ( -
- - +
+
{props.title}
+
{props.description}
+
+ + {props.status === 'OTP_PENDING' && renderOtpPending()} + {props.status === 'OTP_VERIFIED' && renderOtpVerified()} + {props.status === 'ACTIVE' && renderActive()}
- )} -
-) + {confirmFlow && ( + +
{renderConfirmModalContent}
+
+ )} + + ) +} export default TaxFormDetail diff --git a/src/apps/wallet/src/lib/models/OtpVerificationResponse.ts b/src/apps/wallet/src/lib/models/OtpVerificationResponse.ts new file mode 100644 index 000000000..53456138d --- /dev/null +++ b/src/apps/wallet/src/lib/models/OtpVerificationResponse.ts @@ -0,0 +1,3 @@ +export interface OtpVerificationResponse { + data: unknown; + } diff --git a/src/apps/wallet/src/lib/models/ResendOtpResponse.ts b/src/apps/wallet/src/lib/models/ResendOtpResponse.ts new file mode 100644 index 000000000..1fd04f75a --- /dev/null +++ b/src/apps/wallet/src/lib/models/ResendOtpResponse.ts @@ -0,0 +1,3 @@ +export interface ResendOtpResponse { + transactionId: string; + } diff --git a/src/apps/wallet/src/lib/models/TaxForm.ts b/src/apps/wallet/src/lib/models/TaxForm.ts index 05f543c61..6578be894 100644 --- a/src/apps/wallet/src/lib/models/TaxForm.ts +++ b/src/apps/wallet/src/lib/models/TaxForm.ts @@ -10,9 +10,9 @@ export interface TaxForm { description: string } status: string + transactionId: string } export interface SetupTaxFormResponse { transactionId: string - eSignLink: string } diff --git a/src/apps/wallet/src/lib/models/TransactionId.ts b/src/apps/wallet/src/lib/models/TransactionId.ts new file mode 100644 index 000000000..a0e860446 --- /dev/null +++ b/src/apps/wallet/src/lib/models/TransactionId.ts @@ -0,0 +1,5 @@ +export interface TransactionResponse { + transactionId: string + type?: string; + email: string +} diff --git a/src/apps/wallet/src/lib/services/wallet.ts b/src/apps/wallet/src/lib/services/wallet.ts index 67dc6fe1e..d27491885 100644 --- a/src/apps/wallet/src/lib/services/wallet.ts +++ b/src/apps/wallet/src/lib/services/wallet.ts @@ -1,11 +1,12 @@ import { EnvironmentConfig } from '~/config' -import { xhrGetAsync, xhrPostAsync } from '~/libs/core' +import { xhrDeleteAsync, xhrGetAsync, xhrPostAsync } from '~/libs/core' import { WalletDetails } from '../models/WalletDetails' import { PaymentProvider, SetPaymentProviderResponse } from '../models/PaymentProvider' -import { ApiError } from '../models/ApiError' import { WinningDetail } from '../models/WinningDetail' -import { SetupTaxFormResponse, TaxForm } from '../models/TaxForm' +import { TaxForm } from '../models/TaxForm' +import { OtpVerificationResponse } from '../models/OtpVerificationResponse' +import { TransactionResponse } from '../models/TransactionId' import ApiResponse from '../models/ApiResponse' const baseUrl = `${EnvironmentConfig.API.V5}/payments` @@ -92,14 +93,14 @@ export async function confirmPaymentProvider(provider: string, code: string, tra return response.data } -export async function setupTaxForm(userId: string, taxForm: string): Promise { +export async function setupTaxForm(userId: string, taxForm: string): Promise { const body = JSON.stringify({ taxForm, userId, }) const url = `${baseUrl}/user/tax-form` - const response = await xhrPostAsync>(url, body) + const response = await xhrPostAsync>(url, body) if (response.status === 'error') { throw new Error('Error setting tax form') @@ -108,6 +109,17 @@ export async function setupTaxForm(userId: string, taxForm: string): Promise { + const url = `${baseUrl}/user/tax-forms/${taxFormId}` + const response = await xhrDeleteAsync>(url) + + if (response.status === 'error') { + throw new Error('Error removing tax form') + } + + return response.data +} + export async function processPayments(paymentIds: string[]): Promise<{ processed: boolean }> { const body = JSON.stringify({ paymentIds, @@ -122,7 +134,7 @@ export async function processPayments(paymentIds: string[]): Promise<{ processed return response.data } -export async function verifyOtp(transactionId: string, code: string): Promise { +export async function verifyOtp(transactionId: string, code: string): Promise { const body = JSON.stringify({ otpCode: code, transactionId, @@ -130,12 +142,33 @@ export async function verifyOtp(transactionId: string, code: string): Promise>(url, body) + const response = await xhrPostAsync>(url, body) if (response.status === 'error') { throw new Error('OTP verification failed or OTP has expired') } + + return response.data } catch (err) { throw new Error('OTP verification failed or OTP has expired') } } + +export async function resendOtp(transactionId: string): Promise { + const body = JSON.stringify({ + transactionId, + }) + + const url = `${baseUrl}/otp/resend` + try { + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Failed to resend OTP.') + } + + return response.data + } catch (err) { + throw new Error('Failed to resend OTP.') + } +} From 5a5948362ed8b835c3667934273cbcd6c4ce186c Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Mon, 15 Jan 2024 17:11:25 +0600 Subject: [PATCH 14/48] fix(tax-form): tax form layout and flow Signed-off-by: Rakib Ansary --- .../tabs/tax-forms/TaxFormsTab.module.scss | 2 +- .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 80 +++++++++++++++---- .../src/lib/components/otp-modal/OtpModal.tsx | 7 +- .../tax-form-card/TaxFormCard.module.scss | 2 +- .../tax-form-detail/TaxFormDetail.module.scss | 13 ++- .../tax-form-detail/TaxFormDetail.tsx | 10 ++- src/apps/wallet/src/lib/services/wallet.ts | 31 ++++++- src/libs/core/lib/xhr/xhr-functions/index.ts | 1 + .../lib/xhr/xhr-functions/xhr.functions.ts | 10 +++ 9 files changed, 129 insertions(+), 27 deletions(-) diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss index 2cd540383..d54a34073 100644 --- a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.module.scss @@ -39,7 +39,7 @@ .stacked { display: grid; gap: $sp-4; - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(1, 1fr); @media (min-width: '768px') { grid-template-columns: repeat(2, 1fr); diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx index 070eac903..68f8b411a 100644 --- a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx @@ -1,13 +1,17 @@ import { FC, useEffect, useState } from 'react' +import { toast } from 'react-toastify' import { Collapsible, LoadingCircles } from '~/libs/ui' import { UserProfile } from '~/libs/core' +import { downloadBlob } from '~/libs/shared' import { Chip } from '../../../lib' import { OtpModal } from '../../../lib/components/otp-modal' import { TaxFormCard } from '../../../lib/components/tax-form-card' import { IconUS, IconWorld } from '../../../lib/assets/tax-forms' -import { getUserTaxFormDetails, removeTaxForm, resendOtp, setupTaxForm } from '../../../lib/services/wallet' +import { + getRecipientViewURL, getUserTaxFormDetails, removeTaxForm, resendOtp, setupTaxForm, +} from '../../../lib/services/wallet' import { TaxForm } from '../../../lib/models/TaxForm' import { TaxFormDetail } from '../../../lib/components/tax-form-detail' import { TransactionResponse } from '../../../lib/models/TransactionId' @@ -95,8 +99,8 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { const [isLoading, setIsLoading] = useState(false) const [otpFlow, setOtpFlow] = useState(undefined) - async function fetchUserTaxForms(): Promise { - setIsLoading(true) + async function fetchUserTaxForms(refresh: boolean = true): Promise { + setIsLoading(refresh) try { const taxForms = await getUserTaxFormDetails() @@ -141,9 +145,12 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { ...transaction, type: 'SETUP_TAX_FORM', }) - fetchUserTaxForms() + fetchUserTaxForms(false) } catch (err) { - console.log('Error setting up tax form', err) + toast.error( + (err as Error).message ?? 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) } }} @@ -177,16 +184,54 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { .then((transaction: TransactionResponse) => { setOtpFlow({ ...transaction, type: 'REMOVE_TAX_FORM' }) }) + .catch((err: unknown) => { + toast.error( + (err as Error).message ?? 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) + }) }} onResendOtpClick={async function onResendOtpClick() { - const response: TransactionResponse = await resendOtp(taxForm.transactionId) - setTaxForm({ - ...taxForm, - status: 'OTP_VERIFICATION_IN_PROGRESS', - }) - setOtpFlow({ - ...response, - }) + try { + const response: TransactionResponse = await resendOtp(taxForm.transactionId) + setOtpFlow({ + ...response, + type: 'SETUP_TAX_FORM', + }) + } catch (err: unknown) { + toast.error( + (err as Error).message ?? 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) + } + }} + onGetRecipientURL={async function onGetRecipientURL() { + try { + const response: TransactionResponse = await getRecipientViewURL() + setOtpFlow({ + ...response, + type: 'VIEW_TAX_FORM', + }) + } catch (err: unknown) { + toast.error( + (err as Error).message ?? 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) + } + }} + onDownloadClick={async function onDownloadSignedDocumentClick() { + try { + const response: TransactionResponse = await getRecipientViewURL() + setOtpFlow({ + ...response, + type: 'DOWNLOAD_TAX_FORM', + }) + } catch (err: unknown) { + toast.error( + (err as Error).message ?? 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) + } }} /> ) @@ -219,6 +264,7 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { key={otpFlow.transactionId} userEmail={otpFlow.email} isOpen={otpFlow !== undefined} + isBlob={otpFlow.type === 'DOWNLOAD_TAX_FORM'} onClose={function onOtpModalClose() { setOtpFlow(undefined) }} @@ -228,11 +274,17 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { onOtpVerified={function onOtpVerified(data: unknown) { switch (otpFlow.type) { case 'REMOVE_TAX_FORM': - fetchUserTaxForms() + fetchUserTaxForms(false) break case 'SETUP_TAX_FORM': + case 'VIEW_TAX_FORM': + fetchUserTaxForms(false) window.open((data as { eSignLink: string })?.eSignLink, '_blank') break + case 'DOWNLOAD_TAX_FORM': + downloadBlob(data as Blob, `tax-form-${props.profile.userId}-${new Date() + .getTime()}.pdf`) + break default: break } diff --git a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx index a76ce66d6..789473c76 100644 --- a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx +++ b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx @@ -13,6 +13,7 @@ interface OtpModalProps { key: string transactionId: string userEmail?: string + isBlob?: boolean onClose: () => void onResendClick?: () => void onOtpVerified: (data: unknown) => void @@ -34,10 +35,10 @@ const OtpModal: FC = (props: OtpModalProps) => { setOtp(code) if (code.length === 6) { setLoading(true) - verifyOtp(props.transactionId, code) - .then((response: OtpVerificationResponse) => { - props.onOtpVerified(response) + verifyOtp(props.transactionId, code, props.isBlob) + .then((response: OtpVerificationResponse | Blob) => { setLoading(false) + props.onOtpVerified(response) }) .catch((err: Error) => { setLoading(false) diff --git a/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.module.scss b/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.module.scss index 391fff62e..46c3eb47d 100644 --- a/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.module.scss +++ b/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.module.scss @@ -14,7 +14,7 @@ .header { display: flex; align-items: center; - justify-content: start; + justify-content: flex-start; gap: 24px; .icon { diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss index a5d744c33..bf64b99b9 100644 --- a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.module.scss @@ -46,13 +46,24 @@ .actionButton { width: 32px; height: 32px; - background-color: transparent; border: none; cursor: pointer; display: flex; justify-content: center; align-items: center; } + + .downloadIcon { + width: 24px; + height: 24px; + fill: $turq-160; + } + + .deleteIcon { + width: 24px; + height: 24px; + fill: $red-140; + } } .actionItemsStacked { diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx index 5b1e96137..8bc06b1ea 100644 --- a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx @@ -19,6 +19,7 @@ interface TaxFormDetailProps { title: string description: string status: string + onGetRecipientURL?: () => void onResendOtpClick?: () => void onDownloadClick?: () => void onDeleteClick?: () => void @@ -76,10 +77,10 @@ const TaxFormDetail: React.FC = (props: TaxFormDetailProps) icon: IconSolid.MailIcon, text: 'Go to DocuSign page', }} - onConfirm={function onResendDocsuignClick() { + onConfirm={function onGetRecipientURL() { setConfirmFlow({ action: 'Yes', - callback: props.onResendOtpClick, + callback: props.onGetRecipientURL, content: 'You will be redirected to DocuSign.', title: 'Are you sure?', }) @@ -89,9 +90,10 @@ const TaxFormDetail: React.FC = (props: TaxFormDetailProps) const renderActive = (): JSX.Element => (
- + { + const url = `${baseUrl}/user/tax-form/esign-url` + const response = await xhrGetAsync>(url) + + if (response.status === 'error') { + throw new Error('Error removing tax form') + } + + return response.data +} + export async function processPayments(paymentIds: string[]): Promise<{ processed: boolean }> { const body = JSON.stringify({ paymentIds, @@ -134,7 +147,8 @@ export async function processPayments(paymentIds: string[]): Promise<{ processed return response.data } -export async function verifyOtp(transactionId: string, code: string): Promise { +// eslint-disable-next-line max-len +export async function verifyOtp(transactionId: string, code: string, blob: boolean = false): Promise { const body = JSON.stringify({ otpCode: code, transactionId, @@ -142,7 +156,14 @@ export async function verifyOtp(transactionId: string, code: string): Promise>(url, body) + // eslint-disable-next-line max-len + const response = await xhrPostAsyncWithBlobHandling | Blob>(url, body, { + responseType: blob ? 'blob' : 'json', + }) + + if (response instanceof Blob) { + return response as Blob + } if (response.status === 'error') { throw new Error('OTP verification failed or OTP has expired') @@ -169,6 +190,10 @@ export async function resendOtp(transactionId: string): Promise( return output.data } +export async function postAsyncWithBlobHandling( + url: string, + data: T, + config?: AxiosRequestConfig, + xhrInstance: AxiosInstance = globalInstance, +): Promise { + const response: AxiosResponse = await xhrInstance.post(url, data, config) + return response.data +} + export async function putAsync( url: string, data: T, From 0ee226add471cc7b861bd037861799aaaa0e656b Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Mon, 15 Jan 2024 17:16:20 +0600 Subject: [PATCH 15/48] chore: lint fixes Signed-off-by: Rakib Ansary --- .../wallet/src/lib/components/action-bar-item/ActionBarItem.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx b/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx index a9eefb6db..cedb3c2bc 100644 --- a/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx +++ b/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx @@ -23,7 +23,6 @@ const ActionBarItem: React.FC = (props: ActionBarItemProps) props.onConfirm?.() }, [props]) - return ( <>
From e23c87151181e81ae158127b20d3458892552913 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Mon, 15 Jan 2024 17:34:39 +0600 Subject: [PATCH 16/48] fix(payment-providers): layout on mobile Signed-off-by: Rakib Ansary --- .../wallet/src/home/tabs/payments/PaymentsTab.module.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss index 11c8d3aa7..8405d75da 100644 --- a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss +++ b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss @@ -80,7 +80,12 @@ .providersStacked { display: grid; gap: $sp-4; - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(1, 1fr); + + @media (min-width: '768px') { + grid-template-columns: repeat(2, 1fr); + grid-template-rows: auto auto; + } } .providerSubmitted { From 363bf3376c64663145515ee9c577fae9c9453bc9 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 16 Jan 2024 08:26:29 +0600 Subject: [PATCH 17/48] fix(payment-providers): layout fixes Signed-off-by: Rakib Ansary --- .../tabs/payments/PaymentsTab.module.scss | 2 + .../src/home/tabs/payments/PaymentsTab.tsx | 283 +++++++++--------- .../action-bar-item/ActionBarItem.tsx | 52 ++-- .../PaymentProviderCard.module.scss | 122 ++++---- .../PaymentProviderCard.tsx | 165 +++++++--- .../tax-form-detail/TaxFormDetail.tsx | 8 +- .../wallet/src/lib/models/ConfirmFlowData.ts | 6 + .../wallet/src/lib/models/PaymentProvider.ts | 8 +- src/apps/wallet/src/lib/services/wallet.ts | 14 +- 9 files changed, 372 insertions(+), 288 deletions(-) create mode 100644 src/apps/wallet/src/lib/models/ConfirmFlowData.ts diff --git a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss index 8405d75da..d384d2b1d 100644 --- a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss +++ b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss @@ -66,12 +66,14 @@ margin-bottom: $sp-4; .alternateProviderButton { + margin-top: $sp-4; padding-left: 0px !important; padding-right: 0px !important; } } .providersSingleRow { + margin-top: $sp-4; display: grid; grid-template-columns: repeat(1, 1fr); width: 100%; diff --git a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx index 9060f1ebf..ef16ca99f 100644 --- a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx +++ b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx @@ -1,16 +1,19 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable react/jsx-no-bind */ import { FC, useEffect, useState } from 'react' +import { toast } from 'react-toastify' import { Button, Collapsible, LoadingCircles } from '~/libs/ui' -import { UserProfile } from '~/libs/core' import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/solid' import { Chip, IconDollar, IconSpeed, IconWorld, PayoneerLogo, PayPalLogo } from '../../../lib' -import { confirmPaymentProvider, getUserPaymentProviders, setPaymentProvider } from '../../../lib/services/wallet' -import { PaymentProvider, SetPaymentProviderResponse } from '../../../lib/models/PaymentProvider' +import { PaymentProvider } from '../../../lib/models/PaymentProvider' import { PaymentProviderCard } from '../../../lib/components/payment-provider-card' import { OtpModal } from '../../../lib/components/otp-modal' +import { TransactionResponse } from '../../../lib/models/TransactionId' +import { + getUserPaymentProviders, resendOtp, setPaymentProvider, +} from '../../../lib/services/wallet' import { PaymentInfoModal } from './payment-info-modal' import styles from './PaymentsTab.module.scss' @@ -58,37 +61,29 @@ const PAYMENT_PROVIDER_DETAILS = { }, } -interface PaymentsTabProps { - profile: UserProfile -} - -const PaymentsTab: FC = (props: PaymentsTabProps) => { - const [userPaymentProvider, setUserPaymentProvider] = useState(undefined) +const PaymentsTab: FC = () => { + const [selectedPaymentProvider, setSelectedPaymentProvider] = useState(undefined) const [setupRequired, setSetupRequired] = useState(false) const [isLoading, setIsLoading] = useState(false) - const [, setError] = useState(undefined) - const [selectedPaymentProvider, setSelectedPaymentProvider] = useState(undefined) - const [providerToSet, setProviderToSet] = useState(undefined) - const [transactionId, setTransactionId] = useState(undefined) - const [registrationLink, setRegistrationLink] = useState(undefined) - const [providerStatus, setProviderStatus] = useState(undefined) const [showAlternateProvider, setShowAlternateProvider] = useState(false) - const fetchPaymentProviders = async () => { - setIsLoading(true) - setError(undefined) + const [paymentInfoModalFlow, setPaymentInfoModalFlow] = useState(undefined) + const [otpFlow, setOtpFlow] = useState(undefined) + + const fetchPaymentProviders = async (refresh: boolean = true) => { + setIsLoading(refresh) try { const providers = await getUserPaymentProviders() + if (providers.length === 0) { + setSetupRequired(true) + } else { + setSetupRequired(false) + setSelectedPaymentProvider(providers[0]) + } - const status = providers && providers.length > 0 ? providers[0].status : undefined - - setSetupRequired(providers.length === 0) - setUserPaymentProvider(status !== undefined ? providers[0] : undefined) - setProviderStatus(status) } catch (apiError) { - setError('Error fetching payment providers') - setUserPaymentProvider(undefined) + setSelectedPaymentProvider(undefined) } setIsLoading(false) @@ -98,33 +93,29 @@ const PaymentsTab: FC = (props: PaymentsTabProps) => { fetchPaymentProviders() }, []) - useEffect(() => { - if (providerStatus === 'OTP_VERIFIED') { - const queryParams = new URLSearchParams(window.location.search) - const code = queryParams.get('code') - - if (code) { - const storedTransactionId = localStorage.getItem('transactionId') - if (storedTransactionId) { - confirmPaymentProvider('Paypal', code, storedTransactionId) - .then((response: any) => { - console.log(response) - fetchPaymentProviders() - }) - .catch((err: any) => { - console.log(err) - }) - .finally(() => { - localStorage.removeItem('transactionId') - }) - } - } - } - }, [providerStatus]) + // useEffect(() => { + // if (providerStatus === 'OTP_VERIFIED') { + // const queryParams = new URLSearchParams(window.location.search) + // const code = queryParams.get('code') - function onProviderSelected(provider: string): void { - setSelectedPaymentProvider(provider) - } + // if (code) { + // const storedTransactionId = localStorage.getItem('transactionId') + // if (storedTransactionId) { + // confirmPaymentProvider('Paypal', code, storedTransactionId) + // .then((response: any) => { + // console.log(response) + // fetchPaymentProviders() + // }) + // .catch((err: any) => { + // console.log(err) + // }) + // .finally(() => { + // localStorage.removeItem('transactionId') + // }) + // } + // } + // } + // }, [providerStatus]) function renderProviders(): JSX.Element { return ( @@ -133,71 +124,93 @@ const PaymentsTab: FC = (props: PaymentsTabProps) => { provider={{ description: 'Payoneer', name: 'Payoneer', status: 'NOT_CONNECTED', type: 'Payoneer' }} logo={PAYMENT_PROVIDER_DETAILS.Payoneer.logo} details={PAYMENT_PROVIDER_DETAILS.Payoneer.details} - onConnectClick={() => onProviderSelected('Payoneer')} + onConnectClick={function onConnectClick() { setPaymentInfoModalFlow('Payoneer') }} /> onProviderSelected('Paypal')} + onConnectClick={function onConnectClick() { setPaymentInfoModalFlow('Paypal') }} />
) } function renderConnectedProvider(): JSX.Element | undefined { - if (userPaymentProvider) { - return ( -
-

Chosen Payment Provider

-
- -
-
+ ) } return ( @@ -218,8 +231,8 @@ const PaymentsTab: FC = (props: PaymentsTabProps) => { {isLoading && } - {!isLoading && setupRequired && renderProviders()} - {!isLoading && !setupRequired && renderConnectedProvider()} + {!isLoading && selectedPaymentProvider === undefined && renderProviders()} + {!isLoading && selectedPaymentProvider !== undefined && renderConnectedProvider()}

Provider details are based on the latest information from their official sites; we advise @@ -228,47 +241,43 @@ const PaymentsTab: FC = (props: PaymentsTabProps) => {

- {selectedPaymentProvider && ( + {paymentInfoModalFlow && ( { - setSelectedPaymentProvider(undefined) - }} - handlePaymentSelection={(provider: string) => { - setSelectedPaymentProvider(undefined) - const details: any = {} - - if (provider === 'Payoneer') { - details.payeeId = `${props.profile.userId}` - } - - setPaymentProvider(details, provider, true) - .then((response: SetPaymentProviderResponse) => { - localStorage.setItem('transactionId', response.transactionId) - setTransactionId(response.transactionId) - setRegistrationLink(response.registrationLink) - setProviderToSet(provider) + selectedPaymentProvider={paymentInfoModalFlow} + handleModalClose={function closePaymentInfoModal() { setPaymentInfoModalFlow(undefined) }} + handlePaymentSelection={async function confirmPaymentProviderSelection(provider: string) { + setPaymentInfoModalFlow(undefined) + setPaymentProvider(provider) + .then((response: TransactionResponse) => { + setOtpFlow({ + ...response, + type: 'SETUP_PAYMENT_PROVIDER', + }) + fetchPaymentProviders(false) }) - .catch((err: any) => { - console.log(err) + .catch((err: Error) => { + toast.error( + err.message ?? 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) }) }} /> )} - {providerToSet && transactionId && ( + {otpFlow && ( { - setProviderToSet(undefined) - }} - onOtpVerified={() => { - window.open(registrationLink, '_blank') + transactionId={otpFlow.transactionId} + key={otpFlow.transactionId} + userEmail={otpFlow.email} + isOpen={otpFlow !== undefined} + onClose={function onOtpModalClose() { setOtpFlow(undefined) }} + onResendClick={function onResendClick() { resendOtp(otpFlow.transactionId) }} + onOtpVerified={function onOtpVerified(data: unknown) { + const registrationLink = (data as any).registrationLink - setProviderToSet(undefined) - setRegistrationLink(undefined) + window.open(registrationLink, '_blank') + setOtpFlow(undefined) fetchPaymentProviders() }} /> diff --git a/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx b/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx index cedb3c2bc..1ea2a947a 100644 --- a/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx +++ b/src/apps/wallet/src/lib/components/action-bar-item/ActionBarItem.tsx @@ -24,33 +24,31 @@ const ActionBarItem: React.FC = (props: ActionBarItemProps) }, [props]) return ( - <> -
- - -
- +
+ + +
) } diff --git a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss index 121018a15..0cb4c5a5a 100644 --- a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss +++ b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss @@ -2,78 +2,90 @@ .card { display: flex; - flex-direction: column; + flex-direction: row; align-items: flex-start; - background-color: white; - padding: 16px; + justify-content: space-between; border-radius: 8px; - border: 1px solid #eaeaea; - margin: 16px 0; + background-color: white; + border: 1px solid #D4D4D4; - .header { - width: 100%; + .content { display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; + flex-direction: column; + align-items: flex-start; + padding: 16px; - .status { - .verifying { - color: $legacy-120; - } + .header { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; } - } - .detailContainer { - display: grid; - gap: 4px; - width: 100%; - margin-bottom: 16px; + .detailContainer { + display: grid; + gap: $sp-8; + width: 100%; + margin-bottom: 16px; + margin-top: 32px; - &.singleRow { - grid-template-columns: repeat(3, 1fr); - } - - &.stackedRows { - grid-template-columns: 1fr; - .detail:not(:last-child) { - margin-bottom: 8px; + &.singleRow { + grid-template-columns: repeat(3, 1fr); } - } - .detail { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; + &.stackedRows { + grid-template-columns: 1fr; + .detail:not(:last-child) { + margin-bottom: 8px; + } + } - .iconLabelContainer { + .detail { display: flex; - } + flex-direction: column; + align-items: flex-start; + gap: 4px; - .label { - margin-left: 8px; - color: $teal-140; - font-family: Roboto; - font-size: 12px; - font-weight: 700; - line-height: 16px; - letter-spacing: 1px; - text-align: left; - } + .iconLabelContainer { + display: flex; + } + + .label { + margin-left: 8px; + color: $teal-140; + font-family: $font-roboto-mono; + font-size: 12px; + font-weight: 700; + line-height: 16px; + letter-spacing: 1px; + text-align: left; + } - .value { - font-family: Roboto; - font-size: 16px; - font-weight: 400; - line-height: 24px; - letter-spacing: 0px; - text-align: left; + .value { + color: #2a2a2a; + font-size: 14px; + } } } + + .footer { + margin-top: 24px; + } } - .footer { - margin-top: 24px; + .actionItems { + padding-top: 16px; + margin-right: 24px; + + .actionItemsContainer { + display: flex; + flex-direction: column; + align-items: flex-end; + + .warningLabel { + color: $legacy-120; + } + } } } diff --git a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx index 2301d5075..cbfed7956 100644 --- a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx +++ b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx @@ -1,7 +1,11 @@ -import { Button, PageDivider } from '~/libs/ui' -import { CheckCircleIcon, ExclamationCircleIcon } from '@heroicons/react/solid' +import { useMemo, useState } from 'react' +import { CheckCircleIcon } from '@heroicons/react/solid' +import { Button, ConfirmModal, IconSolid, PageDivider } from '~/libs/ui' + +import { ActionBarItem } from '../action-bar-item' import { PaymentProvider } from '../../models/PaymentProvider' +import { ConfirmFlowData } from '../../models/ConfirmFlowData' import styles from './PaymentProviderCard.module.scss' @@ -16,67 +20,130 @@ interface PaymentProviderProps { logo: React.ReactNode details: Detail[] onConnectClick?: () => void + onResendOtpClick?: () => void + onGoToRegistrationClick?: () => void } const PaymentProviderCard: React.FC = (props: PaymentProviderProps) => { + const [confirmFlow, setConfirmFlow] = useState(undefined) + + const renderConfirmModalContent = useMemo(() => { + if (confirmFlow?.content === undefined) { + return undefined + } + + if (typeof confirmFlow?.content === 'function') { + return confirmFlow?.content() + } + + return confirmFlow?.content + }, [confirmFlow]) + const canConnect = props.provider.status === 'NOT_CONNECTED' - const isVerified = props.provider.status === 'VERIFIED' - const isPending = ['PENDING', 'OTP_VERIFIED'].includes(props.provider.status) - const PENDING_TO_LABEL_MAP: { - [key: string]: string - } = { - OTP_VERIFIED: 'Verifying', - PENDING: 'Pending OTP Verification', - } + const renderOtpPending = (): JSX.Element => ( + + ) - const detailContainerStyle = { - marginTop: canConnect ? '0px' : '32px', - } + const renderOtpVerified = (): JSX.Element => ( + + ) return ( -
-
-
{props.logo}
- {(isVerified || isPending) && ( -
- {isVerified &&
+ )} +
+
+ {props.provider.status === 'VERIFIED' + &&
+
+ {confirmFlow && ( + +
{renderConfirmModalContent}
+
)} -
+ ) } diff --git a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx index 8bc06b1ea..74d0673dd 100644 --- a/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx +++ b/src/apps/wallet/src/lib/components/tax-form-detail/TaxFormDetail.tsx @@ -5,16 +5,10 @@ import { ConfirmModal, IconSolid } from '~/libs/ui' import { IconCheckCircle } from '../../assets/tax-forms' import { ActionBarItem } from '../action-bar-item' +import { ConfirmFlowData } from '../../models/ConfirmFlowData' import styles from './TaxFormDetail.module.scss' -interface ConfirmFlowData { - title: string; - action: string; - content: React.ReactNode | (() => React.ReactNode) - callback?: () => void; -} - interface TaxFormDetailProps { title: string description: string diff --git a/src/apps/wallet/src/lib/models/ConfirmFlowData.ts b/src/apps/wallet/src/lib/models/ConfirmFlowData.ts new file mode 100644 index 000000000..29c7753e5 --- /dev/null +++ b/src/apps/wallet/src/lib/models/ConfirmFlowData.ts @@ -0,0 +1,6 @@ +export interface ConfirmFlowData { + title: string; + action: string; + content: React.ReactNode | (() => React.ReactNode) + callback?: () => void; +} diff --git a/src/apps/wallet/src/lib/models/PaymentProvider.ts b/src/apps/wallet/src/lib/models/PaymentProvider.ts index 172cb0c52..c78e84687 100644 --- a/src/apps/wallet/src/lib/models/PaymentProvider.ts +++ b/src/apps/wallet/src/lib/models/PaymentProvider.ts @@ -1,11 +1,9 @@ export interface PaymentProvider { + id?: number + upmId?: string type: 'Payoneer' | 'Paypal' name: 'Payoneer' | 'Paypal' description: string status: string -} - -export interface SetPaymentProviderResponse { - transactionId: string - registrationLink: string + transactionId?: string } diff --git a/src/apps/wallet/src/lib/services/wallet.ts b/src/apps/wallet/src/lib/services/wallet.ts index 083062129..971a5efce 100644 --- a/src/apps/wallet/src/lib/services/wallet.ts +++ b/src/apps/wallet/src/lib/services/wallet.ts @@ -4,7 +4,7 @@ import { EnvironmentConfig } from '~/config' import { xhrDeleteAsync, xhrGetAsync, xhrPostAsync, xhrPostAsyncWithBlobHandling } from '~/libs/core' import { WalletDetails } from '../models/WalletDetails' -import { PaymentProvider, SetPaymentProviderResponse } from '../models/PaymentProvider' +import { PaymentProvider } from '../models/PaymentProvider' import { WinningDetail } from '../models/WinningDetail' import { TaxForm } from '../models/TaxForm' import { OtpVerificationResponse } from '../models/OtpVerificationResponse' @@ -58,18 +58,16 @@ export async function getPayments(userId: string): Promise { } export async function setPaymentProvider( - details: any, type: string, - setDefault: boolean, -): Promise { +): Promise { const body = JSON.stringify({ - details, - setDefault, + details: {}, + setDefault: true, type, }) const url = `${baseUrl}/user/payment-method` - const response = await xhrPostAsync>(url, body) + const response = await xhrPostAsync>(url, body) if (response.status === 'error') { throw new Error('Error setting payment provider') @@ -123,7 +121,7 @@ export async function removeTaxForm(taxFormId: string): Promise { - const url = `${baseUrl}/user/tax-form/esign-url` + const url = `${baseUrl}/user/tax-form/view` const response = await xhrGetAsync>(url) if (response.status === 'error') { From af1ccf47b6e39a0275d7e2bccf0ebbc73d57d85f Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 16 Jan 2024 08:52:46 +0600 Subject: [PATCH 18/48] fix(tax-form): profile prop is not needed Signed-off-by: Rakib Ansary --- src/apps/wallet/src/home/tabs/WalletTabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/wallet/src/home/tabs/WalletTabs.tsx b/src/apps/wallet/src/home/tabs/WalletTabs.tsx index c35fdc861..17b0db0bb 100644 --- a/src/apps/wallet/src/home/tabs/WalletTabs.tsx +++ b/src/apps/wallet/src/home/tabs/WalletTabs.tsx @@ -41,7 +41,7 @@ const WalletTabs: FC = (props: WalletHomeProps) => { )} - {activeTab === WalletTabViews.withdrawalmethods && } + {activeTab === WalletTabViews.withdrawalmethods && } {activeTab === WalletTabViews.winnings && } From 4d90343e203d4aa84eff41fc414d8e856851aaba Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 16 Jan 2024 11:54:02 +0600 Subject: [PATCH 19/48] fix(payment-providers): layout fixes Signed-off-by: Rakib Ansary --- .../tabs/payments/PaymentsTab.module.scss | 1 + .../src/home/tabs/payments/PaymentsTab.tsx | 68 +++++++++++++++++-- .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 4 +- .../PaymentProviderCard.module.scss | 4 +- .../PaymentProviderCard.tsx | 28 +++++++- src/apps/wallet/src/lib/services/wallet.ts | 22 ++++++ 6 files changed, 113 insertions(+), 14 deletions(-) diff --git a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss index d384d2b1d..c6d8d6f81 100644 --- a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss +++ b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.module.scss @@ -83,6 +83,7 @@ display: grid; gap: $sp-4; grid-template-columns: repeat(1, 1fr); + margin-top: 24px; @media (min-width: '768px') { grid-template-columns: repeat(2, 1fr); diff --git a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx index ef16ca99f..29b1607f2 100644 --- a/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx +++ b/src/apps/wallet/src/home/tabs/payments/PaymentsTab.tsx @@ -12,7 +12,8 @@ import { PaymentProviderCard } from '../../../lib/components/payment-provider-ca import { OtpModal } from '../../../lib/components/otp-modal' import { TransactionResponse } from '../../../lib/models/TransactionId' import { - getUserPaymentProviders, resendOtp, setPaymentProvider, + getPaymentProviderRegistrationLink, + getUserPaymentProviders, removePaymentProvider, resendOtp, setPaymentProvider, } from '../../../lib/services/wallet' import { PaymentInfoModal } from './payment-info-modal' @@ -147,8 +148,28 @@ const PaymentsTab: FC = () => { provider={selectedPaymentProvider} logo={PAYMENT_PROVIDER_DETAILS[selectedPaymentProvider.type].logo} details={PAYMENT_PROVIDER_DETAILS[selectedPaymentProvider.type].details} - onGoToRegistrationClick={function onGoToRegistrationClick() { - // TODO: intentionally left empty + onGoToRegistrationClick={async function onGoToRegistrationClick() { + const type = selectedPaymentProvider.type + if (type === undefined) { + toast.error( + 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) + return + } + + try { + const response: TransactionResponse = await getPaymentProviderRegistrationLink(type) + setOtpFlow({ + ...response, + type: 'SETUP_PAYMENT_PROVIDER', + }) + } catch (err: unknown) { + toast.error( + (err as Error).message ?? 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) + } }} onResendOtpClick={async function onResendOtpClick() { const transactionId = selectedPaymentProvider.transactionId @@ -173,6 +194,31 @@ const PaymentsTab: FC = () => { ) } }} + onRemoveProvider={async function onRemoveProvider() { + const transactionId = selectedPaymentProvider.transactionId + if (transactionId === undefined) { + toast.error( + 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) + return + } + + try { + // eslint-disable-next-line max-len + const response: TransactionResponse = await removePaymentProvider(selectedPaymentProvider.type) + setOtpFlow({ + ...response, + type: 'REMOVE_PAYMENT_PROVIDER', + }) + fetchPaymentProviders(false) + } catch (err: unknown) { + toast.error( + (err as Error).message ?? 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) + } + }} />
)} + {canCancel && ( +
+
+ )}
{props.provider.status === 'VERIFIED' diff --git a/src/apps/wallet/src/lib/services/wallet.ts b/src/apps/wallet/src/lib/services/wallet.ts index 971a5efce..1fdf0d8d8 100644 --- a/src/apps/wallet/src/lib/services/wallet.ts +++ b/src/apps/wallet/src/lib/services/wallet.ts @@ -93,6 +93,28 @@ export async function confirmPaymentProvider(provider: string, code: string, tra return response.data } +export async function getPaymentProviderRegistrationLink(type: string): Promise { + const url = `${baseUrl}/user/payment-method/${type}/registration-link` + const response = await xhrGetAsync>(url) + + if (response.status === 'error') { + throw new Error('Error getting payment provider registration link') + } + + return response.data +} + +export async function removePaymentProvider(type: string): Promise { + const url = `${baseUrl}/user/payment-method/${type}` + const response = await xhrDeleteAsync>(url) + + if (response.status === 'error') { + throw new Error('Error getting payment provider registration link') + } + + return response.data +} + export async function setupTaxForm(userId: string, taxForm: string): Promise { const body = JSON.stringify({ taxForm, From de20599831df945e4951c8f25f842c7e274e8be1 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 16 Jan 2024 13:54:12 +0600 Subject: [PATCH 20/48] fix(payment-providers): style & layout Signed-off-by: Rakib Ansary --- .../payment-provider-card/PaymentProviderCard.module.scss | 1 + .../payment-provider-card/PaymentProviderCard.tsx | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss index afefeaf82..3a56c65d7 100644 --- a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss +++ b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss @@ -30,6 +30,7 @@ &.singleRow { grid-template-columns: repeat(3, 1fr); margin-top: 24px; + gap: $sp-6; } &.stackedRows { diff --git a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx index 38ddf4aec..7767a6224 100644 --- a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx +++ b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx @@ -41,7 +41,7 @@ const PaymentProviderCard: React.FC = (props: PaymentProvi }, [confirmFlow]) const canConnect = props.provider.status === 'NOT_CONNECTED' - const canCancel = ['OTP_PENDING', 'OTP_VERIFIED', 'VERIFIED'].includes(props.provider.status) + const canCancel = ['OTP_PENDING', 'OTP_VERIFIED', 'VERIFIED', 'CONNECTED'].includes(props.provider.status) const renderOtpPending = (): JSX.Element => ( = (props: PaymentProvi info={{ className: styles.warningLabel, icon: IconSolid.ExclamationCircleIcon, - text: 'PENDING REGISTRATION', + text: 'PENDING VERIFICATION', }} action={{ className: styles.actionButton, @@ -143,7 +143,7 @@ const PaymentProviderCard: React.FC = (props: PaymentProvi )}
- {props.provider.status === 'VERIFIED' + {props.provider.status === 'CONNECTED' && + {!loading && showResendButton && ( + )}
From 71231b5ae1db0917154f16500c97366a425e8367 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 16 Jan 2024 20:10:23 +0600 Subject: [PATCH 24/48] fix(core-182): resend otp flow Signed-off-by: Rakib Ansary --- .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 18 ------------------ .../src/lib/components/otp-modal/OtpModal.tsx | 1 - 2 files changed, 19 deletions(-) diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx index d97f10cc0..f64328366 100644 --- a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx @@ -268,24 +268,6 @@ const PaymentsTab: FC = (props: TaxFormsTabProps) => { onClose={function onOtpModalClose() { setOtpFlow(undefined) }} - onResendClick={async function onResendClick() { - toast.info( - 'Sending OTP...', - { position: toast.POSITION.BOTTOM_RIGHT }, - ) - try { - await resendOtp(otpFlow.transactionId) - toast.success( - 'OTP sent successfully.', - { position: toast.POSITION.BOTTOM_RIGHT }, - ) - } catch (err: unknown) { - toast.error( - (err as Error).message ?? 'Something went wrong. Please try again.', - { position: toast.POSITION.BOTTOM_RIGHT }, - ) - } - }} onOtpVerified={function onOtpVerified(data: unknown) { switch (otpFlow.type) { case 'REMOVE_TAX_FORM': diff --git a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx index 6718aa789..0b23a7eb7 100644 --- a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx +++ b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx @@ -16,7 +16,6 @@ interface OtpModalProps { userEmail?: string isBlob?: boolean onClose: () => void - onResendClick?: () => void onOtpVerified: (data: unknown) => void } From 86fc1d1f97dc821050df91569e9448ccfaacf5df Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 16 Jan 2024 20:11:39 +0600 Subject: [PATCH 25/48] fix(core-182): resend otp flow Signed-off-by: Rakib Ansary --- src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx index 0b23a7eb7..4434ee93a 100644 --- a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx +++ b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx @@ -9,6 +9,8 @@ import { OtpVerificationResponse } from '../../models/OtpVerificationResponse' import styles from './OtpModal.module.scss' +const RESEND_OTP_TIMEOUT = 60000 + interface OtpModalProps { isOpen: boolean key: string @@ -32,7 +34,7 @@ const OtpModal: FC = (props: OtpModalProps) => { setShowResendButton(false) timer = setTimeout(() => { setShowResendButton(true) - }, 60000) + }, RESEND_OTP_TIMEOUT) } return () => { @@ -114,6 +116,9 @@ const OtpModal: FC = (props: OtpModalProps) => { try { await resendOtp(props.transactionId) setShowResendButton(false) + setTimeout(() => { + setShowResendButton(true) + }, RESEND_OTP_TIMEOUT) toast.success( 'OTP sent successfully.', { position: toast.POSITION.BOTTOM_RIGHT }, From 8f6638f2bcd7fe2f3ccf831a9705d1900b8b16f8 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 16 Jan 2024 20:41:45 +0600 Subject: [PATCH 26/48] fix(core-171): show release date Signed-off-by: Rakib Ansary --- src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx | 3 ++- .../src/lib/components/payments-table/PaymentTable.tsx | 8 ++++++-- src/apps/wallet/src/lib/models/WinningDetail.ts | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index b903fdcad..ef3ac4981 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -59,13 +59,14 @@ const ListView: FC = (props: ListViewProps) => { payments.forEach((payment: WinningDetail) => { payment.details.forEach(detail => { const winning: Winning = { + canBeReleased: new Date(payment.releaseDate) <= new Date(), createDate: formatIOSDateString(payment.createdAt), datePaid: '', description: payment.description, id: payment.id, installment: detail.installmentNumber, netPayment: `${detail.netAmount}`, - releaseDate: '', + releaseDate: formatIOSDateString(payment.releaseDate), status: detail.status, type: payment.type, } diff --git a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx index 79d50db52..77c2610ff 100644 --- a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx +++ b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx @@ -64,6 +64,7 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => payment.status !== 'OWED') === undefined} checked={isAllSelected} aria-label='Select all payments' /> @@ -72,7 +73,10 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => {props.payments.map(payment => ( - + {payment.description} {payment.type} {payment.installment} @@ -84,7 +88,7 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => togglePaymentSelection(payment.id)} aria-label={`Select payment ${payment.id}`} diff --git a/src/apps/wallet/src/lib/models/WinningDetail.ts b/src/apps/wallet/src/lib/models/WinningDetail.ts index 16c61bd7c..3955550d3 100644 --- a/src/apps/wallet/src/lib/models/WinningDetail.ts +++ b/src/apps/wallet/src/lib/models/WinningDetail.ts @@ -8,6 +8,7 @@ export interface Winning { releaseDate: string datePaid: string installment: number + canBeReleased: boolean } export interface WinningDetail { @@ -32,4 +33,5 @@ export interface WinningDetail { currency: string }[] createdAt: string + releaseDate: string } From c958cf6f2eaf18624aac8a9cb0dbc2e1c0c1928e Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 16 Jan 2024 20:45:12 +0600 Subject: [PATCH 27/48] fix(core-171): show release date Signed-off-by: Rakib Ansary --- src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx | 2 +- src/apps/wallet/src/lib/models/WinningDetail.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index ef3ac4981..552fea441 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -61,7 +61,7 @@ const ListView: FC = (props: ListViewProps) => { const winning: Winning = { canBeReleased: new Date(payment.releaseDate) <= new Date(), createDate: formatIOSDateString(payment.createdAt), - datePaid: '', + datePaid: payment.datePaid !== undefined ? formatIOSDateString(payment.datePaid) : '', description: payment.description, id: payment.id, installment: detail.installmentNumber, diff --git a/src/apps/wallet/src/lib/models/WinningDetail.ts b/src/apps/wallet/src/lib/models/WinningDetail.ts index 3955550d3..f8288fab1 100644 --- a/src/apps/wallet/src/lib/models/WinningDetail.ts +++ b/src/apps/wallet/src/lib/models/WinningDetail.ts @@ -34,4 +34,5 @@ export interface WinningDetail { }[] createdAt: string releaseDate: string + datePaid: string } From 8fac88a6f2706a903c491f0655b0b063cfcdd6e2 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 16 Jan 2024 21:13:36 +0600 Subject: [PATCH 28/48] fix(payment-providers): connected label should not be clickable Signed-off-by: Rakib Ansary --- .../components/payment-provider-card/PaymentProviderCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx index 7767a6224..0a4fad84c 100644 --- a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx +++ b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx @@ -144,7 +144,7 @@ const PaymentProviderCard: React.FC = (props: PaymentProvi
{props.provider.status === 'CONNECTED' - &&
From e56e3f6aab95854fbeda7e873db1365872f17da9 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Wed, 17 Jan 2024 09:40:36 +0600 Subject: [PATCH 29/48] fix(winnings): club installments together Signed-off-by: Rakib Ansary --- .../src/home/tabs/winnings/WinningsTab.tsx | 29 +++++++++---------- .../payments-table/PaymentTable.tsx | 19 +++++++++--- .../wallet/src/lib/models/WinningDetail.ts | 23 ++++++++------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index 552fea441..bbb8a8030 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -57,21 +57,20 @@ const ListView: FC = (props: ListViewProps) => { const tempWinnings: Winning[] = [] payments.forEach((payment: WinningDetail) => { - payment.details.forEach(detail => { - const winning: Winning = { - canBeReleased: new Date(payment.releaseDate) <= new Date(), - createDate: formatIOSDateString(payment.createdAt), - datePaid: payment.datePaid !== undefined ? formatIOSDateString(payment.datePaid) : '', - description: payment.description, - id: payment.id, - installment: detail.installmentNumber, - netPayment: `${detail.netAmount}`, - releaseDate: formatIOSDateString(payment.releaseDate), - status: detail.status, - type: payment.type, - } - tempWinnings.push(winning) - }) + const winning: Winning = { + canBeReleased: new Date(payment.releaseDate) <= new Date(), + createDate: formatIOSDateString(payment.createdAt), + currency: payment.details[0].currency, + datePaid: payment.datePaid !== undefined ? formatIOSDateString(payment.datePaid) : '', + description: payment.description, + details: payment.details, + id: payment.id, + netPayment: `${payment.details[0].totalAmount}`, + releaseDate: formatIOSDateString(payment.releaseDate), + status: payment.details[0].status, + type: payment.category.replaceAll('_', ' '), + } + tempWinnings.push(winning) }) return tempWinnings diff --git a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx index 77c2610ff..dc6e2fc7c 100644 --- a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx +++ b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx @@ -8,6 +8,19 @@ import { Winning } from '../../models/WinningDetail' import styles from './PaymentTable.module.scss' +const mapCurrency = (currency: string): string => { + switch (currency) { + case 'USD': + return '$' + case 'GBP': + return '£' + case 'EUR': + return '€' + default: + return currency + } +} + interface PaymentTableProps { payments: ReadonlyArray onPayMeClick: (paymentIds: { [paymentId: string]: boolean }) => void @@ -54,7 +67,6 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => Description Type - Installment Create Date Net Payment Status @@ -74,14 +86,13 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => {props.payments.map(payment => ( {payment.description} {payment.type} - {payment.installment} {payment.createDate} - {payment.netPayment} + {`${payment.netPayment} ${mapCurrency(payment.currency)}`} {payment.status} {payment.releaseDate} {payment.datePaid} diff --git a/src/apps/wallet/src/lib/models/WinningDetail.ts b/src/apps/wallet/src/lib/models/WinningDetail.ts index f8288fab1..9e07afbe7 100644 --- a/src/apps/wallet/src/lib/models/WinningDetail.ts +++ b/src/apps/wallet/src/lib/models/WinningDetail.ts @@ -1,3 +1,13 @@ +export interface PaymentDetail { + id: string + netAmount: string + grossAmount: string + totalAmount: string + installmentNumber: number + status: string + currency: string +} + export interface Winning { id: string description: string @@ -7,8 +17,9 @@ export interface Winning { status: string releaseDate: string datePaid: string - installment: number canBeReleased: boolean + currency: string + details: PaymentDetail[] } export interface WinningDetail { @@ -23,15 +34,7 @@ export interface WinningDetail { attributes: { url: string } - details: { - id: string - netAmount: string - grossAmount: string - totalAmount: string - installmentNumber: number - status: string - currency: string - }[] + details: PaymentDetail[] createdAt: string releaseDate: string datePaid: string From 77618e1fe6e34b8371f4762231f1915a97847bb9 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Wed, 17 Jan 2024 09:52:04 +0600 Subject: [PATCH 30/48] fix(winnings): currency format Signed-off-by: Rakib Ansary --- .../wallet/src/home/tabs/winnings/WinningsTab.tsx | 13 +++++++++++-- .../components/payments-table/PaymentTable.tsx | 15 +-------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index bbb8a8030..6fbc1ebf4 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -61,11 +61,20 @@ const ListView: FC = (props: ListViewProps) => { canBeReleased: new Date(payment.releaseDate) <= new Date(), createDate: formatIOSDateString(payment.createdAt), currency: payment.details[0].currency, - datePaid: payment.datePaid !== undefined ? formatIOSDateString(payment.datePaid) : '', + // eslint-disable-next-line max-len + datePaid: payment.datePaid !== undefined && payment.datePaid.length ? formatIOSDateString(payment.datePaid) : '-', description: payment.description, details: payment.details, id: payment.id, - netPayment: `${payment.details[0].totalAmount}`, + netPayment: `${new Intl.NumberFormat('en-US', { + currency: payment.details[0].currency, + maximumFractionDigits: 2, + minimumFractionDigits: 2, + style: 'currency', + }) + .format( + Number(payment.details[0].totalAmount), + )}`, releaseDate: formatIOSDateString(payment.releaseDate), status: payment.details[0].status, type: payment.category.replaceAll('_', ' '), diff --git a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx index dc6e2fc7c..240fcfcfd 100644 --- a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx +++ b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx @@ -8,19 +8,6 @@ import { Winning } from '../../models/WinningDetail' import styles from './PaymentTable.module.scss' -const mapCurrency = (currency: string): string => { - switch (currency) { - case 'USD': - return '$' - case 'GBP': - return '£' - case 'EUR': - return '€' - default: - return currency - } -} - interface PaymentTableProps { payments: ReadonlyArray onPayMeClick: (paymentIds: { [paymentId: string]: boolean }) => void @@ -92,7 +79,7 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => {payment.description} {payment.type} {payment.createDate} - {`${payment.netPayment} ${mapCurrency(payment.currency)}`} + {payment.netPayment} {payment.status} {payment.releaseDate} {payment.datePaid} From 695f604df899eaab067dfc82528df17577aeecae Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Wed, 17 Jan 2024 19:48:29 +0600 Subject: [PATCH 31/48] fix(core-246): email should be bold Signed-off-by: Rakib Ansary --- src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx index 4434ee93a..d91a0e976 100644 --- a/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx +++ b/src/apps/wallet/src/lib/components/otp-modal/OtpModal.tsx @@ -83,7 +83,7 @@ const OtpModal: FC = (props: OtpModalProps) => {

For added security we’ve sent a 6-digit code to your {' '} - {props.userEmail ?? '***@gmail.com'} + {props.userEmail ?? '***@gmail.com'} {' '} email. The code expires shortly, so please enter it soon. From 6159ed6aa5715ae8f6e063bfc6ad7dd4fc898ec8 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Wed, 24 Jan 2024 23:34:05 +0600 Subject: [PATCH 32/48] feat(wallet-admin): first draft Signed-off-by: Rakib Ansary --- craco.config.js | 1 + src/apps/platform/src/platform.routes.tsx | 2 + src/apps/wallet-admin/.prettierrc | 7 + src/apps/wallet-admin/README.md | 0 src/apps/wallet-admin/index.tsx | 1 + src/apps/wallet-admin/src/WalletAdminApp.tsx | 20 ++ .../wallet-admin/src/home/WalletHomePage.tsx | 19 ++ src/apps/wallet-admin/src/home/index.ts | 1 + .../home/page-layout/WalletLayout.module.scss | 5 + .../src/home/page-layout/WalletLayout.tsx | 20 ++ .../src/home/page-layout/index.ts | 1 + .../src/home/tabs/WalletTabs.module.scss | 11 + .../wallet-admin/src/home/tabs/WalletTabs.tsx | 54 ++++ .../src/home/tabs/config/index.ts | 1 + .../home/tabs/config/wallet-tabs-config.ts | 48 ++++ src/apps/wallet-admin/src/home/tabs/index.ts | 1 + .../tabs/payments/PaymentsTab.module.scss | 28 ++ .../src/home/tabs/payments/PaymentsTab.tsx | 63 +++++ .../src/home/tabs/payments/index.ts | 1 + .../tabs/tax-forms/TaxFormsTab.module.scss | 28 ++ .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 68 +++++ .../src/home/tabs/tax-forms/index.ts | 1 + .../home/tabs/winnings/Winnings.module.scss | 28 ++ .../src/home/tabs/winnings/WinningsTab.tsx | 150 ++++++++++ .../src/home/tabs/winnings/index.ts | 1 + src/apps/wallet-admin/src/index.ts | 1 + .../src/lib/assets/home/banner-image.svg | 11 + .../src/lib/assets/home/banner-text.svg | 5 + .../wallet-admin/src/lib/assets/home/index.ts | 4 + src/apps/wallet-admin/src/lib/assets/index.ts | 5 + .../lib/assets/payments/PayPal_logo_gray.svg | 8 + .../lib/assets/payments/Payoneer_log_gray.svg | 4 + .../assets/payments/Payoneer_logo_color.svg | 15 + .../lib/assets/payments/Paypal_logo_color.svg | 14 + .../payments/Western_Union_Logo_gray.svg | 13 + .../src/lib/assets/payments/icon-dollar.svg | 3 + .../src/lib/assets/payments/icon-speed.svg | 3 + .../src/lib/assets/payments/icon-world.svg | 3 + .../src/lib/assets/payments/index.ts | 8 + .../src/lib/assets/preferences/email.svg | 4 + .../src/lib/assets/preferences/forum.svg | 4 + .../src/lib/assets/preferences/index.ts | 7 + .../src/lib/assets/security/apple-store.svg | 34 +++ .../src/lib/assets/security/credential.png | Bin 0 -> 47241 bytes .../src/lib/assets/security/dicelogo.png | Bin 0 -> 3067 bytes .../src/lib/assets/security/dicelogobig.png | Bin 0 -> 23747 bytes .../src/lib/assets/security/dicelogosmall.png | Bin 0 -> 6681 bytes .../src/lib/assets/security/google-play.png | Bin 0 -> 7000 bytes .../src/lib/assets/security/index.ts | 17 ++ .../src/lib/assets/security/mfa.svg | 4 + .../src/lib/assets/security/unsuccessful.svg | 3 + .../lib/assets/tax-forms/ic-check-circle.svg | 3 + .../src/lib/assets/tax-forms/ic-earth.svg | 30 ++ .../src/lib/assets/tax-forms/ic-us.svg | 25 ++ .../src/lib/assets/tax-forms/index.ts | 5 + .../src/lib/assets/tcandyou/data_science.svg | 11 + .../src/lib/assets/tcandyou/design.svg | 14 + .../src/lib/assets/tcandyou/develop.svg | 11 + .../src/lib/assets/tcandyou/ico-ethereum.png | Bin 0 -> 2649 bytes .../src/lib/assets/tcandyou/ico-ibmcloud.png | Bin 0 -> 3424 bytes .../src/lib/assets/tcandyou/ico-veteran.png | Bin 0 -> 16329 bytes .../src/lib/assets/tcandyou/index.ts | 15 + .../assets/tools/Financial Institution.svg | 6 + .../tools/Internet Service Provider.svg | 6 + .../src/lib/assets/tools/Mobile Carrier.svg | 6 + .../src/lib/assets/tools/Television.svg | 16 ++ .../src/lib/assets/tools/console.svg | 1 + .../src/lib/assets/tools/desktop.svg | 4 + .../src/lib/assets/tools/index.ts | 31 +++ .../src/lib/assets/tools/laptop.svg | 1 + .../src/lib/assets/tools/other_device.svg | 9 + .../assets/tools/other_service_provider.svg | 17 ++ .../src/lib/assets/tools/smartphone.svg | 4 + .../src/lib/assets/tools/software.svg | 8 + .../src/lib/assets/tools/subscription.svg | 3 + .../src/lib/assets/tools/tablet.svg | 1 + .../src/lib/assets/tools/wearable.svg | 1 + .../action-bar-item/ActionBarItem.tsx | 56 ++++ .../lib/components/action-bar-item/index.ts | 1 + .../src/lib/components/chip/Chip.module.scss | 18 ++ .../src/lib/components/chip/Chip.tsx | 11 + .../src/lib/components/chip/index.ts | 1 + .../filter-bar/FilterBar.module.scss | 21 ++ .../lib/components/filter-bar/FilterBar.tsx | 78 ++++++ .../src/lib/components/filter-bar/index.ts | 1 + .../wallet-admin/src/lib/components/index.ts | 4 + .../components/info-row/InfoRow.module.scss | 38 +++ .../src/lib/components/info-row/InfoRow.tsx | 21 ++ .../src/lib/components/info-row/index.ts | 1 + .../components/otp-modal/OtpModal.module.scss | 87 ++++++ .../src/lib/components/otp-modal/OtpModal.tsx | 140 ++++++++++ .../src/lib/components/otp-modal/index.ts | 1 + .../PaymentProviderCard.module.scss | 90 ++++++ .../PaymentProviderCard.tsx | 172 ++++++++++++ .../components/payment-provider-card/index.ts | 1 + .../payments-table/PaymentTable.module.scss | 77 ++++++ .../payments-table/PaymentTable.tsx | 119 ++++++++ .../SettingSection.module.scss | 26 ++ .../setting-section/SettingSection.tsx | 30 ++ .../lib/components/setting-section/index.ts | 1 + .../tax-form-card/TaxFormCard.module.scss | 46 ++++ .../components/tax-form-card/TaxFormCard.tsx | 83 ++++++ .../src/lib/components/tax-form-card/index.ts | 1 + .../tax-form-detail/TaxFormDetail.module.scss | 92 +++++++ .../tax-form-detail/TaxFormDetail.tsx | 140 ++++++++++ .../lib/components/tax-form-detail/index.ts | 1 + src/apps/wallet-admin/src/lib/index.ts | 3 + .../wallet-admin/src/lib/models/ApiError.ts | 4 + .../src/lib/models/ApiResponse.ts | 4 + .../src/lib/models/ConfirmFlowData.ts | 6 + .../src/lib/models/OtpVerificationResponse.ts | 3 + .../src/lib/models/PaymentProvider.ts | 9 + .../src/lib/models/ResendOtpResponse.ts | 3 + .../wallet-admin/src/lib/models/TaxForm.ts | 18 ++ .../src/lib/models/TransactionId.ts | 5 + .../src/lib/models/WalletDetails.ts | 19 ++ .../src/lib/models/WinningDetail.ts | 41 +++ .../wallet-admin/src/lib/services/wallet.ts | 259 ++++++++++++++++++ .../src/lib/wallet-swr/WalletSwr.tsx | 23 ++ .../wallet-admin/src/lib/wallet-swr/index.ts | 1 + .../wallet-admin/src/wallet-admin.routes.tsx | 34 +++ .../src/home/tabs/winnings/WinningsTab.tsx | 11 +- .../filter-bar/FilterBar.module.scss | 0 .../lib/components/filter-bar/FilterBar.tsx | 47 ++++ .../src/lib/components/filter-bar/index.ts | 1 + src/apps/wallet/src/lib/components/index.ts | 1 + src/config/constants.ts | 2 + .../profile-factory/user-role.enum.ts | 8 +- tsconfig.paths.json | 3 + 129 files changed, 2819 insertions(+), 2 deletions(-) create mode 100644 src/apps/wallet-admin/.prettierrc create mode 100644 src/apps/wallet-admin/README.md create mode 100644 src/apps/wallet-admin/index.tsx create mode 100644 src/apps/wallet-admin/src/WalletAdminApp.tsx create mode 100644 src/apps/wallet-admin/src/home/WalletHomePage.tsx create mode 100644 src/apps/wallet-admin/src/home/index.ts create mode 100644 src/apps/wallet-admin/src/home/page-layout/WalletLayout.module.scss create mode 100644 src/apps/wallet-admin/src/home/page-layout/WalletLayout.tsx create mode 100644 src/apps/wallet-admin/src/home/page-layout/index.ts create mode 100644 src/apps/wallet-admin/src/home/tabs/WalletTabs.module.scss create mode 100644 src/apps/wallet-admin/src/home/tabs/WalletTabs.tsx create mode 100644 src/apps/wallet-admin/src/home/tabs/config/index.ts create mode 100644 src/apps/wallet-admin/src/home/tabs/config/wallet-tabs-config.ts create mode 100644 src/apps/wallet-admin/src/home/tabs/index.ts create mode 100644 src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.module.scss create mode 100644 src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx create mode 100644 src/apps/wallet-admin/src/home/tabs/payments/index.ts create mode 100644 src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss create mode 100644 src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx create mode 100644 src/apps/wallet-admin/src/home/tabs/tax-forms/index.ts create mode 100644 src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss create mode 100644 src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx create mode 100644 src/apps/wallet-admin/src/home/tabs/winnings/index.ts create mode 100644 src/apps/wallet-admin/src/index.ts create mode 100644 src/apps/wallet-admin/src/lib/assets/home/banner-image.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/home/banner-text.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/home/index.ts create mode 100644 src/apps/wallet-admin/src/lib/assets/index.ts create mode 100644 src/apps/wallet-admin/src/lib/assets/payments/PayPal_logo_gray.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/payments/Payoneer_log_gray.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/payments/Payoneer_logo_color.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/payments/Paypal_logo_color.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/payments/Western_Union_Logo_gray.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/payments/icon-dollar.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/payments/icon-speed.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/payments/icon-world.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/payments/index.ts create mode 100755 src/apps/wallet-admin/src/lib/assets/preferences/email.svg create mode 100755 src/apps/wallet-admin/src/lib/assets/preferences/forum.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/preferences/index.ts create mode 100644 src/apps/wallet-admin/src/lib/assets/security/apple-store.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/security/credential.png create mode 100644 src/apps/wallet-admin/src/lib/assets/security/dicelogo.png create mode 100644 src/apps/wallet-admin/src/lib/assets/security/dicelogobig.png create mode 100644 src/apps/wallet-admin/src/lib/assets/security/dicelogosmall.png create mode 100644 src/apps/wallet-admin/src/lib/assets/security/google-play.png create mode 100644 src/apps/wallet-admin/src/lib/assets/security/index.ts create mode 100644 src/apps/wallet-admin/src/lib/assets/security/mfa.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/security/unsuccessful.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tax-forms/ic-check-circle.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tax-forms/ic-earth.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tax-forms/ic-us.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tax-forms/index.ts create mode 100644 src/apps/wallet-admin/src/lib/assets/tcandyou/data_science.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tcandyou/design.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tcandyou/develop.svg create mode 100755 src/apps/wallet-admin/src/lib/assets/tcandyou/ico-ethereum.png create mode 100755 src/apps/wallet-admin/src/lib/assets/tcandyou/ico-ibmcloud.png create mode 100644 src/apps/wallet-admin/src/lib/assets/tcandyou/ico-veteran.png create mode 100644 src/apps/wallet-admin/src/lib/assets/tcandyou/index.ts create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/Financial Institution.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/Internet Service Provider.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/Mobile Carrier.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/Television.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/console.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/desktop.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/index.ts create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/laptop.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/other_device.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/other_service_provider.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/smartphone.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/software.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/subscription.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/tablet.svg create mode 100644 src/apps/wallet-admin/src/lib/assets/tools/wearable.svg create mode 100644 src/apps/wallet-admin/src/lib/components/action-bar-item/ActionBarItem.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/action-bar-item/index.ts create mode 100644 src/apps/wallet-admin/src/lib/components/chip/Chip.module.scss create mode 100644 src/apps/wallet-admin/src/lib/components/chip/Chip.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/chip/index.ts create mode 100644 src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.module.scss create mode 100644 src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/filter-bar/index.ts create mode 100644 src/apps/wallet-admin/src/lib/components/index.ts create mode 100644 src/apps/wallet-admin/src/lib/components/info-row/InfoRow.module.scss create mode 100644 src/apps/wallet-admin/src/lib/components/info-row/InfoRow.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/info-row/index.ts create mode 100644 src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.module.scss create mode 100644 src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/otp-modal/index.ts create mode 100644 src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss create mode 100644 src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/payment-provider-card/index.ts create mode 100644 src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss create mode 100644 src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.module.scss create mode 100644 src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/setting-section/index.ts create mode 100644 src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.module.scss create mode 100644 src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/tax-form-card/index.ts create mode 100644 src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.module.scss create mode 100644 src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/tax-form-detail/index.ts create mode 100644 src/apps/wallet-admin/src/lib/index.ts create mode 100644 src/apps/wallet-admin/src/lib/models/ApiError.ts create mode 100644 src/apps/wallet-admin/src/lib/models/ApiResponse.ts create mode 100644 src/apps/wallet-admin/src/lib/models/ConfirmFlowData.ts create mode 100644 src/apps/wallet-admin/src/lib/models/OtpVerificationResponse.ts create mode 100644 src/apps/wallet-admin/src/lib/models/PaymentProvider.ts create mode 100644 src/apps/wallet-admin/src/lib/models/ResendOtpResponse.ts create mode 100644 src/apps/wallet-admin/src/lib/models/TaxForm.ts create mode 100644 src/apps/wallet-admin/src/lib/models/TransactionId.ts create mode 100644 src/apps/wallet-admin/src/lib/models/WalletDetails.ts create mode 100644 src/apps/wallet-admin/src/lib/models/WinningDetail.ts create mode 100644 src/apps/wallet-admin/src/lib/services/wallet.ts create mode 100644 src/apps/wallet-admin/src/lib/wallet-swr/WalletSwr.tsx create mode 100644 src/apps/wallet-admin/src/lib/wallet-swr/index.ts create mode 100644 src/apps/wallet-admin/src/wallet-admin.routes.tsx create mode 100644 src/apps/wallet/src/lib/components/filter-bar/FilterBar.module.scss create mode 100644 src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx create mode 100644 src/apps/wallet/src/lib/components/filter-bar/index.ts diff --git a/craco.config.js b/craco.config.js index 1e2d52d01..ce20f52b2 100644 --- a/craco.config.js +++ b/craco.config.js @@ -44,6 +44,7 @@ module.exports = { '@talentSearch': resolve('src/apps/talent-search/src'), '@profiles': resolve('src/apps/profiles/src'), '@wallet': resolve('src/apps/wallet/src'), + '@walletAdmin': resolve('src/apps/wallet-admin/src'), '@platform': resolve('src/apps/platform/src'), // aliases used in SCSS files diff --git a/src/apps/platform/src/platform.routes.tsx b/src/apps/platform/src/platform.routes.tsx index 6dd61530c..fb156a3eb 100644 --- a/src/apps/platform/src/platform.routes.tsx +++ b/src/apps/platform/src/platform.routes.tsx @@ -10,6 +10,7 @@ import { talentSearchRoutes } from '~/apps/talent-search' import { accountsRoutes } from '~/apps/accounts' import { onboardingRoutes } from '~/apps/onboarding' import { walletRoutes } from '~/apps/wallet' +import { walletAdminRoutes } from '~/apps/wallet-admin' const Home: LazyLoadedComponent = lazyLoad( () => import('./routes/home'), @@ -37,6 +38,7 @@ export const platformRoutes: Array = [ ...talentSearchRoutes, ...profilesRoutes, ...walletRoutes, + ...walletAdminRoutes, ...accountsRoutes, ...homeRoutes, ] diff --git a/src/apps/wallet-admin/.prettierrc b/src/apps/wallet-admin/.prettierrc new file mode 100644 index 000000000..35288c5c7 --- /dev/null +++ b/src/apps/wallet-admin/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "trailingComma": "all", + "jsxSingleQuote": true, + "jsxBracketSameLine": true, + "printWidth": 120 +} diff --git a/src/apps/wallet-admin/README.md b/src/apps/wallet-admin/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/src/apps/wallet-admin/index.tsx b/src/apps/wallet-admin/index.tsx new file mode 100644 index 000000000..6f39cd49b --- /dev/null +++ b/src/apps/wallet-admin/index.tsx @@ -0,0 +1 @@ +export * from './src' diff --git a/src/apps/wallet-admin/src/WalletAdminApp.tsx b/src/apps/wallet-admin/src/WalletAdminApp.tsx new file mode 100644 index 000000000..58764103d --- /dev/null +++ b/src/apps/wallet-admin/src/WalletAdminApp.tsx @@ -0,0 +1,20 @@ +import { FC, useContext } from 'react' +import { Outlet, Routes } from 'react-router-dom' + +import { routerContext, RouterContextData } from '~/libs/core' + +import { toolTitle } from './wallet-admin.routes' +import { WalletSwr } from './lib' + +const AccountsApp: FC<{}> = () => { + const { getChildRoutes }: RouterContextData = useContext(routerContext) + + return ( + + + {getChildRoutes(toolTitle)} + + ) +} + +export default AccountsApp diff --git a/src/apps/wallet-admin/src/home/WalletHomePage.tsx b/src/apps/wallet-admin/src/home/WalletHomePage.tsx new file mode 100644 index 000000000..0fafad41b --- /dev/null +++ b/src/apps/wallet-admin/src/home/WalletHomePage.tsx @@ -0,0 +1,19 @@ +import { FC, useContext } from 'react' + +import { profileContext, ProfileContextData } from '~/libs/core' +import { LoadingSpinner } from '~/libs/ui' + +import { WalletLayout } from './page-layout' + +const AccountSettingsPage: FC<{}> = () => { + const { profile, initialized }: ProfileContextData = useContext(profileContext) + + return ( + <> + + {initialized && profile && } + + ) +} + +export default AccountSettingsPage diff --git a/src/apps/wallet-admin/src/home/index.ts b/src/apps/wallet-admin/src/home/index.ts new file mode 100644 index 000000000..f3370c004 --- /dev/null +++ b/src/apps/wallet-admin/src/home/index.ts @@ -0,0 +1 @@ +export { default as WalletHomePage } from './WalletHomePage' diff --git a/src/apps/wallet-admin/src/home/page-layout/WalletLayout.module.scss b/src/apps/wallet-admin/src/home/page-layout/WalletLayout.module.scss new file mode 100644 index 000000000..6ff4b2df4 --- /dev/null +++ b/src/apps/wallet-admin/src/home/page-layout/WalletLayout.module.scss @@ -0,0 +1,5 @@ +@import '@libs/ui/styles/includes'; + +.contentLayoutOuter { + margin: $sp-8 auto !important; +} \ No newline at end of file diff --git a/src/apps/wallet-admin/src/home/page-layout/WalletLayout.tsx b/src/apps/wallet-admin/src/home/page-layout/WalletLayout.tsx new file mode 100644 index 000000000..b7cdb2703 --- /dev/null +++ b/src/apps/wallet-admin/src/home/page-layout/WalletLayout.tsx @@ -0,0 +1,20 @@ +import { FC } from 'react' + +import { UserProfile } from '~/libs/core' +import { ContentLayout } from '~/libs/ui' + +import { WalletTabs } from '../tabs' + +import styles from './WalletLayout.module.scss' + +interface WalletHomeLayoutProps { + profile: UserProfile +} + +const WalletLayout: FC = (props: WalletHomeLayoutProps) => ( + + + +) + +export default WalletLayout diff --git a/src/apps/wallet-admin/src/home/page-layout/index.ts b/src/apps/wallet-admin/src/home/page-layout/index.ts new file mode 100644 index 000000000..df2160a7c --- /dev/null +++ b/src/apps/wallet-admin/src/home/page-layout/index.ts @@ -0,0 +1 @@ +export { default as WalletLayout } from './WalletLayout' diff --git a/src/apps/wallet-admin/src/home/tabs/WalletTabs.module.scss b/src/apps/wallet-admin/src/home/tabs/WalletTabs.module.scss new file mode 100644 index 000000000..30e3c9b0c --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/WalletTabs.module.scss @@ -0,0 +1,11 @@ +@import '@libs/ui/styles/includes'; + +.container { + form { + @include ltelg { + :global(.input-el) { + margin-bottom: $sp-4; + } + } + } +} diff --git a/src/apps/wallet-admin/src/home/tabs/WalletTabs.tsx b/src/apps/wallet-admin/src/home/tabs/WalletTabs.tsx new file mode 100644 index 000000000..84aec0268 --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/WalletTabs.tsx @@ -0,0 +1,54 @@ +import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react' +import { useLocation } from 'react-router-dom' + +import { UserProfile } from '~/libs/core' +import { PageTitle, TabsNavbar, TabsNavItem } from '~/libs/ui' + +import { getHashFromTabId, getTabIdFromHash, WalletAdminTabsConfig, WalletTabViews } from './config' +import { PaymentsTab } from './payments' +import { WinningsTab } from './winnings' +import { TaxFormsTab } from './tax-forms' +import styles from './WalletTabs.module.scss' + +interface WalletHomeProps { + profile: UserProfile +} + +const WalletTabs: FC = (props: WalletHomeProps) => { + const { hash }: { hash: string } = useLocation() + + const activeTabHash: string = useMemo(() => getTabIdFromHash(hash), [hash]) + + const [activeTab, setActiveTab]: [string, Dispatch>] = useState(activeTabHash) + + useEffect(() => { + setActiveTab(activeTabHash) + }, [activeTabHash]) + + function handleTabChange(tabId: string): void { + setActiveTab(tabId) + window.location.hash = getHashFromTabId(tabId) + } + + return ( +

+ + + + {[ + WalletAdminTabsConfig.find( + (tab: TabsNavItem) => tab.id === activeTab, + )?.title, + 'Wallet Admin', 'Topcoder'].join(' | ')} + + + {activeTab === WalletTabViews.withdrawalmethods && } + + {activeTab === WalletTabViews.winnings && } + + {activeTab === WalletTabViews.taxforms && } +
+ ) +} + +export default WalletTabs diff --git a/src/apps/wallet-admin/src/home/tabs/config/index.ts b/src/apps/wallet-admin/src/home/tabs/config/index.ts new file mode 100644 index 000000000..329aa026f --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/config/index.ts @@ -0,0 +1 @@ +export * from './wallet-tabs-config' diff --git a/src/apps/wallet-admin/src/home/tabs/config/wallet-tabs-config.ts b/src/apps/wallet-admin/src/home/tabs/config/wallet-tabs-config.ts new file mode 100644 index 000000000..ded00a08f --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/config/wallet-tabs-config.ts @@ -0,0 +1,48 @@ +import { TabsNavItem } from '~/libs/ui' + +export enum WalletTabViews { + winnings = '0', + taxforms = '1', + withdrawalmethods = '2', +} + +export const WalletAdminTabsConfig: TabsNavItem[] = [ + { + id: WalletTabViews.winnings, + title: 'Winnings', + }, + { + id: WalletTabViews.withdrawalmethods, + title: 'Withdrawal Methods', + }, + { + id: WalletTabViews.taxforms, + title: 'Tax Forms', + }, +] + +export function getHashFromTabId(tabId: string): string { + switch (tabId) { + case WalletTabViews.winnings: + return '#winnings' + case WalletTabViews.taxforms: + return '#tax-forms' + case WalletTabViews.withdrawalmethods: + return '#withdrawal-methods' + default: + return '#winnings' + } +} + +export function getTabIdFromHash(hash: string): string { + switch (hash) { + case '#winnings': + return WalletTabViews.winnings + case '#tax-forms': + return WalletTabViews.taxforms + case '#withdrawal-methods': + return WalletTabViews.withdrawalmethods + default: + return WalletTabViews.winnings + } +} diff --git a/src/apps/wallet-admin/src/home/tabs/index.ts b/src/apps/wallet-admin/src/home/tabs/index.ts new file mode 100644 index 000000000..40e188685 --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/index.ts @@ -0,0 +1 @@ +export { default as WalletTabs } from './WalletTabs' diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.module.scss b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.module.scss new file mode 100644 index 000000000..a9e9eed24 --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.module.scss @@ -0,0 +1,28 @@ +@import '@libs/ui/styles/includes'; + +.container { + background-color: $black-5; + padding: $sp-6; + margin: $sp-4 0; + border-radius: 6px; + + @include ltelg { + padding: $sp-4; + } + + .header { + display: flex; + justify-content: flex-start; + gap: 5px; + align-items: center; + + @include ltelg { + flex-direction: column; + } + } + + .content { + border-radius: 4px; + margin-top: $sp-4; + } +} diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx new file mode 100644 index 000000000..038cd1107 --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx @@ -0,0 +1,63 @@ +/* eslint-disable react/jsx-no-bind */ +import { toast } from 'react-toastify' +import React, { FC, useCallback, useState } from 'react' + +import { LoadingCircles } from '~/libs/ui' + +import { FilterBar } from '../../../lib' +import { fetchPaymentProviders } from '../../../lib/services/wallet' + +import styles from './PaymentsTab.module.scss' + +const PaymentsTab: FC = () => { + const [isLoading, setIsLoading] = React.useState(false) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [userIdsFilter, setUserIdsFilter] = useState([]) + + const loadTaxForms = useCallback(async () => { + setIsLoading(true) + try { + const paymentMethods = await fetchPaymentProviders(currentPage, pageSize, userIdsFilter) + console.log('paymentMethods', paymentMethods) + } catch (apiError) { + toast.error('Something went wrong fetching user payment methods. Please try again later.') + } + + setIsLoading(false) + }, [userIdsFilter]) + + React.useEffect(() => { + loadTaxForms() + }, [loadTaxForms]) + + return ( +
+
+

Payment Provider

+
+
+ { + console.log(key, value) + }} + onResetFilters={() => { + console.log('reset') + }} + /> +
+ {isLoading && } + {!isLoading && ( +
Table data
+ )} +
+
+ ) +} + +export default PaymentsTab diff --git a/src/apps/wallet-admin/src/home/tabs/payments/index.ts b/src/apps/wallet-admin/src/home/tabs/payments/index.ts new file mode 100644 index 000000000..8dc41bc69 --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/payments/index.ts @@ -0,0 +1 @@ +export { default as PaymentsTab } from './PaymentsTab' diff --git a/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss b/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss new file mode 100644 index 000000000..a9e9eed24 --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss @@ -0,0 +1,28 @@ +@import '@libs/ui/styles/includes'; + +.container { + background-color: $black-5; + padding: $sp-6; + margin: $sp-4 0; + border-radius: 6px; + + @include ltelg { + padding: $sp-4; + } + + .header { + display: flex; + justify-content: flex-start; + gap: 5px; + align-items: center; + + @include ltelg { + flex-direction: column; + } + } + + .content { + border-radius: 4px; + margin-top: $sp-4; + } +} diff --git a/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx new file mode 100644 index 000000000..43b7df517 --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx @@ -0,0 +1,68 @@ +/* eslint-disable react/jsx-no-bind */ +import { toast } from 'react-toastify' +import React, { FC, useCallback, useState } from 'react' + +import { LoadingCircles } from '~/libs/ui' +import { UserProfile } from '~/libs/core' + +import { FilterBar } from '../../../lib' +import { fetchTaxForms } from '../../../lib/services/wallet' + +import styles from './TaxFormsTab.module.scss' + +interface TaxFormsTabProps { + profile: UserProfile +} + +const TaxFormsTab: FC = (_props: TaxFormsTabProps) => { + const [isLoading, setIsLoading] = React.useState(false) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [userIdsFilter, setUserIdsFilter] = useState([]) + + const loadTaxForms = useCallback(async () => { + setIsLoading(true) + try { + const taxForms = await fetchTaxForms(currentPage, pageSize, userIdsFilter) + console.log('TaxForms', taxForms) + } catch (apiError) { + toast.error('Something went wrong fetching tax forms. Please try again later.') + } + + setIsLoading(false) + }, [userIdsFilter]) + + React.useEffect(() => { + loadTaxForms() + }, [loadTaxForms]) + + return ( +
+
+

Tax Forms

+
+
+ { + console.log(key, value) + }} + onResetFilters={() => { + console.log('reset') + }} + /> +
+ {isLoading && } + {!isLoading && ( +
Table data
+ )} +
+
+ ) +} + +export default TaxFormsTab diff --git a/src/apps/wallet-admin/src/home/tabs/tax-forms/index.ts b/src/apps/wallet-admin/src/home/tabs/tax-forms/index.ts new file mode 100644 index 000000000..08eb9c245 --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/tax-forms/index.ts @@ -0,0 +1 @@ +export { default as TaxFormsTab } from './TaxFormsTab' diff --git a/src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss b/src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss new file mode 100644 index 000000000..a9e9eed24 --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss @@ -0,0 +1,28 @@ +@import '@libs/ui/styles/includes'; + +.container { + background-color: $black-5; + padding: $sp-6; + margin: $sp-4 0; + border-radius: 6px; + + @include ltelg { + padding: $sp-4; + } + + .header { + display: flex; + justify-content: flex-start; + gap: 5px; + align-items: center; + + @include ltelg { + flex-direction: column; + } + } + + .content { + border-radius: 4px; + margin-top: $sp-4; + } +} diff --git a/src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx new file mode 100644 index 000000000..9fd13db87 --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx @@ -0,0 +1,150 @@ +/* eslint-disable react/jsx-no-bind */ +import React, { FC, useEffect } from 'react' + +import { LoadingCircles } from '~/libs/ui' +import { UserProfile } from '~/libs/core' + +import { getPayments, processPayments } from '../../../lib/services/wallet' +import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' +import { FilterBar } from '../../../lib' +import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' + +import styles from './Winnings.module.scss' + +interface ListViewProps { + profile: UserProfile +} + +function formatIOSDateString(iosDateString: string): string { + const date = new Date(iosDateString) + + if (Number.isNaN(date.getTime())) { + throw new Error('Invalid date string') + } + + const options: Intl.DateTimeFormatOptions = { + day: 'numeric', + hour: '2-digit', + hour12: true, + minute: '2-digit', + month: 'long', + second: '2-digit', + year: 'numeric', + } + + return date.toLocaleDateString(undefined, options) +} + +const ListView: FC = (props: ListViewProps) => { + const [winnings, setWinnings] = React.useState>([]) + const [isLoading, setIsLoading] = React.useState(false) + + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const fetchWinnings = async () => { + setIsLoading(true) + try { + const payments = await getPayments(`${props.profile.userId}`) + setWinnings(convertToWinnings(payments)) + } catch (apiError) {} + + setIsLoading(false) + } + + useEffect(() => { + fetchWinnings() + }, []) + + function convertToWinnings(payments: WinningDetail[]): Winning[] { + const tempWinnings: Winning[] = [] + + payments.forEach((payment: WinningDetail) => { + const winning: Winning = { + canBeReleased: new Date(payment.releaseDate) <= new Date(), + createDate: formatIOSDateString(payment.createdAt), + currency: payment.details[0].currency, + // eslint-disable-next-line max-len + datePaid: payment.datePaid !== undefined && payment.datePaid.length ? formatIOSDateString(payment.datePaid) : '-', + description: payment.description, + details: payment.details, + id: payment.id, + netPayment: `${new Intl.NumberFormat('en-US', { + currency: payment.details[0].currency, + maximumFractionDigits: 2, + minimumFractionDigits: 2, + style: 'currency', + }) + .format( + Number(payment.details[0].totalAmount), + )}`, + releaseDate: formatIOSDateString(payment.releaseDate), + status: payment.details[0].status, + type: payment.category.replaceAll('_', ' '), + } + tempWinnings.push(winning) + }) + + return tempWinnings + } + + return ( +
+
+

Winnings

+
+
+ { + console.log(key, value) + }} + onResetFilters={() => { + console.log('reset') + }} + /> +
+ {isLoading && } + {!isLoading && ( + { + const ids = Object.keys(paymentIds) + await processPayments(ids) + + fetchWinnings() + }} + /> + )} +
+
+ ) +} + +export default ListView diff --git a/src/apps/wallet-admin/src/home/tabs/winnings/index.ts b/src/apps/wallet-admin/src/home/tabs/winnings/index.ts new file mode 100644 index 000000000..615080cbb --- /dev/null +++ b/src/apps/wallet-admin/src/home/tabs/winnings/index.ts @@ -0,0 +1 @@ +export { default as WinningsTab } from './WinningsTab' diff --git a/src/apps/wallet-admin/src/index.ts b/src/apps/wallet-admin/src/index.ts new file mode 100644 index 000000000..0c0a6b9ba --- /dev/null +++ b/src/apps/wallet-admin/src/index.ts @@ -0,0 +1 @@ +export { walletAdminRoutes, rootRoute as walletRootRoute } from './wallet-admin.routes' diff --git a/src/apps/wallet-admin/src/lib/assets/home/banner-image.svg b/src/apps/wallet-admin/src/lib/assets/home/banner-image.svg new file mode 100644 index 000000000..91a984724 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/home/banner-image.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/home/banner-text.svg b/src/apps/wallet-admin/src/lib/assets/home/banner-text.svg new file mode 100644 index 000000000..3953d78eb --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/home/banner-text.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/home/index.ts b/src/apps/wallet-admin/src/lib/assets/home/index.ts new file mode 100644 index 000000000..5e4f6eac1 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/home/index.ts @@ -0,0 +1,4 @@ +import { ReactComponent as BannerText } from './banner-text.svg' +import { ReactComponent as BannerImage } from './banner-image.svg' + +export { BannerText, BannerImage } diff --git a/src/apps/wallet-admin/src/lib/assets/index.ts b/src/apps/wallet-admin/src/lib/assets/index.ts new file mode 100644 index 000000000..3dc6095bc --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/index.ts @@ -0,0 +1,5 @@ +export * from './security' +export * from './preferences' +export * from './payments' +export * from './tools' +export * from './tcandyou' diff --git a/src/apps/wallet-admin/src/lib/assets/payments/PayPal_logo_gray.svg b/src/apps/wallet-admin/src/lib/assets/payments/PayPal_logo_gray.svg new file mode 100644 index 000000000..8ed6edcec --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/payments/PayPal_logo_gray.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_log_gray.svg b/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_log_gray.svg new file mode 100644 index 000000000..13a13fa00 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_log_gray.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_logo_color.svg b/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_logo_color.svg new file mode 100644 index 000000000..866975133 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_logo_color.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/payments/Paypal_logo_color.svg b/src/apps/wallet-admin/src/lib/assets/payments/Paypal_logo_color.svg new file mode 100644 index 000000000..957a29773 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/payments/Paypal_logo_color.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/payments/Western_Union_Logo_gray.svg b/src/apps/wallet-admin/src/lib/assets/payments/Western_Union_Logo_gray.svg new file mode 100644 index 000000000..8cb8fcaf8 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/payments/Western_Union_Logo_gray.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/payments/icon-dollar.svg b/src/apps/wallet-admin/src/lib/assets/payments/icon-dollar.svg new file mode 100644 index 000000000..ac96c7b15 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/payments/icon-dollar.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/wallet-admin/src/lib/assets/payments/icon-speed.svg b/src/apps/wallet-admin/src/lib/assets/payments/icon-speed.svg new file mode 100644 index 000000000..6d782e8fa --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/payments/icon-speed.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/wallet-admin/src/lib/assets/payments/icon-world.svg b/src/apps/wallet-admin/src/lib/assets/payments/icon-world.svg new file mode 100644 index 000000000..a1d05cdfe --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/payments/icon-world.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/wallet-admin/src/lib/assets/payments/index.ts b/src/apps/wallet-admin/src/lib/assets/payments/index.ts new file mode 100644 index 000000000..cd439a908 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/payments/index.ts @@ -0,0 +1,8 @@ +import { ReactComponent as PayoneerLogo } from './Payoneer_logo_color.svg' +import { ReactComponent as PayPalLogo } from './Paypal_logo_color.svg' +import { ReactComponent as WesternUnionLogo } from './Western_Union_Logo_gray.svg' +import { ReactComponent as IconDollar } from './icon-dollar.svg' +import { ReactComponent as IconWorld } from './icon-world.svg' +import { ReactComponent as IconSpeed } from './icon-speed.svg' + +export { IconDollar, IconWorld, IconSpeed, PayoneerLogo, PayPalLogo, WesternUnionLogo } diff --git a/src/apps/wallet-admin/src/lib/assets/preferences/email.svg b/src/apps/wallet-admin/src/lib/assets/preferences/email.svg new file mode 100755 index 000000000..84a793d3b --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/preferences/email.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/preferences/forum.svg b/src/apps/wallet-admin/src/lib/assets/preferences/forum.svg new file mode 100755 index 000000000..92e12622e --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/preferences/forum.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/preferences/index.ts b/src/apps/wallet-admin/src/lib/assets/preferences/index.ts new file mode 100644 index 000000000..bf54f01b2 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/preferences/index.ts @@ -0,0 +1,7 @@ +import { ReactComponent as ForumIcon } from './forum.svg' +import { ReactComponent as EmailIcon } from './email.svg' + +export { + EmailIcon, + ForumIcon, +} diff --git a/src/apps/wallet-admin/src/lib/assets/security/apple-store.svg b/src/apps/wallet-admin/src/lib/assets/security/apple-store.svg new file mode 100644 index 000000000..e0ab6869e --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/security/apple-store.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/security/credential.png b/src/apps/wallet-admin/src/lib/assets/security/credential.png new file mode 100644 index 0000000000000000000000000000000000000000..914e8d04f84699f81b11476072af3b1788b1750e GIT binary patch literal 47241 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 literal 0 HcmV?d00001 diff --git a/src/apps/wallet-admin/src/lib/assets/security/dicelogo.png b/src/apps/wallet-admin/src/lib/assets/security/dicelogo.png new file mode 100644 index 0000000000000000000000000000000000000000..723b63b2e6750ae6584a86cb94ba3af2661f00da GIT binary patch literal 3067 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

Pt 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 ZmE{2L6bCHqzs&JBa?;A5swGW={|Azy8DRhb literal 0 HcmV?d00001 diff --git a/src/apps/wallet-admin/src/lib/assets/security/dicelogosmall.png b/src/apps/wallet-admin/src/lib/assets/security/dicelogosmall.png new file mode 100644 index 0000000000000000000000000000000000000000..c2be4e4a804a6d5327d68c4ba816e25f5a1c469c GIT binary patch literal 6681 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 literal 0 HcmV?d00001 diff --git a/src/apps/wallet-admin/src/lib/assets/security/google-play.png b/src/apps/wallet-admin/src/lib/assets/security/google-play.png new file mode 100644 index 0000000000000000000000000000000000000000..5311fbf0314dff6dd64b120666457eb7eaadc86e GIT binary patch literal 7000 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 q85kH83aVt~BN>0000 + + + \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/security/unsuccessful.svg b/src/apps/wallet-admin/src/lib/assets/security/unsuccessful.svg new file mode 100644 index 000000000..29ae859bd --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/security/unsuccessful.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-check-circle.svg b/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-check-circle.svg new file mode 100644 index 000000000..d64ba990e --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-check-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-earth.svg b/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-earth.svg new file mode 100644 index 000000000..998adac47 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-earth.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-us.svg b/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-us.svg new file mode 100644 index 000000000..08c6c8a09 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-us.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tax-forms/index.ts b/src/apps/wallet-admin/src/lib/assets/tax-forms/index.ts new file mode 100644 index 000000000..8fe4627a1 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tax-forms/index.ts @@ -0,0 +1,5 @@ +import { ReactComponent as IconWorld } from './ic-earth.svg' +import { ReactComponent as IconUS } from './ic-us.svg' +import { ReactComponent as IconCheckCircle } from './ic-check-circle.svg' + +export { IconUS, IconWorld, IconCheckCircle } diff --git a/src/apps/wallet-admin/src/lib/assets/tcandyou/data_science.svg b/src/apps/wallet-admin/src/lib/assets/tcandyou/data_science.svg new file mode 100644 index 000000000..4a5565e0e --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tcandyou/data_science.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/tcandyou/design.svg b/src/apps/wallet-admin/src/lib/assets/tcandyou/design.svg new file mode 100644 index 000000000..2e344e11b --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tcandyou/design.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/tcandyou/develop.svg b/src/apps/wallet-admin/src/lib/assets/tcandyou/develop.svg new file mode 100644 index 000000000..fe60ce444 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tcandyou/develop.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/tcandyou/ico-ethereum.png b/src/apps/wallet-admin/src/lib/assets/tcandyou/ico-ethereum.png new file mode 100755 index 0000000000000000000000000000000000000000..907388abf20f521e040aaa6251f1b9e3aa2cb8fe GIT binary patch literal 2649 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 LNkvXXu0mjfJk8&7 literal 0 HcmV?d00001 diff --git a/src/apps/wallet-admin/src/lib/assets/tcandyou/index.ts b/src/apps/wallet-admin/src/lib/assets/tcandyou/index.ts new file mode 100644 index 000000000..7e4b90ac6 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tcandyou/index.ts @@ -0,0 +1,15 @@ +import { ReactComponent as DevelopmentTrackIcon } from './develop.svg' +import { ReactComponent as DesignTrackIcon } from './design.svg' +import { ReactComponent as DataScienceTrackIcon } from './data_science.svg' +import ethereumCommunityImage from './ico-ethereum.png' +import ibmCommunityImage from './ico-ibmcloud.png' +import veteransCommunityImage from './ico-veteran.png' + +export { + ethereumCommunityImage, + ibmCommunityImage, + veteransCommunityImage, + DesignTrackIcon, + DataScienceTrackIcon, + DevelopmentTrackIcon, +} diff --git a/src/apps/wallet-admin/src/lib/assets/tools/Financial Institution.svg b/src/apps/wallet-admin/src/lib/assets/tools/Financial Institution.svg new file mode 100644 index 000000000..5736f8c0f --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/Financial Institution.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/Internet Service Provider.svg b/src/apps/wallet-admin/src/lib/assets/tools/Internet Service Provider.svg new file mode 100644 index 000000000..5736f8c0f --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/Internet Service Provider.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/Mobile Carrier.svg b/src/apps/wallet-admin/src/lib/assets/tools/Mobile Carrier.svg new file mode 100644 index 000000000..eb23554ab --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/Mobile Carrier.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/Television.svg b/src/apps/wallet-admin/src/lib/assets/tools/Television.svg new file mode 100644 index 000000000..671a335c5 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/Television.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/console.svg b/src/apps/wallet-admin/src/lib/assets/tools/console.svg new file mode 100644 index 000000000..237abfc97 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/console.svg @@ -0,0 +1 @@ +004-consoleCreated with Sketch. diff --git a/src/apps/wallet-admin/src/lib/assets/tools/desktop.svg b/src/apps/wallet-admin/src/lib/assets/tools/desktop.svg new file mode 100644 index 000000000..1755a7581 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/desktop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/index.ts b/src/apps/wallet-admin/src/lib/assets/tools/index.ts new file mode 100644 index 000000000..176aaa7f5 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/index.ts @@ -0,0 +1,31 @@ +import { ReactComponent as SoftwareIcon } from './software.svg' +import { ReactComponent as DesktopIncon } from './desktop.svg' +import { ReactComponent as SmartphoneIcon } from './smartphone.svg' +import { ReactComponent as OtherDeviceIcon } from './other_device.svg' +import { ReactComponent as LaptopIcon } from './laptop.svg' +import { ReactComponent as ConsoleIcon } from './console.svg' +import { ReactComponent as TabletIcon } from './tablet.svg' +import { ReactComponent as WearableIcon } from './wearable.svg' +import { ReactComponent as FinancialInstitutionIcon } from './Financial Institution.svg' +import { ReactComponent as OtherServiceProviderIcon } from './other_service_provider.svg' +import { ReactComponent as TelevisionServiceProviderIcon } from './Television.svg' +import { ReactComponent as MobileCarrierServiceProviderIcon } from './Mobile Carrier.svg' +import { ReactComponent as InternetServiceProviderIcon } from './Internet Service Provider.svg' +import { ReactComponent as SubscriptionsIcon } from './subscription.svg' + +export { + ConsoleIcon, + DesktopIncon, + FinancialInstitutionIcon, + InternetServiceProviderIcon, + MobileCarrierServiceProviderIcon, + LaptopIcon, + OtherDeviceIcon, + OtherServiceProviderIcon, + SmartphoneIcon, + SoftwareIcon, + SubscriptionsIcon, + TabletIcon, + TelevisionServiceProviderIcon, + WearableIcon, +} diff --git a/src/apps/wallet-admin/src/lib/assets/tools/laptop.svg b/src/apps/wallet-admin/src/lib/assets/tools/laptop.svg new file mode 100644 index 000000000..d54366f39 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/laptop.svg @@ -0,0 +1 @@ +notebookCreated with Sketch. diff --git a/src/apps/wallet-admin/src/lib/assets/tools/other_device.svg b/src/apps/wallet-admin/src/lib/assets/tools/other_device.svg new file mode 100644 index 000000000..1fca19866 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/other_device.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/other_service_provider.svg b/src/apps/wallet-admin/src/lib/assets/tools/other_service_provider.svg new file mode 100644 index 000000000..659b14dae --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/other_service_provider.svg @@ -0,0 +1,17 @@ + + + + + +64px_wifi off + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/smartphone.svg b/src/apps/wallet-admin/src/lib/assets/tools/smartphone.svg new file mode 100644 index 000000000..c8b32fa44 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/smartphone.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/software.svg b/src/apps/wallet-admin/src/lib/assets/tools/software.svg new file mode 100644 index 000000000..266fc1aaf --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/software.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/subscription.svg b/src/apps/wallet-admin/src/lib/assets/tools/subscription.svg new file mode 100644 index 000000000..eafb3cfa9 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/subscription.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/tablet.svg b/src/apps/wallet-admin/src/lib/assets/tools/tablet.svg new file mode 100644 index 000000000..fa1a0d651 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/tablet.svg @@ -0,0 +1 @@ + diff --git a/src/apps/wallet-admin/src/lib/assets/tools/wearable.svg b/src/apps/wallet-admin/src/lib/assets/tools/wearable.svg new file mode 100644 index 000000000..646ae6854 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/assets/tools/wearable.svg @@ -0,0 +1 @@ +005-smart-watchCreated with Sketch. diff --git a/src/apps/wallet-admin/src/lib/components/action-bar-item/ActionBarItem.tsx b/src/apps/wallet-admin/src/lib/components/action-bar-item/ActionBarItem.tsx new file mode 100644 index 000000000..1ea2a947a --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/action-bar-item/ActionBarItem.tsx @@ -0,0 +1,56 @@ +import React, { SVGProps } from 'react' + +import { LinkButton } from '~/libs/ui' + +interface ActionBarItemProps { + containerClassName: string; + action: { + text: string; + className: string; + icon: React.FC>; + isButtonDisabled?: boolean; + }; + info: { + text: string; + icon: React.FC>; + className: string; + }, + onConfirm?: () => void; +} + +const ActionBarItem: React.FC = (props: ActionBarItemProps) => { + const handleActionClick = React.useCallback(() => { + props.onConfirm?.() + }, [props]) + + return ( +

+ + +
+ ) + +} + +export default ActionBarItem diff --git a/src/apps/wallet-admin/src/lib/components/action-bar-item/index.ts b/src/apps/wallet-admin/src/lib/components/action-bar-item/index.ts new file mode 100644 index 000000000..07d166714 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/action-bar-item/index.ts @@ -0,0 +1 @@ +export { default as ActionBarItem } from './ActionBarItem' diff --git a/src/apps/wallet-admin/src/lib/components/chip/Chip.module.scss b/src/apps/wallet-admin/src/lib/components/chip/Chip.module.scss new file mode 100644 index 000000000..7a78c4b52 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/chip/Chip.module.scss @@ -0,0 +1,18 @@ +@import '@libs/ui/styles/includes'; + +.container { + background: #ef476f; + width: 84px; + height: 16px; + padding: 3px 4px 3px 4px; + border-radius: 2px; + gap: 2px; + display: inline-flex; + align-items: center; + justify-content: center; + font-family: $font-roboto; + color: white; + font-size: 11px; + line-height: normal; + text-align: center; +} diff --git a/src/apps/wallet-admin/src/lib/components/chip/Chip.tsx b/src/apps/wallet-admin/src/lib/components/chip/Chip.tsx new file mode 100644 index 000000000..ce17c080a --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/chip/Chip.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +import styles from './Chip.module.scss' + +interface ChipProps { + text: string +} + +const Chip: React.FC = (props: ChipProps) =>
{props.text}
+ +export default Chip diff --git a/src/apps/wallet-admin/src/lib/components/chip/index.ts b/src/apps/wallet-admin/src/lib/components/chip/index.ts new file mode 100644 index 000000000..877198b11 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/chip/index.ts @@ -0,0 +1 @@ +export { default as Chip } from './Chip' diff --git a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.module.scss b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.module.scss new file mode 100644 index 000000000..b690c918a --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.module.scss @@ -0,0 +1,21 @@ +.FilterBar { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + + .filterContainer { + display: flex; + flex-wrap: wrap; + gap: 10px; + + > .input { + margin-bottom: 0px; + } + } + + .resetButton { + flex-shrink: 0; + margin-left: 10px; + } + } diff --git a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx new file mode 100644 index 000000000..c5bf5fc9d --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx @@ -0,0 +1,78 @@ +/* eslint-disable max-len */ +import React, { ChangeEvent } from 'react' + +import { Button, InputSelect, InputText } from '~/libs/ui' + +import styles from './FilterBar.module.scss' + +type FilterOptions = { + label: string + value: string +} + +type Filter = { + key: string + label: string + type: 'input' | 'dropdown' + options?: FilterOptions[] +} + +interface FilterBarProps { + filters: Filter[], + onFilterChange: (key: string, value: string[]) => void + onResetFilters?: () => void +} + +const FilterBar: React.FC = (props: FilterBarProps) => { + const [selectedValue, setSelectedValue] = React.useState>(new Map()) + + const renderDropdown = (index: number, filter: Filter): JSX.Element => ( + ) { + setSelectedValue(new Map(selectedValue.set(filter.key, event.target.value))) + props.onFilterChange(filter.key, [event.target.value]) + }} + name={filter.key} + label={filter.label} + dirty + placeholder={filter.label} + /> + ) + + return ( +
+
+ {props.filters.map((options, index) => ( +
+ {options.type === 'dropdown' && renderDropdown(index, options)} + {options.type === 'input' && ( + ) { + props.onFilterChange(options.key, [event.target.value]) + }} + /> + )} +
+ ))} +
+
+ ) +} + +export default FilterBar diff --git a/src/apps/wallet-admin/src/lib/components/filter-bar/index.ts b/src/apps/wallet-admin/src/lib/components/filter-bar/index.ts new file mode 100644 index 000000000..902a76c74 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/filter-bar/index.ts @@ -0,0 +1 @@ +export { default as FilterBar } from './FilterBar' diff --git a/src/apps/wallet-admin/src/lib/components/index.ts b/src/apps/wallet-admin/src/lib/components/index.ts new file mode 100644 index 000000000..6f213563b --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/index.ts @@ -0,0 +1,4 @@ +export * from './setting-section' +export * from './info-row' +export * from './chip' +export * from './filter-bar' diff --git a/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.module.scss b/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.module.scss new file mode 100644 index 000000000..f44d056e0 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.module.scss @@ -0,0 +1,38 @@ +@import '@libs/ui/styles/includes'; + +.info-row { + display: flex; + align-items: center; + background-color: $tc-white; + padding: $sp-4 $sp-8; + border-radius: 8px; +} + +.title { + font-family: $font-roboto; + font-style: normal; + font-weight: 400; + font-size: 20px; + line-height: 26px; + color: $black-100; +} + +.value-action-container { + display: flex; + justify-content: flex-end; + align-items: center; + flex-grow: 1; + padding-left: 10px; + gap: 200px; +} + +.value { + display: flex; + justify-content: flex-end; +} + +.action { + display: flex; + min-width: 200px; + justify-content: flex-end; +} diff --git a/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.tsx b/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.tsx new file mode 100644 index 000000000..104cf01a5 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.tsx @@ -0,0 +1,21 @@ +import React from 'react' + +import styles from './InfoRow.module.scss' + +interface InfoRowProps { + title: string + value: React.ReactNode + action?: React.ReactNode +} + +const InfoRow: React.FC = (props: InfoRowProps) => ( +
+
{props.title}
+
+
{props.value}
+ {props.action &&
{props.action}
} +
+
+) + +export default InfoRow diff --git a/src/apps/wallet-admin/src/lib/components/info-row/index.ts b/src/apps/wallet-admin/src/lib/components/info-row/index.ts new file mode 100644 index 000000000..3d72e3e11 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/info-row/index.ts @@ -0,0 +1 @@ +export { default as InfoRow } from './InfoRow' diff --git a/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.module.scss b/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.module.scss new file mode 100644 index 000000000..9c5d0a5a2 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.module.scss @@ -0,0 +1,87 @@ +@import '@libs/ui/styles/includes'; + +.otp-modal { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; + padding-bottom: 0; + + .error { + color: red; + } + + .otpInput { + width: 48px !important; + height: 48px; + margin: 5px; + text-align: center; + font-size: 1rem; + border: 2px solid #ccc; + border-radius: 4px; + + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } + + &::-moz-focus-inner { + border: 0; + } + + &[type='number'] { + -moz-appearance: textfield; + } + + &::-ms-clear, + &::-ms-reveal { + display: none; + width: 0; + height: 0; + } + + &[type='number']::placeholder { + color: transparent; + } + + &:focus { + outline: none; + border-color: #007bff; + } + } + + h3 { + font-size: 1.5rem; + text-align: center; + margin-bottom: 20px; + } + + .otpInputStyle { + width: 40px; + height: 50px; + margin: 0 5px; + text-align: center; + font-size: 2rem; + border: 1px solid #ccc; + border-radius: 4px; + + &:focus { + outline: none; + border-color: blue; + } + } + + .otpInputFocusStyle { + border-color: #007bff; + } + + p { + font-size: 0.9rem; + color: #666; + text-align: center; + margin-top: $sp-1; + margin-bottom: $sp-4; + } +} diff --git a/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.tsx b/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.tsx new file mode 100644 index 000000000..d91a0e976 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.tsx @@ -0,0 +1,140 @@ +import { toast } from 'react-toastify' +import OTPInput, { InputProps } from 'react-otp-input' +import React, { FC } from 'react' + +import { BaseModal, LinkButton, LoadingCircles } from '~/libs/ui' +import { resendOtp, verifyOtp } from '~/apps/wallet/src/lib/services/wallet' + +import { OtpVerificationResponse } from '../../models/OtpVerificationResponse' + +import styles from './OtpModal.module.scss' + +const RESEND_OTP_TIMEOUT = 60000 + +interface OtpModalProps { + isOpen: boolean + key: string + transactionId: string + userEmail?: string + isBlob?: boolean + onClose: () => void + onOtpVerified: (data: unknown) => void +} + +const OtpModal: FC = (props: OtpModalProps) => { + const [otp, setOtp] = React.useState('') + const [loading, setLoading] = React.useState(false) + const [error, setError] = React.useState('') + const [showResendButton, setShowResendButton] = React.useState(false) + + // eslint-disable-next-line consistent-return + React.useEffect(() => { + let timer: NodeJS.Timeout | undefined + if (props.isOpen) { + setShowResendButton(false) + timer = setTimeout(() => { + setShowResendButton(true) + }, RESEND_OTP_TIMEOUT) + } + + return () => { + if (timer) { + clearTimeout(timer) + } + } + }, [props.isOpen]) + + React.useEffect(() => { + if (!props.isOpen) { + setOtp('') + setError('') + } + }, [props.isOpen]) + + function handleChange(code: string): void { + setOtp(code) + if (code.length === 6) { + setLoading(true) + verifyOtp(props.transactionId, code, props.isBlob) + .then((response: OtpVerificationResponse | Blob) => { + setLoading(false) + props.onOtpVerified(response) + }) + .catch((err: Error) => { + setLoading(false) + setError(err.message) + }) + } else if (code.length < 6) { + setError('') + } + } + + return ( + +
+ {error &&

{error}

} +

+ For added security we’ve sent a 6-digit code to your + {' '} + {props.userEmail ?? '***@gmail.com'} + {' '} + email. The code + expires shortly, so please enter it soon. +

+ } + onChange={handleChange} + inputType='number' + shouldAutoFocus + inputStyle={styles.otpInput} + /> + +

Can't find the code? Check your spam folder.

+ {loading && } + {!loading && showResendButton && ( + { + setShowResendButton(true) + }, RESEND_OTP_TIMEOUT) + toast.success( + 'OTP sent successfully.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) + } catch (err: unknown) { + toast.error( + (err as Error).message ?? 'Something went wrong. Please try again.', + { position: toast.POSITION.BOTTOM_RIGHT }, + ) + } + }} + /> + )} +
+
+ ) +} + +export default OtpModal diff --git a/src/apps/wallet-admin/src/lib/components/otp-modal/index.ts b/src/apps/wallet-admin/src/lib/components/otp-modal/index.ts new file mode 100644 index 000000000..9d973de1e --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/otp-modal/index.ts @@ -0,0 +1 @@ +export { default as OtpModal } from './OtpModal' diff --git a/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss b/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss new file mode 100644 index 000000000..3a56c65d7 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss @@ -0,0 +1,90 @@ +@import '@libs/ui/styles/includes'; + +.card { + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + border-radius: 8px; + background-color: white; + border: 1px solid #D4D4D4; + + .content { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 16px; + + .header { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + .detailContainer { + display: grid; + width: 100%; + + &.singleRow { + grid-template-columns: repeat(3, 1fr); + margin-top: 24px; + gap: $sp-6; + } + + &.stackedRows { + grid-template-columns: 1fr; + .detail:not(:last-child) { + margin-bottom: 8px; + } + } + + .detail { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + + .iconLabelContainer { + display: flex; + } + + .label { + margin-left: 8px; + color: $teal-140; + font-family: $font-roboto-mono; + font-size: 12px; + font-weight: 700; + line-height: 16px; + letter-spacing: 1px; + text-align: left; + } + + .value { + color: #2a2a2a; + font-size: 14px; + } + } + } + + .footer { + margin-top: 24px; + } + } + + .actionItems { + padding-top: 16px; + margin-right: 24px; + + .actionItemsContainer { + display: flex; + flex-direction: column; + align-items: flex-end; + + .warningLabel { + color: $legacy-120; + } + } + } +} diff --git a/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.tsx b/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.tsx new file mode 100644 index 000000000..0a4fad84c --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.tsx @@ -0,0 +1,172 @@ +import { useMemo, useState } from 'react' + +import { CheckCircleIcon } from '@heroicons/react/solid' +import { Button, ConfirmModal, IconOutline, IconSolid, PageDivider } from '~/libs/ui' + +import { ActionBarItem } from '../action-bar-item' +import { PaymentProvider } from '../../models/PaymentProvider' +import { ConfirmFlowData } from '../../models/ConfirmFlowData' + +import styles from './PaymentProviderCard.module.scss' + +interface Detail { + icon: React.ReactNode + label: string + value: string +} + +interface PaymentProviderProps { + provider: PaymentProvider + logo: React.ReactNode + details: Detail[] + onConnectClick?: () => void + onResendOtpClick?: () => void + onGoToRegistrationClick?: () => void + onRemoveProvider?: () => void +} + +const PaymentProviderCard: React.FC = (props: PaymentProviderProps) => { + const [confirmFlow, setConfirmFlow] = useState(undefined) + + const renderConfirmModalContent = useMemo(() => { + if (confirmFlow?.content === undefined) { + return undefined + } + + if (typeof confirmFlow?.content === 'function') { + return confirmFlow?.content() + } + + return confirmFlow?.content + }, [confirmFlow]) + + const canConnect = props.provider.status === 'NOT_CONNECTED' + const canCancel = ['OTP_PENDING', 'OTP_VERIFIED', 'VERIFIED', 'CONNECTED'].includes(props.provider.status) + + const renderOtpPending = (): JSX.Element => ( + + ) + + const renderOtpVerified = (): JSX.Element => ( + + ) + + return ( + <> +
+
+
+
{props.logo}
+
+ + {canConnect && } + +
+ {props.details.map((detail: Detail) => ( +
+
+ {detail.icon} + {detail.label} +
+

{detail.value}

+
+ ))} +
+ + {canConnect && ( +
+
+ )} + {canCancel && ( +
+
+ )} +
+
+ {props.provider.status === 'CONNECTED' + &&
+
+ {confirmFlow && ( + +
{renderConfirmModalContent}
+
+ )} + + ) +} + +export default PaymentProviderCard diff --git a/src/apps/wallet-admin/src/lib/components/payment-provider-card/index.ts b/src/apps/wallet-admin/src/lib/components/payment-provider-card/index.ts new file mode 100644 index 000000000..a3a1c4984 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payment-provider-card/index.ts @@ -0,0 +1 @@ +export { default as PaymentProviderCard } from './PaymentProviderCard' diff --git a/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss b/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss new file mode 100644 index 000000000..8757b2164 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss @@ -0,0 +1,77 @@ +@import '@libs/ui/styles/includes'; + +.tableContainer { + width: 100%; + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + margin: 16px 0; + + th, + td { + text-align: left; + padding: 8px; + border-bottom: 1px solid #eaeaea; + + &:last-child { + width: 50px; + } + } + + th { + background-color: #f5f5f5; + position: sticky; + top: 0; + } + + tbody { + tr { + &:nth-child(odd) { + background-color: #f9f9f9; + } + &:nth-child(even) { + background-color: #ffffff; + } + } + } +} + +.selected { + background-color: #e7f4ff; +} + +.paymentFooter { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + margin-top: 16px; + border-radius: 8px; + background-color: white; + border: 1px solid #eaeaea; + + .total { + font-size: 18px; + font-weight: bold; + + color: #333; + } +} + +@media (max-width: 768px) { + .paymentFooter { + flex-direction: column; + align-items: stretch; + + .total { + margin-bottom: 10px; + } + + .payMeButton { + width: 100%; + } + } +} diff --git a/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx b/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx new file mode 100644 index 000000000..240fcfcfd --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx @@ -0,0 +1,119 @@ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import React, { useState } from 'react' + +import { Button } from '~/libs/ui' + +import { Winning } from '../../models/WinningDetail' + +import styles from './PaymentTable.module.scss' + +interface PaymentTableProps { + payments: ReadonlyArray + onPayMeClick: (paymentIds: { [paymentId: string]: boolean }) => void +} +const PaymentsTable: React.FC = (props: PaymentTableProps) => { + const [selectedPayments, setSelectedPayments] = useState<{ [paymentId: string]: boolean }>({}) + + const togglePaymentSelection = (paymentId: string) => { + setSelectedPayments(prevSelected => ({ + ...prevSelected, + [paymentId]: !prevSelected[paymentId], + })) + } + + const isAllSelected = props.payments.length > 0 && props.payments.every(payment => selectedPayments[payment.id]) + + const toggleAllPayments = () => { + if (isAllSelected) { + setSelectedPayments({}) + } else { + const newSelections: { [paymentId: string]: boolean } = {} + props.payments.forEach(payment => { + newSelections[payment.id] = true + }) + setSelectedPayments(newSelections) + } + } + + const calculateTotal = () => props.payments.reduce((acc: number, payment: Winning) => { + if (selectedPayments[payment.id]) { + return acc + parseFloat(payment.netPayment.replace(/[^0-9.-]+/g, '')) + } + + return acc + }, 0) + + const total = calculateTotal() + + return ( + <> +
+ + + + + + + + + + + + + + + {props.payments.map(payment => ( + + + + + + + + + + + ))} + +
DescriptionTypeCreate DateNet PaymentStatusRelease DateDate Paid + payment.status !== 'OWED') === undefined} + checked={isAllSelected} + aria-label='Select all payments' + /> +
{payment.description}{payment.type}{payment.createDate}{payment.netPayment}{payment.status}{payment.releaseDate}{payment.datePaid} + togglePaymentSelection(payment.id)} + aria-label={`Select payment ${payment.id}`} + /> +
+
+
+
+ Total: $ + {total.toFixed(2)} +
+ +
+ + ) +} + +export default PaymentsTable diff --git a/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.module.scss b/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.module.scss new file mode 100644 index 000000000..dd4087c74 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.module.scss @@ -0,0 +1,26 @@ +@import '@libs/ui/styles/includes'; + +.container { + display: flex; + padding: $sp-4; + border: 1px solid $black-20; + border-radius: 8px; + margin-top: $sp-4; + flex-wrap: wrap; + + .contentMiddle { + display: flex; + flex-direction: column; + flex: 1; + align-self: center; + + @include ltelg { + margin-right: $sp-4; + } + + .infoText { + color: #767676; + max-width: 85%; + } + } +} \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.tsx b/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.tsx new file mode 100644 index 000000000..380286fdf --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react' +import classNames from 'classnames' + +import styles from './SettingSection.module.scss' + +interface SettingSectionProps { + containerClassName?: string + readonly title: string + readonly infoText?: string + actionElement?: React.ReactNode + leftElement?: React.ReactNode +} + +const SettingSection: FC = (props: SettingSectionProps) => ( +
+ {props.leftElement} + +
+

{props.title}

+

+

+ + {props.actionElement} +
+) + +export default SettingSection diff --git a/src/apps/wallet-admin/src/lib/components/setting-section/index.ts b/src/apps/wallet-admin/src/lib/components/setting-section/index.ts new file mode 100644 index 000000000..a2a2fec21 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/setting-section/index.ts @@ -0,0 +1 @@ +export { default as SettingSection } from './SettingSection' diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.module.scss b/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.module.scss new file mode 100644 index 000000000..46c3eb47d --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.module.scss @@ -0,0 +1,46 @@ +@import '@libs/ui/styles/includes'; + +.card { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + background-color: white; + padding: 16px; + border-radius: 8px; + border: 1px solid #eaeaea; + margin: 16px 0; + + .header { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 24px; + + .icon { + max-width: 48px; + max-height: 48px; + } + } + + .content { + display: flex; + flex-direction: column; + gap: 16px; + flex: 1; + + .additionalInfoPurpose { + ul { + list-style-type: disc; + padding: 10px 24px; + } + } + } + + .footer { + display: flex; + width: 100%; + justify-content: space-between; + margin-top: 24px; + } +} diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.tsx b/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.tsx new file mode 100644 index 000000000..855021793 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.tsx @@ -0,0 +1,83 @@ +import React from 'react' + +import { Button, PageDivider } from '~/libs/ui' + +import styles from './TaxFormCard.module.scss' + +interface TaxFormCardProps { + formTitle: string + formDescription: string + reasonTitle: string + reasonDescription: string + instructionsLink: string + instructionsLabel: string + completionLabel: string + additionalInfo?: { + link?: { + text: string + href: string + } + purpose?: { + title: string + points: string[] + } + note?: string + } + icon: React.ReactNode + onSetupClick: () => void +} + +const TaxFormCard: React.FC = (props: TaxFormCardProps) => ( +
+) + +export default TaxFormCard diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-card/index.ts b/src/apps/wallet-admin/src/lib/components/tax-form-card/index.ts new file mode 100644 index 000000000..4b7cccbab --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/tax-form-card/index.ts @@ -0,0 +1 @@ +export { default as TaxFormCard } from './TaxFormCard' diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.module.scss b/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.module.scss new file mode 100644 index 000000000..bf64b99b9 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.module.scss @@ -0,0 +1,92 @@ +@import '@libs/ui/styles/includes'; + +.card { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + background-color: white; + padding: 16px; + border-radius: 8px; + gap: $sp-4; + border: 1px solid #eaeaea; + margin: 16px 0; + + .iconContainer { + width: 64px; + height: 64px; + display: flex; + justify-content: center; + align-items: center; + + background: linear-gradient(264.69deg, #198807 2.17%, #017c6d 97.49%); + border-radius: 4px; + + .icon { + width: 26.67px; + height: 26.67px; + top: 2.67px; + left: 2.67px; + fill: white; + } + } + + .content { + display: flex; + flex-direction: column; + flex: 1; + } + + .actionItems { + display: flex; + align-items: center; + width: 64px; + justify-content: space-between; + + .actionButton { + width: 32px; + height: 32px; + border: none; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + } + + .downloadIcon { + width: 24px; + height: 24px; + fill: $turq-160; + } + + .deleteIcon { + width: 24px; + height: 24px; + fill: $red-140; + } + } + + .actionItemsStacked { + display: flex; + flex-direction: column; + align-items: flex-end; + + .warningLabel { + color: $legacy-120; + } + } + + .actionButton, + .loadingWrapper { + transition: opacity 0.3s ease, transform 0.3s ease; + opacity: 1; + transform: scale(1); + } + + .hidden { + opacity: 0; + display: none; + transform: scale(0.95); + pointer-events: none; + } +} diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.tsx b/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.tsx new file mode 100644 index 000000000..74d0673dd --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.tsx @@ -0,0 +1,140 @@ +import React, { useMemo } from 'react' + +import { DownloadIcon, ExclamationCircleIcon, TrashIcon } from '@heroicons/react/solid' +import { ConfirmModal, IconSolid } from '~/libs/ui' + +import { IconCheckCircle } from '../../assets/tax-forms' +import { ActionBarItem } from '../action-bar-item' +import { ConfirmFlowData } from '../../models/ConfirmFlowData' + +import styles from './TaxFormDetail.module.scss' + +interface TaxFormDetailProps { + title: string + description: string + status: string + onGetRecipientURL?: () => void + onResendOtpClick?: () => void + onDownloadClick?: () => void + onDeleteClick?: () => void +} + +const TaxFormDetail: React.FC = (props: TaxFormDetailProps) => { + const [confirmFlow, setConfirmFlow] = React.useState(undefined) + + const renderConfirmModalContent = useMemo(() => { + if (confirmFlow?.content === undefined) { + return undefined + } + + if (typeof confirmFlow?.content === 'function') { + return confirmFlow?.content() + } + + return confirmFlow?.content + }, [confirmFlow]) + + const renderOtpPending = (): JSX.Element => ( + + ) + + const renderOtpVerified = (): JSX.Element => ( + + ) + + const renderActive = (): JSX.Element => ( +
+ + +
+ ) + + return ( + <> +
+
+ {props.status === 'ACTIVE' && } + {props.status !== 'ACTIVE' && } +
+ +
+
{props.title}
+
{props.description}
+
+ + {props.status === 'OTP_PENDING' && renderOtpPending()} + {props.status === 'OTP_VERIFIED' && renderOtpVerified()} + {props.status === 'ACTIVE' && renderActive()} +
+ {confirmFlow && ( + +
{renderConfirmModalContent}
+
+ )} + + ) +} + +export default TaxFormDetail diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-detail/index.ts b/src/apps/wallet-admin/src/lib/components/tax-form-detail/index.ts new file mode 100644 index 000000000..bfe5dc606 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/tax-form-detail/index.ts @@ -0,0 +1 @@ +export { default as TaxFormDetail } from './TaxFormDetail' diff --git a/src/apps/wallet-admin/src/lib/index.ts b/src/apps/wallet-admin/src/lib/index.ts new file mode 100644 index 000000000..0f5e459b8 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/index.ts @@ -0,0 +1,3 @@ +export * from './wallet-swr' +export * from './assets' +export * from './components' diff --git a/src/apps/wallet-admin/src/lib/models/ApiError.ts b/src/apps/wallet-admin/src/lib/models/ApiError.ts new file mode 100644 index 000000000..9b0135c9e --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/ApiError.ts @@ -0,0 +1,4 @@ +export interface ApiError { + code: string + message: string +} diff --git a/src/apps/wallet-admin/src/lib/models/ApiResponse.ts b/src/apps/wallet-admin/src/lib/models/ApiResponse.ts new file mode 100644 index 000000000..3221e7793 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/ApiResponse.ts @@ -0,0 +1,4 @@ +export default interface ApiResponse { + status: 'success' | 'error' + data: T +} diff --git a/src/apps/wallet-admin/src/lib/models/ConfirmFlowData.ts b/src/apps/wallet-admin/src/lib/models/ConfirmFlowData.ts new file mode 100644 index 000000000..29c7753e5 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/ConfirmFlowData.ts @@ -0,0 +1,6 @@ +export interface ConfirmFlowData { + title: string; + action: string; + content: React.ReactNode | (() => React.ReactNode) + callback?: () => void; +} diff --git a/src/apps/wallet-admin/src/lib/models/OtpVerificationResponse.ts b/src/apps/wallet-admin/src/lib/models/OtpVerificationResponse.ts new file mode 100644 index 000000000..53456138d --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/OtpVerificationResponse.ts @@ -0,0 +1,3 @@ +export interface OtpVerificationResponse { + data: unknown; + } diff --git a/src/apps/wallet-admin/src/lib/models/PaymentProvider.ts b/src/apps/wallet-admin/src/lib/models/PaymentProvider.ts new file mode 100644 index 000000000..c78e84687 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/PaymentProvider.ts @@ -0,0 +1,9 @@ +export interface PaymentProvider { + id?: number + upmId?: string + type: 'Payoneer' | 'Paypal' + name: 'Payoneer' | 'Paypal' + description: string + status: string + transactionId?: string +} diff --git a/src/apps/wallet-admin/src/lib/models/ResendOtpResponse.ts b/src/apps/wallet-admin/src/lib/models/ResendOtpResponse.ts new file mode 100644 index 000000000..1fd04f75a --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/ResendOtpResponse.ts @@ -0,0 +1,3 @@ +export interface ResendOtpResponse { + transactionId: string; + } diff --git a/src/apps/wallet-admin/src/lib/models/TaxForm.ts b/src/apps/wallet-admin/src/lib/models/TaxForm.ts new file mode 100644 index 000000000..6578be894 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/TaxForm.ts @@ -0,0 +1,18 @@ +export interface TaxForm { + id: string + userId: string + dateFiled: string + withholdingAmount: string + withholdingPercentage: string + taxForm: { + name: string + text: string + description: string + } + status: string + transactionId: string +} + +export interface SetupTaxFormResponse { + transactionId: string +} diff --git a/src/apps/wallet-admin/src/lib/models/TransactionId.ts b/src/apps/wallet-admin/src/lib/models/TransactionId.ts new file mode 100644 index 000000000..a0e860446 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/TransactionId.ts @@ -0,0 +1,5 @@ +export interface TransactionResponse { + transactionId: string + type?: string; + email: string +} diff --git a/src/apps/wallet-admin/src/lib/models/WalletDetails.ts b/src/apps/wallet-admin/src/lib/models/WalletDetails.ts new file mode 100644 index 000000000..b7baf2aeb --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/WalletDetails.ts @@ -0,0 +1,19 @@ +export interface Balance { + amount: number + type: string + unit: string +} + +export interface AccountDetails { + balances: Balance[] +} + +export interface WalletDetails { + account: AccountDetails + withdrawalMethod: { + isSetupComplete: boolean + } + taxForm: { + isSetupComplete: boolean + } +} diff --git a/src/apps/wallet-admin/src/lib/models/WinningDetail.ts b/src/apps/wallet-admin/src/lib/models/WinningDetail.ts new file mode 100644 index 000000000..9e07afbe7 --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/WinningDetail.ts @@ -0,0 +1,41 @@ +export interface PaymentDetail { + id: string + netAmount: string + grossAmount: string + totalAmount: string + installmentNumber: number + status: string + currency: string +} + +export interface Winning { + id: string + description: string + type: string + createDate: string + netPayment: string + status: string + releaseDate: string + datePaid: string + canBeReleased: boolean + currency: string + details: PaymentDetail[] +} + +export interface WinningDetail { + id: string + type: string + winnerId: string + origin: string + category: string + title: string + description: string + externalId: string + attributes: { + url: string + } + details: PaymentDetail[] + createdAt: string + releaseDate: string + datePaid: string +} diff --git a/src/apps/wallet-admin/src/lib/services/wallet.ts b/src/apps/wallet-admin/src/lib/services/wallet.ts new file mode 100644 index 000000000..d6318480d --- /dev/null +++ b/src/apps/wallet-admin/src/lib/services/wallet.ts @@ -0,0 +1,259 @@ +import { AxiosError } from 'axios' + +import { EnvironmentConfig } from '~/config' +import { xhrDeleteAsync, xhrGetAsync, xhrPostAsync, xhrPostAsyncWithBlobHandling } from '~/libs/core' + +import { WalletDetails } from '../models/WalletDetails' +import { PaymentProvider } from '../models/PaymentProvider' +import { WinningDetail } from '../models/WinningDetail' +import { TaxForm } from '../models/TaxForm' +import { OtpVerificationResponse } from '../models/OtpVerificationResponse' +import { TransactionResponse } from '../models/TransactionId' +import ApiResponse from '../models/ApiResponse' + +const baseUrl = `${EnvironmentConfig.API.V5}/payments` + +export async function getWalletDetails(): Promise { + const response = await xhrGetAsync>(`${baseUrl}/wallet`) + + if (response.status === 'error') { + throw new Error('Error fetching wallet details') + } + + return response.data +} + +export async function getUserPaymentProviders(): Promise { + const response = await xhrGetAsync>(`${baseUrl}/user/payment-methods`) + + if (response.status === 'error') { + throw new Error('Error fetching user payment providers') + } + + return response.data +} + +export async function getUserTaxFormDetails(): Promise { + const response = await xhrGetAsync>(`${baseUrl}/user/tax-forms`) + if (response.status === 'error') { + throw new Error('Error fetching user tax form details') + } + + return response.data +} + +export async function getPayments(userId: string): Promise { + const body = JSON.stringify({ + winnerId: userId, + }) + + const url = `${baseUrl}/user/winnings` + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Error fetching payments') + } + + return response.data +} + +export async function setPaymentProvider( + type: string, +): Promise { + const body = JSON.stringify({ + details: {}, + setDefault: true, + type, + }) + + const url = `${baseUrl}/user/payment-method` + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Error setting payment provider') + } + + return response.data +} + +export async function confirmPaymentProvider(provider: string, code: string, transactionId: string): Promise { + const body = JSON.stringify({ + code, + provider, + transactionId, + }) + + const url = `${baseUrl}/payment-provider/paypal/confirm` + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Error confirming payment provider') + } + + return response.data +} + +export async function getPaymentProviderRegistrationLink(type: string): Promise { + const url = `${baseUrl}/user/payment-method/${type}/registration-link` + const response = await xhrGetAsync>(url) + + if (response.status === 'error') { + throw new Error('Error getting payment provider registration link') + } + + return response.data +} + +export async function removePaymentProvider(type: string): Promise { + const url = `${baseUrl}/user/payment-method/${type}` + const response = await xhrDeleteAsync>(url) + + if (response.status === 'error') { + throw new Error('Error getting payment provider registration link') + } + + return response.data +} + +export async function setupTaxForm(userId: string, taxForm: string): Promise { + const body = JSON.stringify({ + taxForm, + userId, + }) + + const url = `${baseUrl}/user/tax-form` + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Error setting tax form') + } + + return response.data +} + +export async function removeTaxForm(taxFormId: string): Promise { + const url = `${baseUrl}/user/tax-forms/${taxFormId}` + const response = await xhrDeleteAsync>(url) + + if (response.status === 'error') { + throw new Error('Error removing tax form') + } + + return response.data +} + +export async function getRecipientViewURL(): Promise { + const url = `${baseUrl}/user/tax-form/view` + const response = await xhrGetAsync>(url) + + if (response.status === 'error') { + throw new Error('Error removing tax form') + } + + return response.data +} + +export async function processPayments(paymentIds: string[]): Promise<{ processed: boolean }> { + const body = JSON.stringify({ + paymentIds, + }) + const url = `${baseUrl}/withdraw` + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Error processing payments') + } + + return response.data +} + +// eslint-disable-next-line max-len +export async function verifyOtp(transactionId: string, code: string, blob: boolean = false): Promise { + const body = JSON.stringify({ + otpCode: code, + transactionId, + }) + + const url = `${baseUrl}/otp/verify` + try { + // eslint-disable-next-line max-len + const response = await xhrPostAsyncWithBlobHandling | Blob>(url, body, { + responseType: blob ? 'blob' : 'json', + }) + + if (response instanceof Blob) { + return response as Blob + } + + if (response.status === 'error') { + throw new Error('OTP verification failed or OTP has expired') + } + + return response.data + } catch (err) { + throw new Error('OTP verification failed or OTP has expired') + } +} + +export async function resendOtp(transactionId: string): Promise { + const body = JSON.stringify({ + transactionId, + }) + + const url = `${baseUrl}/otp/resend` + try { + const response = await xhrPostAsync>(url, body) + + if (response.status === 'error') { + throw new Error('Failed to resend OTP.') + } + + return response.data + } catch (err) { + if (err instanceof AxiosError && err.response?.data?.error !== undefined) { + throw new Error(err.response.data.error?.message) + } + + throw new Error('Failed to resend OTP.') + } +} + +export async function fetchTaxForms(pageNumber: number, pageSize: number, userIds: string[]): Promise { + const body = JSON.stringify({ + pageNumber, + pageSize, + userIds, + }) + + try { + const response = await xhrPostAsync>(`${baseUrl}/admin/tax-forms`, body) + + if (response.status === 'error') { + throw new Error('Error fetching tax forms') + } + + return response.data + } catch (err) { + throw new Error('Error fetching tax forms') + } +} + +export async function fetchPaymentProviders(pageNumber: number, pageSize: number, userIds: string[]): Promise { + const body = JSON.stringify({ + pageNumber, + pageSize, + userIds, + }) + + try { + const response = await xhrPostAsync>(`${baseUrl}/admin/payment-methods`, body) + + if (response.status === 'error') { + throw new Error('Error fetching tax forms') + } + + return response.data + } catch (err) { + throw new Error('Error fetching tax forms') + } +} diff --git a/src/apps/wallet-admin/src/lib/wallet-swr/WalletSwr.tsx b/src/apps/wallet-admin/src/lib/wallet-swr/WalletSwr.tsx new file mode 100644 index 000000000..ae683103f --- /dev/null +++ b/src/apps/wallet-admin/src/lib/wallet-swr/WalletSwr.tsx @@ -0,0 +1,23 @@ +import { FC, ReactNode } from 'react' +import { SWRConfig } from 'swr' + +import { xhrGetAsync } from '~/libs/core' + +interface WalletSwrProps { + children: ReactNode +} + +const WalletSwr: FC = (props: WalletSwrProps) => ( + xhrGetAsync(resource), + refreshInterval: 0, + revalidateOnFocus: false, + revalidateOnMount: true, + }} + > + {props.children} + +) + +export default WalletSwr diff --git a/src/apps/wallet-admin/src/lib/wallet-swr/index.ts b/src/apps/wallet-admin/src/lib/wallet-swr/index.ts new file mode 100644 index 000000000..31c20cbba --- /dev/null +++ b/src/apps/wallet-admin/src/lib/wallet-swr/index.ts @@ -0,0 +1 @@ +export { default as WalletSwr } from './WalletSwr' diff --git a/src/apps/wallet-admin/src/wallet-admin.routes.tsx b/src/apps/wallet-admin/src/wallet-admin.routes.tsx new file mode 100644 index 000000000..1214379e3 --- /dev/null +++ b/src/apps/wallet-admin/src/wallet-admin.routes.tsx @@ -0,0 +1,34 @@ +import { lazyLoad, LazyLoadedComponent, PlatformRoute } from '~/libs/core' +import { AppSubdomain, EnvironmentConfig, ToolTitle } from '~/config' + +const WalletApp: LazyLoadedComponent = lazyLoad(() => import('./WalletAdminApp')) +const WalletHomePage: LazyLoadedComponent = lazyLoad( + () => import('./home'), + 'WalletHomePage', +) + +// prettier-ignore +// eslint-disable-next-line max-len +export const rootRoute: string = EnvironmentConfig.SUBDOMAIN === AppSubdomain.walletAdmin ? '' : `/${AppSubdomain.walletAdmin}` + +export const toolTitle = ToolTitle.walletAdmin +export const absoluteRootRoute: string = `${window.location.origin}/${rootRoute}` + +export const walletAdminRoutes: ReadonlyArray = [ + { + authRequired: true, + children: [ + { + children: [], + element: , + id: 'Dashboard', + route: '', + }, + ], + domain: AppSubdomain.walletAdmin, + element: , + id: toolTitle, + rolesRequired: ['Payment Admin'], + route: rootRoute, + }, +] diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index 6fbc1ebf4..a55ec9cf0 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -6,6 +6,7 @@ import { UserProfile } from '~/libs/core' import { getPayments, processPayments } from '../../../lib/services/wallet' import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' +import { FilterBar } from '../../../lib' import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' import styles from './Winnings.module.scss' @@ -51,7 +52,7 @@ const ListView: FC = (props: ListViewProps) => { useEffect(() => { fetchWinnings() - }, [props.profile.userId]) + }, []) function convertToWinnings(payments: WinningDetail[]): Winning[] { const tempWinnings: Winning[] = [] @@ -90,6 +91,14 @@ const ListView: FC = (props: ListViewProps) => {

Winnings

+ {/* */}
{isLoading && } {!isLoading && ( diff --git a/src/apps/wallet/src/lib/components/filter-bar/FilterBar.module.scss b/src/apps/wallet/src/lib/components/filter-bar/FilterBar.module.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx b/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx new file mode 100644 index 000000000..490da5322 --- /dev/null +++ b/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react' + +type DropdownOption = { + label: string + value: string +} + +interface FilterBarProps { + filters: DropdownOption[][] } + +const FilterBar: React.FC = (props: FilterBarProps) => { + const [selectedValues, setSelectedValues] = useState(new Array(props.filters.length) + .fill('')) + + const handleDropdownChange = (value: string, index: number): void => { + const updatedValues = [...selectedValues] + updatedValues[index] = value + setSelectedValues(updatedValues) + } + + const handleClearFilters = (): void => { + setSelectedValues(new Array(props.filters.length) + .fill('')) + } + + return ( +
+ {props.filters.map((options, index) => ( + + ))} + +
+ ) +} + +export default FilterBar diff --git a/src/apps/wallet/src/lib/components/filter-bar/index.ts b/src/apps/wallet/src/lib/components/filter-bar/index.ts new file mode 100644 index 000000000..902a76c74 --- /dev/null +++ b/src/apps/wallet/src/lib/components/filter-bar/index.ts @@ -0,0 +1 @@ +export { default as FilterBar } from './FilterBar' diff --git a/src/apps/wallet/src/lib/components/index.ts b/src/apps/wallet/src/lib/components/index.ts index 24a2b7711..6f213563b 100644 --- a/src/apps/wallet/src/lib/components/index.ts +++ b/src/apps/wallet/src/lib/components/index.ts @@ -1,3 +1,4 @@ export * from './setting-section' export * from './info-row' export * from './chip' +export * from './filter-bar' diff --git a/src/config/constants.ts b/src/config/constants.ts index 2c7640e03..e2469a4dd 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -9,6 +9,7 @@ export enum AppSubdomain { work = 'work', talentSearch = 'talent-search', wallet = 'wallet', + walletAdmin = 'wallet-admin', } export enum ToolTitle { @@ -22,6 +23,7 @@ export enum ToolTitle { onboarding = ' ', talentSearch = 'Expert Talent', wallet = 'Wallet', + walletAdmin = 'Wallet Admin', } export const PageSubheaderPortalId: string = 'page-subheader-portal-el' diff --git a/src/libs/core/lib/profile/profile-functions/profile-factory/user-role.enum.ts b/src/libs/core/lib/profile/profile-functions/profile-factory/user-role.enum.ts index f4ac33a36..7531539ce 100644 --- a/src/libs/core/lib/profile/profile-functions/profile-factory/user-role.enum.ts +++ b/src/libs/core/lib/profile/profile-functions/profile-factory/user-role.enum.ts @@ -4,5 +4,11 @@ export enum UserRole { member = 'Topcoder User', tcaAdmin = 'TCA Admin', administrator = 'administrator', - connectManager = 'Connect Manager' + connectManager = 'Connect Manager', + paymentAdmin = 'Payment Admin', + paymentViewer = 'Payment Viewer', + paymentProviderAdmin = 'PaymentProvider Admin', + paymentProviderViewer = 'PaymentProvider Viewer', + taxFormAdmin = 'TaxForm Admin', + taxFormViewer = 'TaxForm Viewer' } diff --git a/tsconfig.paths.json b/tsconfig.paths.json index 3f83bc357..21ca9255e 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -30,6 +30,9 @@ "@wallet/*": [ "./src/apps/wallet/src/*" ], + "@walletAdmin/*": [ + "./src/apps/wallet-admin/src/*" + ], "@libs/ui/styles/*": [ "./src/libs/ui/lib/styles/*" ] From 9e9de7e0af702e3523c252fd57250cb61808084c Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Sun, 28 Jan 2024 12:21:55 +0600 Subject: [PATCH 33/48] wip: wallet admin Signed-off-by: Rakib Ansary --- .../home/page-layout/WalletLayout.module.scss | 80 ++++- .../tabs/payments/PaymentsTab.module.scss | 4 + .../src/home/tabs/payments/PaymentsTab.tsx | 170 +++++++-- .../tabs/tax-forms/TaxFormsTab.module.scss | 4 + .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 210 +++++++++-- .../home/tabs/winnings/Winnings.module.scss | 4 + .../src/home/tabs/winnings/WinningsTab.tsx | 333 +++++++++++++----- .../lib/components/filter-bar/FilterBar.tsx | 29 +- .../generic-table/GenericTable.module.scss | 0 .../components/generic-table/GenericTable.tsx | 37 ++ .../src/lib/components/generic-table/index.ts | 1 + .../wallet-admin/src/lib/models/Column.ts | 5 + .../wallet-admin/src/lib/services/wallet.ts | 102 +++++- .../lib/xhr/xhr-functions/xhr.functions.ts | 10 + 14 files changed, 829 insertions(+), 160 deletions(-) create mode 100644 src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.module.scss create mode 100644 src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.tsx create mode 100644 src/apps/wallet-admin/src/lib/components/generic-table/index.ts create mode 100644 src/apps/wallet-admin/src/lib/models/Column.ts diff --git a/src/apps/wallet-admin/src/home/page-layout/WalletLayout.module.scss b/src/apps/wallet-admin/src/home/page-layout/WalletLayout.module.scss index 6ff4b2df4..60664866d 100644 --- a/src/apps/wallet-admin/src/home/page-layout/WalletLayout.module.scss +++ b/src/apps/wallet-admin/src/home/page-layout/WalletLayout.module.scss @@ -2,4 +2,82 @@ .contentLayoutOuter { margin: $sp-8 auto !important; -} \ No newline at end of file +} + + +.tableContainer { + width: 100%; + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + margin: 16px 0; + + th, + td { + text-align: left; + padding: 8px; + border-bottom: 1px solid #eaeaea; + + &:last-child { + width: 50px; + } + } + + th { + background-color: #f5f5f5; + position: sticky; + font-weight: 600; + top: 0; + } + + tbody { + tr { + &:nth-child(odd) { + background-color: #f9f9f9; + } + &:nth-child(even) { + background-color: #ffffff; + } + } + } +} + +.selected { + background-color: #e7f4ff; +} + +.paymentFooter { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + margin-top: 16px; + border-radius: 8px; + background-color: white; + border: 1px solid #eaeaea; + + .total { + font-size: 18px; + font-weight: bold; + + color: #333; + } +} + +@media (max-width: 768px) { + .paymentFooter { + flex-direction: column; + align-items: stretch; + + .total { + margin-bottom: 10px; + } + + .payMeButton { + width: 100%; + } + } +} diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.module.scss b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.module.scss index a9e9eed24..a054e0db6 100644 --- a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.module.scss +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.module.scss @@ -24,5 +24,9 @@ .content { border-radius: 4px; margin-top: $sp-4; + + .actionButtons { + display: flex; + } } } diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx index 038cd1107..a819a2e2b 100644 --- a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx @@ -1,25 +1,50 @@ /* eslint-disable react/jsx-no-bind */ import { toast } from 'react-toastify' -import React, { FC, useCallback, useState } from 'react' +import React, { FC, useCallback, useMemo, useState } from 'react' -import { LoadingCircles } from '~/libs/ui' +import { Button, ConfirmModal, LoadingCircles } from '~/libs/ui' import { FilterBar } from '../../../lib' -import { fetchPaymentProviders } from '../../../lib/services/wallet' +import { deletePaymentProvider, fetchPaymentProviderDetail, fetchPaymentProviders } from '../../../lib/services/wallet' +import { GenericTable } from '../../../lib/components/generic-table' +import { Column } from '../../../lib/models/Column' +import { ConfirmFlowData } from '../../../lib/models/ConfirmFlowData' import styles from './PaymentsTab.module.scss' const PaymentsTab: FC = () => { + const [confirmFlow, setConfirmFlow] = React.useState(undefined) + const [isLoading, setIsLoading] = React.useState(false) const [currentPage, setCurrentPage] = useState(1) - const [pageSize, setPageSize] = useState(10) + const [pageSize, setPageSize] = useState(50) const [userIdsFilter, setUserIdsFilter] = useState([]) + const [paymentMethods, setPaymentMethods] = useState([]) + + const renderConfirmModalContent = useMemo(() => { + if (confirmFlow?.content === undefined) { + return undefined + } - const loadTaxForms = useCallback(async () => { + if (typeof confirmFlow?.content === 'function') { + return confirmFlow?.content() + } + + return confirmFlow?.content + }, [confirmFlow]) + + const loadPaymentProviders = useCallback(async () => { setIsLoading(true) try { - const paymentMethods = await fetchPaymentProviders(currentPage, pageSize, userIdsFilter) - console.log('paymentMethods', paymentMethods) + const response = await fetchPaymentProviders(currentPage, pageSize, userIdsFilter) + // eslint-disable-next-line max-len + setPaymentMethods((response.paymentMethods ?? []).map((data: {id: string, upmId: string, name: string, status: string, userId: string}) => ({ + id: data.id, + name: data.name, + status: data.status, + upmId: data.upmId, + userId: data.userId, + }))) } catch (apiError) { toast.error('Something went wrong fetching user payment methods. Please try again later.') } @@ -28,35 +53,114 @@ const PaymentsTab: FC = () => { }, [userIdsFilter]) React.useEffect(() => { - loadTaxForms() - }, [loadTaxForms]) + loadPaymentProviders() + }, [loadPaymentProviders]) - return ( -
-
-

Payment Provider

+ function renderActions(row: any): JSX.Element { + return ( +
+
-
- { - console.log(key, value) - }} - onResetFilters={() => { - console.log('reset') - }} - /> -
- {isLoading && } - {!isLoading && ( -
Table data
- )} + ) + } + + const columns: Column[] = [ + { + accessor: 'userId', + Header: 'User ID', + }, + { + accessor: 'name', + Header: 'Selected Payment Provider', + }, + { + accessor: 'status', + Header: 'Status', + }, + { + accessor: 'actions', + Cell: ({ row }) => (renderActions(row)), + Header: 'Actions', + }, + ] + + return ( + <> +
+
+

Payment Provider

+
+
+ { + setUserIdsFilter(value) + }} + onResetFilters={() => { + setUserIdsFilter([]) + }} + /> +
+ {isLoading && } + {!isLoading && ( + + )} +
-
+ {confirmFlow && ( + +
{renderConfirmModalContent}
+
+ )} + ) } diff --git a/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss b/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss index a9e9eed24..a054e0db6 100644 --- a/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss +++ b/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss @@ -24,5 +24,9 @@ .content { border-radius: 4px; margin-top: $sp-4; + + .actionButtons { + display: flex; + } } } diff --git a/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx index 43b7df517..b2d2f386c 100644 --- a/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx +++ b/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx @@ -1,30 +1,89 @@ +/* eslint-disable max-len */ /* eslint-disable react/jsx-no-bind */ import { toast } from 'react-toastify' -import React, { FC, useCallback, useState } from 'react' +import React, { FC, useCallback, useMemo, useState } from 'react' -import { LoadingCircles } from '~/libs/ui' +import { Button, ConfirmModal, LoadingCircles } from '~/libs/ui' import { UserProfile } from '~/libs/core' +import { downloadBlob } from '~/libs/shared' import { FilterBar } from '../../../lib' -import { fetchTaxForms } from '../../../lib/services/wallet' +import { deleteTaxForm, fetchTaxFormDetail, fetchTaxForms } from '../../../lib/services/wallet' +import { ConfirmFlowData } from '../../../lib/models/ConfirmFlowData' +import { Column } from '../../../lib/models/Column' +import { GenericTable } from '../../../lib/components/generic-table' import styles from './TaxFormsTab.module.scss' +function formatIOSDateString(iosDateString: string): string { + const date = new Date(iosDateString) + + if (Number.isNaN(date.getTime())) { + return 'N/A'; + } + + const options: Intl.DateTimeFormatOptions = { + day: 'numeric', + hour: '2-digit', + hour12: true, + minute: '2-digit', + month: 'long', + second: '2-digit', + year: 'numeric', + } + + return date.toLocaleDateString(undefined, options) +} + interface TaxFormsTabProps { profile: UserProfile } const TaxFormsTab: FC = (_props: TaxFormsTabProps) => { + const [confirmFlow, setConfirmFlow] = React.useState(undefined) + const [isLoading, setIsLoading] = React.useState(false) const [currentPage, setCurrentPage] = useState(1) - const [pageSize, setPageSize] = useState(10) + const [pageSize, setPageSize] = useState(50) const [userIdsFilter, setUserIdsFilter] = useState([]) + const [taxForms, setTaxForms] = useState([]) + + const renderConfirmModalContent = useMemo(() => { + if (confirmFlow?.content === undefined) { + return undefined + } + + if (typeof confirmFlow?.content === 'function') { + return confirmFlow?.content() + } + + return confirmFlow?.content + }, [confirmFlow]) + const loadTaxForms = useCallback(async () => { setIsLoading(true) try { - const taxForms = await fetchTaxForms(currentPage, pageSize, userIdsFilter) - console.log('TaxForms', taxForms) + const response = await fetchTaxForms(currentPage, pageSize, userIdsFilter) + setTaxForms(response.forms.map((data: { + id: string, + taxForm: { + name: string, + }, + status: string, + dateFiled: string, + withholdingAmount: string, + withholdingPercentage: string, + userId: string, + }) => ({ + dateFiled: formatIOSDateString(data.dateFiled), + id: data.id, + name: data.taxForm.name, + status: data.status, + userId: data.userId, + witholdingAmount: data.withholdingAmount, + witholdingPercentage: data.withholdingPercentage, + }))) } catch (apiError) { toast.error('Something went wrong fetching tax forms. Please try again later.') } @@ -36,32 +95,123 @@ const TaxFormsTab: FC = (_props: TaxFormsTabProps) => { loadTaxForms() }, [loadTaxForms]) - return ( -
-
-

Tax Forms

+ function renderActions(row: any): JSX.Element { + return ( +
+
-
- { - console.log(key, value) - }} - onResetFilters={() => { - console.log('reset') - }} - /> -
- {isLoading && } - {!isLoading && ( -
Table data
- )} + ) + } + + const columns: Column[] = [ + { + accessor: 'userId', + Header: 'User ID', + }, + { + accessor: 'name', + Header: 'Filed TaxForm', + }, + { + accessor: 'dateFiled', + Header: 'Date Filed', + }, + { + accessor: 'status', + Header: 'Status', + }, + { + accessor: 'witholdingAmount', + Header: 'Witholding Amount', + }, + { + accessor: 'witholdingPercentage', + Header: 'Witholding Percentage', + }, + { + accessor: 'actions', + Cell: ({ row }) => (renderActions(row)), + Header: 'Actions', + }, + ] + + return ( + <> +
+
+

Tax Forms

+
+
+ { + console.log(key, value) + }} + onResetFilters={() => { + console.log('reset') + }} + /> +
+ {isLoading && } + {!isLoading && ( + + )} +
-
+ {confirmFlow && ( + +
{renderConfirmModalContent}
+
+ )} + ) } diff --git a/src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss b/src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss index a9e9eed24..a054e0db6 100644 --- a/src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss +++ b/src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss @@ -24,5 +24,9 @@ .content { border-radius: 4px; margin-top: $sp-4; + + .actionButtons { + display: flex; + } } } diff --git a/src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx index 9fd13db87..d8aeef3e4 100644 --- a/src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx @@ -1,13 +1,18 @@ +/* eslint-disable max-len */ +/* eslint-disable sort-keys */ /* eslint-disable react/jsx-no-bind */ -import React, { FC, useEffect } from 'react' +import { toast } from 'react-toastify' +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react' -import { LoadingCircles } from '~/libs/ui' +import { Button, ConfirmModal, LoadingCircles } from '~/libs/ui' import { UserProfile } from '~/libs/core' -import { getPayments, processPayments } from '../../../lib/services/wallet' -import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' +import { editWinningRecord, searchWinnings } from '../../../lib/services/wallet' +import { PaymentDetail, WinningDetail } from '../../../lib/models/WinningDetail' import { FilterBar } from '../../../lib' -import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' +import { ConfirmFlowData } from '../../../lib/models/ConfirmFlowData' +import { Column } from '../../../lib/models/Column' +import { GenericTable } from '../../../lib/components/generic-table' import styles from './Winnings.module.scss' @@ -36,114 +41,260 @@ function formatIOSDateString(iosDateString: string): string { } const ListView: FC = (props: ListViewProps) => { - const [winnings, setWinnings] = React.useState>([]) + const [confirmFlow, setConfirmFlow] = React.useState(undefined) + const [isLoading, setIsLoading] = React.useState(false) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(50) + const [externalIdsFilter, setExternalIdsFilter] = useState([]) + const [userIdsFilter, setUserIdsFilter] = useState([]) + + const [winnings, setWinnings] = useState([]) + + const renderConfirmModalContent = useMemo(() => { + if (confirmFlow?.content === undefined) { + return undefined + } + + if (typeof confirmFlow?.content === 'function') { + return confirmFlow?.content() + } + + return confirmFlow?.content + }, [confirmFlow]) // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - const fetchWinnings = async () => { + const fetchWinnings = useCallback(async () => { setIsLoading(true) try { - const payments = await getPayments(`${props.profile.userId}`) - setWinnings(convertToWinnings(payments)) + const filters = { + externalIds: externalIdsFilter, + winnerId: userIdsFilter.length ? userIdsFilter[0] : undefined, + } + + const payments = await searchWinnings(currentPage, pageSize, filters) + console.log('payments', payments) + const tempWinnings = convertToWinnings(payments as WinningDetail[]) + console.log('TempWinnings', tempWinnings) + setWinnings(tempWinnings) } catch (apiError) {} setIsLoading(false) - } + }, [currentPage, pageSize, externalIdsFilter, userIdsFilter]) useEffect(() => { fetchWinnings() - }, []) + }, [fetchWinnings]) - function convertToWinnings(payments: WinningDetail[]): Winning[] { - const tempWinnings: Winning[] = [] + function convertToWinnings(payments: WinningDetail[]): any[] { + let tempWinnings: any[] = [] payments.forEach((payment: WinningDetail) => { - const winning: Winning = { - canBeReleased: new Date(payment.releaseDate) <= new Date(), - createDate: formatIOSDateString(payment.createdAt), - currency: payment.details[0].currency, - // eslint-disable-next-line max-len - datePaid: payment.datePaid !== undefined && payment.datePaid.length ? formatIOSDateString(payment.datePaid) : '-', - description: payment.description, - details: payment.details, - id: payment.id, - netPayment: `${new Intl.NumberFormat('en-US', { + const temp = payment.details.map((detail: PaymentDetail) => { + const winning: any = { + origin: payment.origin, + userId: payment.winnerId, + externalId: payment.externalId, + createDate: formatIOSDateString(payment.createdAt), currency: payment.details[0].currency, - maximumFractionDigits: 2, - minimumFractionDigits: 2, - style: 'currency', - }) - .format( - Number(payment.details[0].totalAmount), - )}`, - releaseDate: formatIOSDateString(payment.releaseDate), - status: payment.details[0].status, - type: payment.category.replaceAll('_', ' '), - } - tempWinnings.push(winning) + // eslint-disable-next-line max-len + datePaid: payment.datePaid !== undefined && payment.datePaid.length ? formatIOSDateString(payment.datePaid) : '-', + description: payment.description, + details: payment.details, + id: payment.id, + paymentId: detail.id, + netPayment: `${new Intl.NumberFormat('en-US', { + currency: detail.currency, + maximumFractionDigits: 2, + minimumFractionDigits: 2, + style: 'currency', + }) + .format( + Number(detail.netAmount), + )}`, + installmentNumber: detail.installmentNumber, + releaseDate: formatIOSDateString(payment.releaseDate), + status: payment.details[0].status, + type: payment.category.replaceAll('_', ' '), + } + + return winning + }) + + tempWinnings = tempWinnings.concat(temp) }) return tempWinnings } - return ( -
-
-

Winnings

+ function renderActions(row: any): JSX.Element { + return ( +
+
-
- { - console.log(key, value) - }} - onResetFilters={() => { - console.log('reset') - }} - /> -
- {isLoading && } - {!isLoading && ( - { - const ids = Object.keys(paymentIds) - await processPayments(ids) - - fetchWinnings() - }} - /> - )} + ) + } + + const columns: Column[] = [ + { + accessor: 'origin', + Header: 'Origin', + }, + { + accessor: 'userId', + Header: 'User ID', + }, + { + accessor: 'externalId', + Header: 'External ID', + }, + { + accessor: 'type', + Header: 'Winning Type', + }, + { + accessor: 'description', + Header: 'Description', + }, + { + accessor: 'status', + Header: 'Status', + }, + { + accessor: 'installmentNumber', + Header: 'Installment', + }, + { + accessor: 'releaseDate', + Header: 'Release Date', + }, + { + accessor: 'netPayment', + Header: 'Amount', + }, + { + accessor: 'actions', + Cell: ({ row }) => (renderActions(row)), + Header: 'Actions', + }, + ] + + return ( + <> +
+
+

Tax Forms

+
+
+ { + console.log(key, value) + + if (key === 'externalId') { + setExternalIdsFilter(value) + } else if (key === 'userId') { + setUserIdsFilter(value) + } + }} + onResetFilters={() => { + console.log('reset') + }} + /> +
+ {isLoading && } + {!isLoading && ( + + )} +
-
+ {confirmFlow && ( + +
{renderConfirmModalContent}
+
+ )} + ) } diff --git a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx index c5bf5fc9d..c5c525f14 100644 --- a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx +++ b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx @@ -2,6 +2,7 @@ import React, { ChangeEvent } from 'react' import { Button, InputSelect, InputText } from '~/libs/ui' +import { InputHandleAutocomplete, MembersAutocompeteResult } from '~/apps/gamification-admin/src/game-lib' import styles from './FilterBar.module.scss' @@ -13,7 +14,7 @@ type FilterOptions = { type Filter = { key: string label: string - type: 'input' | 'dropdown' + type: 'input' | 'dropdown' | 'member_autocomplete' options?: FilterOptions[] } @@ -24,12 +25,12 @@ interface FilterBarProps { } const FilterBar: React.FC = (props: FilterBarProps) => { - const [selectedValue, setSelectedValue] = React.useState>(new Map()) + const [selectedValue, setSelectedValue] = React.useState>(new Map()) const renderDropdown = (index: number, filter: Filter): JSX.Element => ( ) { setSelectedValue(new Map(selectedValue.set(filter.key, event.target.value))) @@ -42,6 +43,19 @@ const FilterBar: React.FC = (props: FilterBarProps) => { /> ) + const renderMemberAutoComplete = (index: number, filter: Filter): JSX.Element => ( + ) { + setSelectedValue(new Map(selectedValue.set(filter.key, event))) + props.onFilterChange(filter.key, event.map(member => member.userId)) + }} + tabIndex={index} + /> + ) + return (
@@ -55,10 +69,16 @@ const FilterBar: React.FC = (props: FilterBarProps) => { type='text' tabIndex={index} onChange={function onChange(event: ChangeEvent) { - props.onFilterChange(options.key, [event.target.value]) + if (event.target.value === '') { + setSelectedValue(new Map(selectedValue.set(options.key, ''))) + props.onFilterChange(options.key, []) + } else { + props.onFilterChange(options.key, [event.target.value]) + } }} /> )} + {options.type === 'member_autocomplete' && renderMemberAutoComplete(index, options)}
))}
@@ -66,6 +86,7 @@ const FilterBar: React.FC = (props: FilterBarProps) => { primary className={styles.resetButton} label='Reset' + size='lg' onClick={function onResetClick() { setSelectedValue(new Map()) props.onResetFilters?.() diff --git a/src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.module.scss b/src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.module.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.tsx b/src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.tsx new file mode 100644 index 000000000..311a077ad --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.tsx @@ -0,0 +1,37 @@ +import React from 'react' + +import { Column } from '../../models/Column' + +interface GenericTableProps { + columns: Column[]; + data: any[]; +} + +const GenericTable: React.FC = (props: GenericTableProps) => ( + + + + {props.columns.map((column, index) => ( + + ))} + + + + {props.data.map((row, rowIndex) => ( + + {props.columns.map((column, columnIndex) => { + const cellValue = row[column.accessor] + const CellRenderer = column.Cell + return ( + + ) + })} + + ))} + +
{column.Header}
+ {CellRenderer ? : cellValue} +
+) + +export default GenericTable diff --git a/src/apps/wallet-admin/src/lib/components/generic-table/index.ts b/src/apps/wallet-admin/src/lib/components/generic-table/index.ts new file mode 100644 index 000000000..87f854d8d --- /dev/null +++ b/src/apps/wallet-admin/src/lib/components/generic-table/index.ts @@ -0,0 +1 @@ +export { default as GenericTable } from './GenericTable' diff --git a/src/apps/wallet-admin/src/lib/models/Column.ts b/src/apps/wallet-admin/src/lib/models/Column.ts new file mode 100644 index 000000000..5eb62e52a --- /dev/null +++ b/src/apps/wallet-admin/src/lib/models/Column.ts @@ -0,0 +1,5 @@ +export type Column = { + Header: string; + accessor: string; + Cell?: (props: { row: any }) => JSX.Element; +}; diff --git a/src/apps/wallet-admin/src/lib/services/wallet.ts b/src/apps/wallet-admin/src/lib/services/wallet.ts index d6318480d..e0c4aa655 100644 --- a/src/apps/wallet-admin/src/lib/services/wallet.ts +++ b/src/apps/wallet-admin/src/lib/services/wallet.ts @@ -1,7 +1,7 @@ import { AxiosError } from 'axios' import { EnvironmentConfig } from '~/config' -import { xhrDeleteAsync, xhrGetAsync, xhrPostAsync, xhrPostAsyncWithBlobHandling } from '~/libs/core' +import { xhrDeleteAsync, xhrGetAsync, xhrPatchAsync, xhrPostAsync, xhrPostAsyncWithBlobHandling } from '~/libs/core' import { WalletDetails } from '../models/WalletDetails' import { PaymentProvider } from '../models/PaymentProvider' @@ -10,6 +10,7 @@ import { TaxForm } from '../models/TaxForm' import { OtpVerificationResponse } from '../models/OtpVerificationResponse' import { TransactionResponse } from '../models/TransactionId' import ApiResponse from '../models/ApiResponse' +import { getAsyncWithBlobHandling } from '~/libs/core/lib/xhr/xhr-functions/xhr.functions' const baseUrl = `${EnvironmentConfig.API.V5}/payments` @@ -257,3 +258,102 @@ export async function fetchPaymentProviders(pageNumber: number, pageSize: number throw new Error('Error fetching tax forms') } } + +export async function fetchPaymentProviderDetail(userId: string, paymentProviderId: string): Promise { + const url = `${baseUrl}/admin/payment-methods/${userId}/${paymentProviderId}` + try { + const response = await xhrGetAsync>(url) + + if (response.status === 'error') { + throw new Error('Error fetching tax forms') + } + + return response.data + } catch (err) { + throw new Error('Error fetching tax forms') + } +} + + +export async function deletePaymentProvider(userId: string, paymentProviderId: string): Promise { + const url = `${baseUrl}/admin/payment-methods/${userId}/${paymentProviderId}` + try { + const response = await xhrDeleteAsync>(url) + + if (response.status === 'error') { + throw new Error('Error fetching tax forms') + } + + return response.data + } catch (err) { + throw new Error('Error fetching tax forms') + } +} + +export async function fetchTaxFormDetail(userId: string, taxFormId: string): Promise { + const url = `${baseUrl}/admin/tax-forms/${userId}/${taxFormId}/download` + try { + const response = await getAsyncWithBlobHandling>(url) + + + console.log('Response', response) + + return response + } catch (err) { + throw new Error('Error fetching tax forms') + } +} + +export async function deleteTaxForm(userId: string, taxFormId: string): Promise { + const url = `${baseUrl}/admin/tax-forms/${userId}/${taxFormId}` + try { + const response = await xhrDeleteAsync>(url) + + if (response.status === 'error') { + throw new Error('Error fetching tax forms') + } + + return response.data + } catch (err) { + throw new Error('Error fetching tax forms') + } +} + +export async function searchWinnings(pageNumber: number, pageSize: number, filters: any): Promise { + const body = JSON.stringify({ + externalIds: filters.externalIds, + winnerId: filters.winnerId, + }) + + try { + const response = await xhrPostAsync>(`${baseUrl}/admin/winnings/search`, body) + + if (response.status === 'error') { + throw new Error('Error fetching tax forms') + } + + return response.data + } catch (err) { + throw new Error('Error fetching tax forms') + } +} + +export async function editWinningRecord(winningId: string, paymentId: string, status: string): Promise { + const body = JSON.stringify({ + paymentId, + paymentStatus: status, + winningsId: winningId, + }) + + try { + const response = await xhrPatchAsync>(`${baseUrl}/admin/winnings`, body) + + if (response.status === 'error') { + throw new Error('Error updating payment record') + } + + return response.data + } catch (err) { + throw new Error('Error updating payment record') + } +} diff --git a/src/libs/core/lib/xhr/xhr-functions/xhr.functions.ts b/src/libs/core/lib/xhr/xhr-functions/xhr.functions.ts index c050ebba3..af5a3d9e2 100644 --- a/src/libs/core/lib/xhr/xhr-functions/xhr.functions.ts +++ b/src/libs/core/lib/xhr/xhr-functions/xhr.functions.ts @@ -50,6 +50,16 @@ export async function getAsync( return output.data } +export async function getAsyncWithBlobHandling( + url: string, + xhrInstance: AxiosInstance = globalInstance, +): Promise { + const response: AxiosResponse = await xhrInstance.get(url, { + responseType: 'blob', + }) + return response.data +} + export interface PaginatedResponse { data: T total: number From 0e01f1172192f2508dfcbefd502d2d38f383986e Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 30 Jan 2024 16:06:08 +0600 Subject: [PATCH 34/48] fix(winnings): prettier table Signed-off-by: Rakib Ansary --- package.json | 1 + .../src/home/tabs/winnings/WinningsTab.tsx | 143 +++++++++++++----- .../filter-bar/FilterBar.module.scss | 27 ++++ .../lib/components/filter-bar/FilterBar.tsx | 114 ++++++++++---- .../payments-table/PaymentTable.module.scss | 34 +++-- .../payments-table/PaymentTable.tsx | 26 ++-- yarn.lock | 12 ++ 7 files changed, 269 insertions(+), 88 deletions(-) diff --git a/package.json b/package.json index c53e8d108..c304fdc14 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@storybook/react": "^7.0.5", "@stripe/react-stripe-js": "1.13.0", "@stripe/stripe-js": "1.41.0", + "@tanstack/react-table": "^8.11.7", "@types/testing-library__jest-dom": "^5.14.5", "apexcharts": "^3.36.0", "axios": "^1.1.2", diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index a55ec9cf0..456aff009 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -1,15 +1,15 @@ /* eslint-disable react/jsx-no-bind */ import React, { FC, useEffect } from 'react' -import { LoadingCircles } from '~/libs/ui' +import { Collapsible, LoadingCircles, PageDivider } from '~/libs/ui' import { UserProfile } from '~/libs/core' import { getPayments, processPayments } from '../../../lib/services/wallet' import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' -import { FilterBar } from '../../../lib' -import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' import styles from './Winnings.module.scss' +import { FilterBar } from '../../../lib' +import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' interface ListViewProps { profile: UserProfile @@ -23,18 +23,30 @@ function formatIOSDateString(iosDateString: string): string { } const options: Intl.DateTimeFormatOptions = { - day: 'numeric', - hour: '2-digit', - hour12: true, - minute: '2-digit', - month: 'long', - second: '2-digit', + day: '2-digit', + month: '2-digit', year: 'numeric', } - return date.toLocaleDateString(undefined, options) + return date.toLocaleDateString('en-GB', options); +} + +function formatStatus(status: string): string { + switch (status) { + case 'ON_HOLD': + return "On Hold"; + case 'OWED': + return 'Available' + case 'PAID': + return 'Paid' + case 'CANCELLED': + return 'Cancelled' + default: + return status.replaceAll('_', ' ') + } } + const ListView: FC = (props: ListViewProps) => { const [winnings, setWinnings] = React.useState>([]) const [isLoading, setIsLoading] = React.useState(false) @@ -63,7 +75,10 @@ const ListView: FC = (props: ListViewProps) => { createDate: formatIOSDateString(payment.createdAt), currency: payment.details[0].currency, // eslint-disable-next-line max-len - datePaid: payment.datePaid !== undefined && payment.datePaid.length ? formatIOSDateString(payment.datePaid) : '-', + datePaid: + payment.datePaid !== undefined && payment.datePaid.length + ? formatIOSDateString(payment.datePaid) + : '-', description: payment.description, details: payment.details, id: payment.id, @@ -72,13 +87,10 @@ const ListView: FC = (props: ListViewProps) => { maximumFractionDigits: 2, minimumFractionDigits: 2, style: 'currency', - }) - .format( - Number(payment.details[0].totalAmount), - )}`, + }).format(Number(payment.details[0].totalAmount))}`, releaseDate: formatIOSDateString(payment.releaseDate), - status: payment.details[0].status, - type: payment.category.replaceAll('_', ' '), + status: formatStatus(payment.details[0].status), + type: payment.category.replaceAll('_', ' ').toLowerCase(), } tempWinnings.push(winning) }) @@ -91,27 +103,86 @@ const ListView: FC = (props: ListViewProps) => {

Winnings

- {/* */} -
- {isLoading && } - {!isLoading && ( - { - const ids = Object.keys(paymentIds) - await processPayments(ids) - - fetchWinnings() +
+ Payments}> + { + console.log(key, value) + }} + onResetFilters={() => { + console.log('reset') }} /> - )} + {/* */} + {isLoading && } + {!isLoading && ( + + )} +
) diff --git a/src/apps/wallet/src/lib/components/filter-bar/FilterBar.module.scss b/src/apps/wallet/src/lib/components/filter-bar/FilterBar.module.scss index e69de29bb..c68d55bd3 100644 --- a/src/apps/wallet/src/lib/components/filter-bar/FilterBar.module.scss +++ b/src/apps/wallet/src/lib/components/filter-bar/FilterBar.module.scss @@ -0,0 +1,27 @@ +.FilterBar { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + + .filterContainer { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + + .filter { + width: 200px; + height: 47px; + } + + .filterInput { + margin-bottom: 0px; + } + } + + .resetButton { + flex-shrink: 0; + margin-left: 10px; + } + } diff --git a/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx b/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx index 490da5322..d84753f73 100644 --- a/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx +++ b/src/apps/wallet/src/lib/components/filter-bar/FilterBar.tsx @@ -1,45 +1,99 @@ -import React, { useState } from 'react' +/* eslint-disable max-len */ +import React, { ChangeEvent } from 'react' -type DropdownOption = { +import { Button, InputSelect, InputText } from '~/libs/ui' +import { InputHandleAutocomplete, MembersAutocompeteResult } from '~/apps/gamification-admin/src/game-lib' + +import styles from './FilterBar.module.scss' + +type FilterOptions = { label: string value: string } +type Filter = { + key: string + label: string + type: 'input' | 'dropdown' | 'member_autocomplete' + options?: FilterOptions[] +} + interface FilterBarProps { - filters: DropdownOption[][] } + filters: Filter[], + onFilterChange: (key: string, value: string[]) => void + onResetFilters?: () => void +} const FilterBar: React.FC = (props: FilterBarProps) => { - const [selectedValues, setSelectedValues] = useState(new Array(props.filters.length) - .fill('')) + const [selectedValue, setSelectedValue] = React.useState>(new Map()) - const handleDropdownChange = (value: string, index: number): void => { - const updatedValues = [...selectedValues] - updatedValues[index] = value - setSelectedValues(updatedValues) - } + const renderDropdown = (index: number, filter: Filter): JSX.Element => ( + ) { + setSelectedValue(new Map(selectedValue.set(filter.key, event.target.value))) + props.onFilterChange(filter.key, [event.target.value]) + }} + name={filter.key} + label={filter.label} + dirty + placeholder={filter.label} + /> + ) - const handleClearFilters = (): void => { - setSelectedValues(new Array(props.filters.length) - .fill('')) - } + const renderMemberAutoComplete = (index: number, filter: Filter): JSX.Element => ( + ) { + setSelectedValue(new Map(selectedValue.set(filter.key, event))) + props.onFilterChange(filter.key, event.map(member => member.userId)) + }} + tabIndex={index} + /> + ) return ( -
- {props.filters.map((options, index) => ( - - ))} - +
+
+ {props.filters.map((options, index) => ( +
+ {options.type === 'dropdown' && renderDropdown(index, options)} + {options.type === 'input' && ( + ) { + if (event.target.value === '') { + setSelectedValue(new Map(selectedValue.set(options.key, ''))) + props.onFilterChange(options.key, []) + } else { + props.onFilterChange(options.key, [event.target.value]) + } + }} + /> + )} + {options.type === 'member_autocomplete' && renderMemberAutoComplete(index, options)} +
+ ))} +
+
) } diff --git a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.module.scss b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.module.scss index 8757b2164..4f77521cc 100644 --- a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.module.scss +++ b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.module.scss @@ -3,40 +3,56 @@ .tableContainer { width: 100%; overflow-x: auto; + margin-top: $sp-2; } table { width: 100%; - border-collapse: collapse; + border-collapse: separate; + border-spacing: 0; margin: 16px 0; th, td { text-align: left; padding: 8px; - border-bottom: 1px solid #eaeaea; &:last-child { width: 50px; } } - th { - background-color: #f5f5f5; - position: sticky; - top: 0; - } - tbody { tr { &:nth-child(odd) { - background-color: #f9f9f9; + background-color: #f4f4f4; } &:nth-child(even) { background-color: #ffffff; } + + .capitalize { + text-transform: capitalize; + } } } + + th { + top: 0; + background-color: white !important; + text-transform: uppercase; + } + + tbody tr td:first-child { + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; + } + + tbody tr td:last-child { + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + } + } .selected { diff --git a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx index 240fcfcfd..9c3208a99 100644 --- a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx +++ b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx @@ -52,14 +52,14 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => - - - - - - - - + + + + + + + - - - - + + + + +
DescriptionTypeCreate DateNet PaymentStatusRelease DateDate Paid + DESCRIPTIONTYPECREATE DATENET PAYMENTSTATUSRELEASE DATEDATE PAID = (props: PaymentTableProps) => key={`${payment.id}`} className={selectedPayments[payment.id] ? 'selected' : ''} > - {payment.description}{payment.type}{payment.createDate}{payment.netPayment}{payment.status}{payment.description}{payment.type}{payment.createDate}{payment.netPayment}{payment.status} {payment.releaseDate} {payment.datePaid} diff --git a/yarn.lock b/yarn.lock index 7539dd276..978b46cbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4779,6 +4779,18 @@ dependencies: defer-to-connect "^2.0.0" +"@tanstack/react-table@^8.11.7": + version "8.11.7" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.11.7.tgz#a2934c1ee32025d58c9dc4d13cbc15fe0a3e045e" + integrity sha512-ZbzfMkLjxUTzNPBXJYH38pv2VpC9WUA+Qe5USSHEBz0dysDTv4z/ARI3csOed/5gmlmrPzVUN3UXGuUMbod3Jg== + dependencies: + "@tanstack/table-core" "8.11.7" + +"@tanstack/table-core@8.11.7": + version "8.11.7" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.11.7.tgz#266af5af8576e8860df8abfe4e32e9c4ffbc76b0" + integrity sha512-N3ksnkbPbsF3PjubuZCB/etTqvctpXWRHIXTmYfJFnhynQKjeZu8BCuHvdlLPpumKbA+bjY4Ay9AELYLOXPWBg== + "@testing-library/dom@^8.3.0": version "8.20.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.20.0.tgz#914aa862cef0f5e89b98cc48e3445c4c921010f6" From 03b801e38a7f35d51774b9c88a486defa8dfe04e Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 30 Jan 2024 16:59:21 +0600 Subject: [PATCH 35/48] fix(payment-providers): core-181 design fixes Signed-off-by: Rakib Ansary --- .../payment-info-modal/PaymentInfoModal.tsx | 34 ++++---- .../src/home/tabs/winnings/WinningsTab.tsx | 81 ++++++++++--------- 2 files changed, 62 insertions(+), 53 deletions(-) diff --git a/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx index ffc19803a..57276139c 100644 --- a/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx +++ b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx @@ -2,8 +2,8 @@ /* eslint-disable react/jsx-no-bind */ import { FC } from 'react' -import { BaseModal, Button } from '~/libs/ui' -import { PayoneerLogo, PayPalLogo } from '~/apps/accounts/src/lib' +import { BaseModal, Button, IconOutline, LinkButton } from '~/libs/ui' +import { PayoneerLogo, PayPalLogo } from '../../../../lib' import styles from './PaymentInfoModal.module.scss' @@ -23,8 +23,8 @@ function renderPayoneer(): JSX.Element { transfer option (where available) and a wire transfer option. Certain fees may apply.

- You will be directed to Payoneer's website in a new tab to complete your the connection. Please - make sure your account is fully verified to ensure withdrawal success. + You will be directed to Payoneer's website in a new tab to complete your connection. Please make + sure your account is fully verified to ensure withdrawal success. You can return here after finishing up on Payoneer's site.

@@ -48,22 +48,26 @@ function renderPaypal(): JSX.Element { const PaymentInfoModal: FC = (props: PaymentInfoModalProps) => ( { - props.handlePaymentSelection(props.selectedPaymentProvider) - }} - /> +
+ +
} onClose={props.handleModalClose} open size='body' title='CONNECT PAYMENT PROVIDER ACCOUNT' - classNames={{ modal: styles.infoModal }} - > + classNames={{ modal: styles.infoModal }}>
{props.selectedPaymentProvider === 'Payoneer' ? renderPayoneer() : renderPaypal()}
diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index 456aff009..ef7925547 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/jsx-no-bind */ -import React, { FC, useEffect } from 'react' +import React, { FC, useCallback, useEffect } from 'react' -import { Collapsible, LoadingCircles, PageDivider } from '~/libs/ui' +import { Collapsible, LoadingCircles } from '~/libs/ui' import { UserProfile } from '~/libs/core' import { getPayments, processPayments } from '../../../lib/services/wallet' @@ -10,6 +10,7 @@ import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' import styles from './Winnings.module.scss' import { FilterBar } from '../../../lib' import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' +import { parse } from 'path' interface ListViewProps { profile: UserProfile @@ -46,57 +47,61 @@ function formatStatus(status: string): string { } } +const formatCurrency = (amountStr: string, currency: string) => { + let amount: number; + try { + amount = parseFloat(amountStr); + } catch (error) { -const ListView: FC = (props: ListViewProps) => { - const [winnings, setWinnings] = React.useState>([]) - const [isLoading, setIsLoading] = React.useState(false) - - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - const fetchWinnings = async () => { - setIsLoading(true) - try { - const payments = await getPayments(`${props.profile.userId}`) - setWinnings(convertToWinnings(payments)) - } catch (apiError) {} - - setIsLoading(false) + return amountStr; } - useEffect(() => { - fetchWinnings() - }, []) + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: currency, + maximumFractionDigits: 2, + minimumFractionDigits: 2 + }).format(amount); +}; - function convertToWinnings(payments: WinningDetail[]): Winning[] { - const tempWinnings: Winning[] = [] +const ListView: FC = (props: ListViewProps) => { + const [winnings, setWinnings] = React.useState>([]) + const [isLoading, setIsLoading] = React.useState(false) - payments.forEach((payment: WinningDetail) => { - const winning: Winning = { + const convertToWinnings = useCallback((payments: WinningDetail[]) => { + return payments.map(payment => { + return { canBeReleased: new Date(payment.releaseDate) <= new Date(), createDate: formatIOSDateString(payment.createdAt), currency: payment.details[0].currency, - // eslint-disable-next-line max-len - datePaid: - payment.datePaid !== undefined && payment.datePaid.length - ? formatIOSDateString(payment.datePaid) - : '-', + datePaid: payment.datePaid ? formatIOSDateString(payment.datePaid) : '-', description: payment.description, details: payment.details, id: payment.id, - netPayment: `${new Intl.NumberFormat('en-US', { - currency: payment.details[0].currency, - maximumFractionDigits: 2, - minimumFractionDigits: 2, - style: 'currency', - }).format(Number(payment.details[0].totalAmount))}`, + netPayment: formatCurrency(payment.details[0].totalAmount, payment.details[0].currency), releaseDate: formatIOSDateString(payment.releaseDate), status: formatStatus(payment.details[0].status), type: payment.category.replaceAll('_', ' ').toLowerCase(), - } - tempWinnings.push(winning) - }) + }; + }); + }, []); - return tempWinnings - } + const fetchWinnings = useCallback(async () => { + setIsLoading(true); + try { + const payments = await getPayments(props.profile.userId.toString()); + const winningsData = convertToWinnings(payments); + setWinnings(winningsData); + } catch (apiError) { + console.error('Failed to fetch winnings:', apiError); + } finally { + setIsLoading(false); + } + }, [props.profile.userId, convertToWinnings]); + + useEffect(() => { + fetchWinnings(); + }, [fetchWinnings]); return (
From 9962787c57d31f6579bb5a62762a7c368c770791 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Tue, 30 Jan 2024 17:16:17 +0600 Subject: [PATCH 36/48] fix(lint): lint fixes Signed-off-by: Rakib Ansary --- .../src/home/tabs/payments/PaymentsTab.tsx | 8 +- .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 5 +- .../src/home/tabs/winnings/WinningsTab.tsx | 5 +- .../components/generic-table/GenericTable.tsx | 2 + .../wallet-admin/src/lib/services/wallet.ts | 6 +- .../payment-info-modal/PaymentInfoModal.tsx | 4 +- .../src/home/tabs/tax-forms/TaxFormsTab.tsx | 73 +++++++++------- .../src/home/tabs/winnings/WinningsTab.tsx | 85 +++++++++---------- 8 files changed, 103 insertions(+), 85 deletions(-) diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx index a819a2e2b..ebdae5ecf 100644 --- a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx @@ -16,11 +16,12 @@ const PaymentsTab: FC = () => { const [confirmFlow, setConfirmFlow] = React.useState(undefined) const [isLoading, setIsLoading] = React.useState(false) - const [currentPage, setCurrentPage] = useState(1) - const [pageSize, setPageSize] = useState(50) const [userIdsFilter, setUserIdsFilter] = useState([]) const [paymentMethods, setPaymentMethods] = useState([]) + const currentPage = 1 + const pageSize = 50 + const renderConfirmModalContent = useMemo(() => { if (confirmFlow?.content === undefined) { return undefined @@ -61,6 +62,7 @@ const PaymentsTab: FC = () => {
togglePaymentSelection(payment.id)} aria-label={`Select payment ${payment.id}`} diff --git a/src/apps/wallet/src/lib/models/WinningDetail.ts b/src/apps/wallet/src/lib/models/WinningDetail.ts index 9e07afbe7..7e7a35990 100644 --- a/src/apps/wallet/src/lib/models/WinningDetail.ts +++ b/src/apps/wallet/src/lib/models/WinningDetail.ts @@ -6,6 +6,7 @@ export interface PaymentDetail { installmentNumber: number status: string currency: string + datePaid: string } export interface Winning { From 78d8ac14a4d9929138d1b6c4d43f85340d96c113 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Wed, 31 Jan 2024 16:19:01 +0600 Subject: [PATCH 39/48] fix: lint & style issues Signed-off-by: Rakib Ansary --- .../payment-info-modal/PaymentInfoModal.tsx | 6 +- .../src/home/tabs/winnings/WinningsTab.tsx | 268 ++++++++++-------- .../PaymentProviderCard.tsx | 3 +- .../payments-table/PaymentTable.tsx | 4 +- 4 files changed, 165 insertions(+), 116 deletions(-) diff --git a/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx index fd2e0632f..cdb6df8be 100644 --- a/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx +++ b/src/apps/wallet/src/home/tabs/payments/payment-info-modal/PaymentInfoModal.tsx @@ -26,7 +26,9 @@ function renderPayoneer(): JSX.Element {

You will be directed to Payoneer's website in a new tab to complete your connection. Please make sure your account is fully verified to ensure withdrawal success. - You can return here after finishing up on Payoneer's site. + + You can return here after finishing up on Payoneer's site. +

) @@ -41,7 +43,7 @@ function renderPaypal(): JSX.Element { You will be directed to PayPal's website in a new tab to complete your connection. Please make sure your account is fully verified to ensure withdrawal success. {' '} - + You can return here after finishing up on PayPal's site. diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index 982942e9c..a0483b774 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -3,16 +3,19 @@ import { toast } from 'react-toastify' import { AxiosError } from 'axios' import React, { FC, useCallback, useEffect } from 'react' -import { Collapsible, LoadingCircles } from '~/libs/ui' +import { Collapsible, ConfirmModal, LoadingCircles } from '~/libs/ui' import { UserProfile } from '~/libs/core' import { getPayments, processPayments } from '../../../lib/services/wallet' import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' import { FilterBar } from '../../../lib' +import { ConfirmFlowData } from '../../../lib/models/ConfirmFlowData' import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' import styles from './Winnings.module.scss' +type PaymentId = { [paymentId: string]: boolean } + interface ListViewProps { profile: UserProfile } @@ -66,6 +69,7 @@ const formatCurrency = (amountStr: string, currency: string): string => { } const ListView: FC = (props: ListViewProps) => { + const [confirmFlow, setConfirmFlow] = React.useState(undefined) const [winnings, setWinnings] = React.useState>([]) const [isLoading, setIsLoading] = React.useState(false) @@ -100,128 +104,170 @@ const ListView: FC = (props: ListViewProps) => { } }, [props.profile.userId, convertToWinnings]) + const renderConfirmModalContent = React.useMemo(() => { + if (confirmFlow?.content === undefined) { + return undefined + } + + if (typeof confirmFlow?.content === 'function') { + return confirmFlow?.content() + } + + return confirmFlow?.content + }, [confirmFlow]) + useEffect(() => { fetchWinnings() }, [fetchWinnings]) - return ( -
-
-

Winnings

-
-
- Payments}> - { - console.log(key, value) - }} - onResetFilters={() => { - console.log('reset') - }} - /> - {/* */} - {isLoading && } - {!isLoading && ( - { - if (paymentIds[key]) { - winningIds.push(key) - } - }) + const processPayouts = async (paymentIds: PaymentId): Promise => { + const winningIds: string[] = [] + Object.keys(paymentIds) + .forEach((key: string) => { + if (paymentIds[key]) { + winningIds.push(key) + } + }) - toast.info('Processing payments...', { - position: toast.POSITION.BOTTOM_RIGHT, - }) - try { - await processPayments(winningIds) - toast.success('Payments processed successfully!', { - position: toast.POSITION.BOTTOM_RIGHT, - }) - } catch (error) { - let message = 'Failed to process payments. Please try again later.' + toast.info('Processing payments...', { + position: toast.POSITION.BOTTOM_RIGHT, + }) + try { + await processPayments(winningIds) + toast.success('Payments processed successfully!', { + position: toast.POSITION.BOTTOM_RIGHT, + }) + } catch (error) { + let message = 'Failed to process payments. Please try again later.' - if (error instanceof AxiosError) { - message = error.response?.data?.error?.message + if (error instanceof AxiosError) { + message = error.response?.data?.error?.message - message = message.charAt(0) - .toUpperCase() + message.slice(1) - } + message = message.charAt(0) + .toUpperCase() + message.slice(1) + } - toast.error(message, { - position: toast.POSITION.BOTTOM_RIGHT, - }) - } + toast.error(message, { + position: toast.POSITION.BOTTOM_RIGHT, + }) + } - fetchWinnings() + fetchWinnings() + } + + return ( + <> +
+
+

Winnings

+
+
+ Payments}> + { + console.log(key, value) + }} + onResetFilters={() => { + console.log('reset') }} /> - )} - + {/* */} + {isLoading && } + {!isLoading && ( + processPayouts(paymentIds), + content: `You are about to process payments for a total of USD ${totalAmount}`, + title: 'Are you sure?', + }) + }} + /> + )} + +
-
+ {confirmFlow && ( + +
{renderConfirmModalContent}
+
+ )} + ) } diff --git a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx index 0a4fad84c..c65f09406 100644 --- a/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx +++ b/src/apps/wallet/src/lib/components/payment-provider-card/PaymentProviderCard.tsx @@ -127,7 +127,8 @@ const PaymentProviderCard: React.FC = (props: PaymentProvi secondary variant='danger' size='lg' - label='Cancel Connection' + label={`${props.provider.status === 'CONNECTED' + ? 'Delete Connection' : 'Cancel Connection'}`} iconToRight icon={IconOutline.TrashIcon} onClick={function onCancelConnectonClick() { diff --git a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx index 03b3894b5..2ec4435bb 100644 --- a/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx +++ b/src/apps/wallet/src/lib/components/payments-table/PaymentTable.tsx @@ -10,7 +10,7 @@ import styles from './PaymentTable.module.scss' interface PaymentTableProps { payments: ReadonlyArray - onPayMeClick: (paymentIds: { [paymentId: string]: boolean }) => void + onPayMeClick: (paymentIds: { [paymentId: string]: boolean }, totalAmount: string) => void } const PaymentsTable: React.FC = (props: PaymentTableProps) => { const [selectedPayments, setSelectedPayments] = useState<{ [paymentId: string]: boolean }>({}) @@ -113,7 +113,7 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) =>
diff --git a/src/apps/wallet/src/lib/services/wallet.ts b/src/apps/wallet/src/lib/services/wallet.ts index 1fdf0d8d8..14f6f5928 100644 --- a/src/apps/wallet/src/lib/services/wallet.ts +++ b/src/apps/wallet/src/lib/services/wallet.ts @@ -42,11 +42,22 @@ export async function getUserTaxFormDetails(): Promise { return response.data } -export async function getPayments(userId: string): Promise { +export async function getPayments(userId: string, filters: Record): Promise { + const filteredFilters: Record = {} + + for (const key in filters) { + if (filters[key].length > 0) { + filteredFilters[key] = filters[key][0] + } + } + const body = JSON.stringify({ winnerId: userId, + ...filteredFilters, }) + console.log('Ignoring filters for now', filteredFilters) + const url = `${baseUrl}/user/winnings` const response = await xhrPostAsync>(url, body) From 663c8327e551302f7095f47d399f45a3d0af7d31 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 1 Feb 2024 01:07:58 +0600 Subject: [PATCH 41/48] feat(winnings): add pagination Signed-off-by: Rakib Ansary --- .../src/home/tabs/winnings/WinningsTab.tsx | 42 ++++++++++++- .../payments-table/PaymentTable.module.scss | 16 +++++ .../payments-table/PaymentTable.tsx | 61 ++++++++++++++++++- .../wallet/src/lib/models/PaginationInfo.ts | 6 ++ src/apps/wallet/src/lib/services/wallet.ts | 16 +++-- 5 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 src/apps/wallet/src/lib/models/PaginationInfo.ts diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index a39208800..52742f71e 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -11,6 +11,7 @@ import { getPayments, processPayments } from '../../../lib/services/wallet' import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' import { FilterBar } from '../../../lib' import { ConfirmFlowData } from '../../../lib/models/ConfirmFlowData' +import { PaginationInfo } from '../../../lib/models/PaginationInfo' import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' import styles from './Winnings.module.scss' @@ -74,6 +75,12 @@ const ListView: FC = (props: ListViewProps) => { const [winnings, setWinnings] = React.useState>([]) const [isLoading, setIsLoading] = React.useState(false) const [filters, setFilters] = React.useState>({}) + const [pagination, setPagination] = React.useState({ + currentPage: 1, + pageSize: 10, + totalItems: 0, + totalPages: 0, + }) const convertToWinnings = useCallback( (payments: WinningDetail[]) => payments.map(payment => ({ @@ -96,15 +103,16 @@ const ListView: FC = (props: ListViewProps) => { const fetchWinnings = useCallback(async () => { setIsLoading(true) try { - const payments = await getPayments(props.profile.userId.toString(), filters) - const winningsData = convertToWinnings(payments) + const payments = await getPayments(props.profile.userId.toString(), pagination.pageSize, (pagination.currentPage - 1) * pagination.pageSize, filters) + const winningsData = convertToWinnings(payments.winnings) setWinnings(winningsData) + setPagination(payments.pagination) } catch (apiError) { console.error('Failed to fetch winnings:', apiError) } finally { setIsLoading(false) } - }, [props.profile.userId, convertToWinnings, filters]) + }, [props.profile.userId, convertToWinnings, filters, pagination.currentPage, pagination.pageSize]) const renderConfirmModalContent = React.useMemo(() => { if (confirmFlow?.content === undefined) { @@ -243,7 +251,35 @@ const ListView: FC = (props: ListViewProps) => { {isLoading && } {!isLoading && winnings.length > 0 && ( onPayMeClick: (paymentIds: { [paymentId: string]: boolean }, totalAmount: string) => void + currentPage: number + numPages: number + onNextPageClick: () => void + onPreviousPageClick: () => void + onPageClick: (pageNumber: number) => void } const PaymentsTable: React.FC = (props: PaymentTableProps) => { const [selectedPayments, setSelectedPayments] = useState<{ [paymentId: string]: boolean }>({}) @@ -52,8 +57,6 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) => const total = calculateTotal() - console.log('All payments', props.payments) - return ( <>
@@ -105,6 +108,58 @@ const PaymentsTable: React.FC = (props: PaymentTableProps) =>
+ + {props.numPages > 1 && ( + <> + +
+
+ {props.currentPage < props.numPages - 2 && ...} +
+ + )} +
Total: $ diff --git a/src/apps/wallet/src/lib/models/PaginationInfo.ts b/src/apps/wallet/src/lib/models/PaginationInfo.ts new file mode 100644 index 000000000..0d2d1e030 --- /dev/null +++ b/src/apps/wallet/src/lib/models/PaginationInfo.ts @@ -0,0 +1,6 @@ +export interface PaginationInfo { + totalItems: number; + totalPages: number; + pageSize: number; + currentPage: number; +} diff --git a/src/apps/wallet/src/lib/services/wallet.ts b/src/apps/wallet/src/lib/services/wallet.ts index 14f6f5928..0ccaadb79 100644 --- a/src/apps/wallet/src/lib/services/wallet.ts +++ b/src/apps/wallet/src/lib/services/wallet.ts @@ -9,6 +9,7 @@ import { WinningDetail } from '../models/WinningDetail' import { TaxForm } from '../models/TaxForm' import { OtpVerificationResponse } from '../models/OtpVerificationResponse' import { TransactionResponse } from '../models/TransactionId' +import { PaginationInfo } from '../models/PaginationInfo' import ApiResponse from '../models/ApiResponse' const baseUrl = `${EnvironmentConfig.API.V5}/payments` @@ -42,7 +43,11 @@ export async function getUserTaxFormDetails(): Promise { return response.data } -export async function getPayments(userId: string, filters: Record): Promise { +// eslint-disable-next-line max-len +export async function getPayments(userId: string, limit: number, offset: number, filters: Record): Promise<{ + winnings: WinningDetail[], + pagination: PaginationInfo +}> { const filteredFilters: Record = {} for (const key in filters) { @@ -52,14 +57,17 @@ export async function getPayments(userId: string, filters: Record>(url, body) + const response = await xhrPostAsync>(url, body) if (response.status === 'error') { throw new Error('Error fetching payments') From cd59b6dcc9d7c25070c13309364dd383d2b9b3a0 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 1 Feb 2024 09:55:00 +0600 Subject: [PATCH 42/48] fix(winnings): handle null search results Signed-off-by: Rakib Ansary --- src/apps/wallet/src/lib/services/wallet.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/apps/wallet/src/lib/services/wallet.ts b/src/apps/wallet/src/lib/services/wallet.ts index 0ccaadb79..d70833e01 100644 --- a/src/apps/wallet/src/lib/services/wallet.ts +++ b/src/apps/wallet/src/lib/services/wallet.ts @@ -73,6 +73,10 @@ export async function getPayments(userId: string, limit: number, offset: number, throw new Error('Error fetching payments') } + if (response.data.winnings === null || response.data.winnings === undefined) { + response.data.winnings = [] + } + return response.data } From 4f9add39067f52308136070c042b5fe577d5d35d Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 1 Feb 2024 10:16:24 +0600 Subject: [PATCH 43/48] fix(winnings): reset page when filters are changed Signed-off-by: Rakib Ansary --- src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx index 52742f71e..78e809675 100644 --- a/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx +++ b/src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx @@ -238,12 +238,20 @@ const ListView: FC = (props: ListViewProps) => { }, ]} onFilterChange={(key: string, value: string[]) => { + setPagination({ + ...pagination, + currentPage: 1, + }) setFilters({ ...filters, [key]: value, }) }} onResetFilters={() => { + setPagination({ + ...pagination, + currentPage: 1, + }) setFilters({}) }} /> From d7bd18b4f153b31c7d62ce745009e0e10db66ebc Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 1 Feb 2024 14:09:45 +0600 Subject: [PATCH 44/48] fix: text corrections Signed-off-by: Rakib Ansary --- src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx | 2 +- .../wallet/src/lib/components/tax-form-card/TaxFormCard.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx index b0f706140..351f328e7 100644 --- a/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx +++ b/src/apps/wallet/src/home/tabs/tax-forms/TaxFormsTab.tsx @@ -89,7 +89,7 @@ const TAX_FORM_DETAILS = [ instructionsLink: 'https://www.irs.gov/pub/irs-pdf/iw8ben.pdf', reasonDescription: // eslint-disable-next-line max-len - 'Under current IRS guidance, foreign persons performing services outside of the U.S. are not subject to income tax withholding. However, Topcoder requires all such members to provide a properly filled out W-8BEN prior to issuing payment. In addition, prize money paid to foreign persons who are not performing services (such as winning an SRM competition) is subject to withholding taxes.', + 'Under current IRS guidance, foreign persons performing services outside of the U.S. are not subject to income tax withholding. However, Topcoder requires all such members to provide a properly filled out W-8BEN-E prior to issuing payment. In addition, prize money paid to foreign persons who are not performing services (such as winning an SRM competition) is subject to withholding taxes.', reasonTitle: 'Why do I need to complete Form W-8BEN-E?', }, ] diff --git a/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx b/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx index 855021793..3a8892142 100644 --- a/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx +++ b/src/apps/wallet/src/lib/components/tax-form-card/TaxFormCard.tsx @@ -72,7 +72,7 @@ const TaxFormCard: React.FC = (props: TaxFormCardProps) => ( label={props.instructionsLabel} // eslint-disable-next-line react/jsx-no-bind onClick={() => { - window.location.href = props.instructionsLink + window.open(props.instructionsLink, '_blank') }} />
- {props.numPages > 1 && ( - <> - -
-
- {props.currentPage < props.numPages - 2 && ...} -
- - )} -
Total: $ {total.toFixed(2)}
+ {props.numPages > 1 && ( + <> +
+
+ {props.currentPage < props.numPages - 2 && ...} +
+ + )}
- ) - } - - const columns: Column[] = [ - { - accessor: 'userId', - Header: 'User ID', - }, - { - accessor: 'name', - Header: 'Selected Payment Provider', - }, - { - accessor: 'status', - Header: 'Status', - }, - { - accessor: 'actions', - // eslint-disable-next-line @typescript-eslint/typedef - Cell: ({ row }) => (renderActions(row)), - Header: 'Actions', - }, - ] - - return ( - <> -
-
-

Payment Provider

-
-
- { - setUserIdsFilter(value) - }} - onResetFilters={() => { - setUserIdsFilter([]) - }} - /> -
- {isLoading && } - {!isLoading && ( - - )} -
-
- {confirmFlow && ( - -
{renderConfirmModalContent}
-
- )} - - ) -} - -export default PaymentsTab diff --git a/src/apps/wallet-admin/src/home/tabs/payments/index.ts b/src/apps/wallet-admin/src/home/tabs/payments/index.ts deleted file mode 100644 index 8dc41bc69..000000000 --- a/src/apps/wallet-admin/src/home/tabs/payments/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as PaymentsTab } from './PaymentsTab' diff --git a/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss b/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss deleted file mode 100644 index a054e0db6..000000000 --- a/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.module.scss +++ /dev/null @@ -1,32 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.container { - background-color: $black-5; - padding: $sp-6; - margin: $sp-4 0; - border-radius: 6px; - - @include ltelg { - padding: $sp-4; - } - - .header { - display: flex; - justify-content: flex-start; - gap: 5px; - align-items: center; - - @include ltelg { - flex-direction: column; - } - } - - .content { - border-radius: 4px; - margin-top: $sp-4; - - .actionButtons { - display: flex; - } - } -} diff --git a/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx b/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx deleted file mode 100644 index 4461c87ae..000000000 --- a/src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx +++ /dev/null @@ -1,221 +0,0 @@ -/* eslint-disable @typescript-eslint/typedef */ -/* eslint-disable react/no-unused-prop-types */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable max-len */ -/* eslint-disable react/jsx-no-bind */ -import { toast } from 'react-toastify' -import React, { FC, useCallback, useMemo, useState } from 'react' - -import { Button, ConfirmModal, LoadingCircles } from '~/libs/ui' -import { UserProfile } from '~/libs/core' -import { downloadBlob } from '~/libs/shared' - -import { FilterBar } from '../../../lib' -import { deleteTaxForm, fetchTaxFormDetail, fetchTaxForms } from '../../../lib/services/wallet' -import { ConfirmFlowData } from '../../../lib/models/ConfirmFlowData' -import { Column } from '../../../lib/models/Column' -import { GenericTable } from '../../../lib/components/generic-table' - -import styles from './TaxFormsTab.module.scss' - -function formatIOSDateString(iosDateString: string): string { - const date = new Date(iosDateString) - - if (Number.isNaN(date.getTime())) { - return 'N/A' - } - - const options: Intl.DateTimeFormatOptions = { - day: 'numeric', - hour: '2-digit', - hour12: true, - minute: '2-digit', - month: 'long', - second: '2-digit', - year: 'numeric', - } - - return date.toLocaleDateString(undefined, options) -} - -interface TaxFormsTabProps { - profile: UserProfile -} - -const TaxFormsTab: FC = (_props: TaxFormsTabProps) => { - const [confirmFlow, setConfirmFlow] = React.useState(undefined) - - const [isLoading, setIsLoading] = React.useState(false) - const [currentPage, setCurrentPage] = useState(1) - const [pageSize, setPageSize] = useState(50) - const [userIdsFilter, setUserIdsFilter] = useState([]) - - const [taxForms, setTaxForms] = useState([]) - - const renderConfirmModalContent = useMemo(() => { - if (confirmFlow?.content === undefined) { - return undefined - } - - if (typeof confirmFlow?.content === 'function') { - return confirmFlow?.content() - } - - return confirmFlow?.content - }, [confirmFlow]) - - const loadTaxForms = useCallback(async () => { - setIsLoading(true) - try { - const response = await fetchTaxForms(currentPage, pageSize, userIdsFilter) - setTaxForms(response.forms.map((data: { - id: string, - taxForm: { - name: string, - }, - status: string, - dateFiled: string, - withholdingAmount: string, - withholdingPercentage: string, - userId: string, - }) => ({ - dateFiled: formatIOSDateString(data.dateFiled), - id: data.id, - name: data.taxForm.name, - status: data.status, - userId: data.userId, - witholdingAmount: data.withholdingAmount, - witholdingPercentage: data.withholdingPercentage, - }))) - } catch (apiError) { - toast.error('Something went wrong fetching tax forms. Please try again later.') - } - - setIsLoading(false) - }, [userIdsFilter]) - - React.useEffect(() => { - loadTaxForms() - }, [loadTaxForms]) - - function renderActions(row: any): JSX.Element { - return ( -
-
- ) - } - - const columns: Column[] = [ - { - accessor: 'userId', - Header: 'User ID', - }, - { - accessor: 'name', - Header: 'Filed TaxForm', - }, - { - accessor: 'dateFiled', - Header: 'Date Filed', - }, - { - accessor: 'status', - Header: 'Status', - }, - { - accessor: 'witholdingAmount', - Header: 'Witholding Amount', - }, - { - accessor: 'witholdingPercentage', - Header: 'Witholding Percentage', - }, - { - accessor: 'actions', - Cell: ({ row }) => (renderActions(row)), - Header: 'Actions', - }, - ] - - return ( - <> -
-
-

Tax Forms

-
-
- { - console.log(key, value) - }} - onResetFilters={() => { - console.log('reset') - }} - /> -
- {isLoading && } - {!isLoading && ( - - )} -
-
- {confirmFlow && ( - -
{renderConfirmModalContent}
-
- )} - - ) -} - -export default TaxFormsTab diff --git a/src/apps/wallet-admin/src/home/tabs/tax-forms/index.ts b/src/apps/wallet-admin/src/home/tabs/tax-forms/index.ts deleted file mode 100644 index 08eb9c245..000000000 --- a/src/apps/wallet-admin/src/home/tabs/tax-forms/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as TaxFormsTab } from './TaxFormsTab' diff --git a/src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss b/src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss deleted file mode 100644 index a054e0db6..000000000 --- a/src/apps/wallet-admin/src/home/tabs/winnings/Winnings.module.scss +++ /dev/null @@ -1,32 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.container { - background-color: $black-5; - padding: $sp-6; - margin: $sp-4 0; - border-radius: 6px; - - @include ltelg { - padding: $sp-4; - } - - .header { - display: flex; - justify-content: flex-start; - gap: 5px; - align-items: center; - - @include ltelg { - flex-direction: column; - } - } - - .content { - border-radius: 4px; - margin-top: $sp-4; - - .actionButtons { - display: flex; - } - } -} diff --git a/src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx b/src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx deleted file mode 100644 index 562b5174d..000000000 --- a/src/apps/wallet-admin/src/home/tabs/winnings/WinningsTab.tsx +++ /dev/null @@ -1,304 +0,0 @@ -/* eslint-disable react/no-unused-prop-types */ -/* eslint-disable max-len */ -/* eslint-disable sort-keys */ -/* eslint-disable react/jsx-no-bind */ -/* eslint-disable @typescript-eslint/typedef */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { toast } from 'react-toastify' -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react' - -import { Button, ConfirmModal, LoadingCircles } from '~/libs/ui' -import { UserProfile } from '~/libs/core' - -import { editWinningRecord, searchWinnings } from '../../../lib/services/wallet' -import { PaymentDetail, WinningDetail } from '../../../lib/models/WinningDetail' -import { FilterBar } from '../../../lib' -import { ConfirmFlowData } from '../../../lib/models/ConfirmFlowData' -import { Column } from '../../../lib/models/Column' -import { GenericTable } from '../../../lib/components/generic-table' - -import styles from './Winnings.module.scss' - -interface ListViewProps { - profile: UserProfile -} - -function formatIOSDateString(iosDateString: string): string { - const date = new Date(iosDateString) - - if (Number.isNaN(date.getTime())) { - throw new Error('Invalid date string') - } - - const options: Intl.DateTimeFormatOptions = { - day: 'numeric', - hour: '2-digit', - hour12: true, - minute: '2-digit', - month: 'long', - second: '2-digit', - year: 'numeric', - } - - return date.toLocaleDateString(undefined, options) -} - -const ListView: FC = (props: ListViewProps) => { - const [confirmFlow, setConfirmFlow] = React.useState(undefined) - - const [isLoading, setIsLoading] = React.useState(false) - const [currentPage, setCurrentPage] = useState(1) - const [pageSize, setPageSize] = useState(50) - const [externalIdsFilter, setExternalIdsFilter] = useState([]) - const [userIdsFilter, setUserIdsFilter] = useState([]) - - const [winnings, setWinnings] = useState([]) - - const renderConfirmModalContent = useMemo(() => { - if (confirmFlow?.content === undefined) { - return undefined - } - - if (typeof confirmFlow?.content === 'function') { - return confirmFlow?.content() - } - - return confirmFlow?.content - }, [confirmFlow]) - - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - const fetchWinnings = useCallback(async () => { - setIsLoading(true) - try { - const filters = { - externalIds: externalIdsFilter, - winnerId: userIdsFilter.length ? userIdsFilter[0] : undefined, - } - - const payments = await searchWinnings(currentPage, pageSize, filters) - console.log('payments', payments) - const tempWinnings = convertToWinnings(payments as WinningDetail[]) - console.log('TempWinnings', tempWinnings) - setWinnings(tempWinnings) - } catch (apiError) {} - - setIsLoading(false) - }, [currentPage, pageSize, externalIdsFilter, userIdsFilter]) - - useEffect(() => { - fetchWinnings() - }, [fetchWinnings]) - - function convertToWinnings(payments: WinningDetail[]): any[] { - let tempWinnings: any[] = [] - - payments.forEach((payment: WinningDetail) => { - const temp = payment.details.map((detail: PaymentDetail) => { - const winning: any = { - origin: payment.origin, - userId: payment.winnerId, - externalId: payment.externalId, - createDate: formatIOSDateString(payment.createdAt), - currency: payment.details[0].currency, - // eslint-disable-next-line max-len - datePaid: payment.datePaid !== undefined && payment.datePaid.length ? formatIOSDateString(payment.datePaid) : '-', - description: payment.description, - details: payment.details, - id: payment.id, - paymentId: detail.id, - netPayment: `${new Intl.NumberFormat('en-US', { - currency: detail.currency, - maximumFractionDigits: 2, - minimumFractionDigits: 2, - style: 'currency', - }) - .format( - Number(detail.netAmount), - )}`, - installmentNumber: detail.installmentNumber, - releaseDate: formatIOSDateString(payment.releaseDate), - status: payment.details[0].status, - type: payment.category.replaceAll('_', ' '), - } - - return winning - }) - - tempWinnings = tempWinnings.concat(temp) - }) - - return tempWinnings - } - - function renderActions(row: any): JSX.Element { - return ( -
-
- ) - } - - const columns: Column[] = [ - { - accessor: 'origin', - Header: 'Origin', - }, - { - accessor: 'userId', - Header: 'User ID', - }, - { - accessor: 'externalId', - Header: 'External ID', - }, - { - accessor: 'type', - Header: 'Winning Type', - }, - { - accessor: 'description', - Header: 'Description', - }, - { - accessor: 'status', - Header: 'Status', - }, - { - accessor: 'installmentNumber', - Header: 'Installment', - }, - { - accessor: 'releaseDate', - Header: 'Release Date', - }, - { - accessor: 'netPayment', - Header: 'Amount', - }, - { - accessor: 'actions', - Cell: ({ row }) => (renderActions(row)), - Header: 'Actions', - }, - ] - - return ( - <> -
-
-

Tax Forms

-
-
- { - console.log(key, value) - - if (key === 'externalId') { - setExternalIdsFilter(value) - } else if (key === 'userId') { - setUserIdsFilter(value) - } - }} - onResetFilters={() => { - console.log('reset') - }} - /> -
- {isLoading && } - {!isLoading && ( - - )} -
-
- {confirmFlow && ( - -
{renderConfirmModalContent}
-
- )} - - ) -} - -export default ListView diff --git a/src/apps/wallet-admin/src/home/tabs/winnings/index.ts b/src/apps/wallet-admin/src/home/tabs/winnings/index.ts deleted file mode 100644 index 615080cbb..000000000 --- a/src/apps/wallet-admin/src/home/tabs/winnings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as WinningsTab } from './WinningsTab' diff --git a/src/apps/wallet-admin/src/index.ts b/src/apps/wallet-admin/src/index.ts deleted file mode 100644 index 0c0a6b9ba..000000000 --- a/src/apps/wallet-admin/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { walletAdminRoutes, rootRoute as walletRootRoute } from './wallet-admin.routes' diff --git a/src/apps/wallet-admin/src/lib/assets/home/banner-image.svg b/src/apps/wallet-admin/src/lib/assets/home/banner-image.svg deleted file mode 100644 index 91a984724..000000000 --- a/src/apps/wallet-admin/src/lib/assets/home/banner-image.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/home/banner-text.svg b/src/apps/wallet-admin/src/lib/assets/home/banner-text.svg deleted file mode 100644 index 3953d78eb..000000000 --- a/src/apps/wallet-admin/src/lib/assets/home/banner-text.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/home/index.ts b/src/apps/wallet-admin/src/lib/assets/home/index.ts deleted file mode 100644 index 5e4f6eac1..000000000 --- a/src/apps/wallet-admin/src/lib/assets/home/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ReactComponent as BannerText } from './banner-text.svg' -import { ReactComponent as BannerImage } from './banner-image.svg' - -export { BannerText, BannerImage } diff --git a/src/apps/wallet-admin/src/lib/assets/index.ts b/src/apps/wallet-admin/src/lib/assets/index.ts deleted file mode 100644 index 3dc6095bc..000000000 --- a/src/apps/wallet-admin/src/lib/assets/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './security' -export * from './preferences' -export * from './payments' -export * from './tools' -export * from './tcandyou' diff --git a/src/apps/wallet-admin/src/lib/assets/payments/PayPal_logo_gray.svg b/src/apps/wallet-admin/src/lib/assets/payments/PayPal_logo_gray.svg deleted file mode 100644 index 8ed6edcec..000000000 --- a/src/apps/wallet-admin/src/lib/assets/payments/PayPal_logo_gray.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_log_gray.svg b/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_log_gray.svg deleted file mode 100644 index 13a13fa00..000000000 --- a/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_log_gray.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_logo_color.svg b/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_logo_color.svg deleted file mode 100644 index 866975133..000000000 --- a/src/apps/wallet-admin/src/lib/assets/payments/Payoneer_logo_color.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/payments/Paypal_logo_color.svg b/src/apps/wallet-admin/src/lib/assets/payments/Paypal_logo_color.svg deleted file mode 100644 index 957a29773..000000000 --- a/src/apps/wallet-admin/src/lib/assets/payments/Paypal_logo_color.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/payments/Western_Union_Logo_gray.svg b/src/apps/wallet-admin/src/lib/assets/payments/Western_Union_Logo_gray.svg deleted file mode 100644 index 8cb8fcaf8..000000000 --- a/src/apps/wallet-admin/src/lib/assets/payments/Western_Union_Logo_gray.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/payments/icon-dollar.svg b/src/apps/wallet-admin/src/lib/assets/payments/icon-dollar.svg deleted file mode 100644 index ac96c7b15..000000000 --- a/src/apps/wallet-admin/src/lib/assets/payments/icon-dollar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/wallet-admin/src/lib/assets/payments/icon-speed.svg b/src/apps/wallet-admin/src/lib/assets/payments/icon-speed.svg deleted file mode 100644 index 6d782e8fa..000000000 --- a/src/apps/wallet-admin/src/lib/assets/payments/icon-speed.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/wallet-admin/src/lib/assets/payments/icon-world.svg b/src/apps/wallet-admin/src/lib/assets/payments/icon-world.svg deleted file mode 100644 index a1d05cdfe..000000000 --- a/src/apps/wallet-admin/src/lib/assets/payments/icon-world.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/wallet-admin/src/lib/assets/payments/index.ts b/src/apps/wallet-admin/src/lib/assets/payments/index.ts deleted file mode 100644 index cd439a908..000000000 --- a/src/apps/wallet-admin/src/lib/assets/payments/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ReactComponent as PayoneerLogo } from './Payoneer_logo_color.svg' -import { ReactComponent as PayPalLogo } from './Paypal_logo_color.svg' -import { ReactComponent as WesternUnionLogo } from './Western_Union_Logo_gray.svg' -import { ReactComponent as IconDollar } from './icon-dollar.svg' -import { ReactComponent as IconWorld } from './icon-world.svg' -import { ReactComponent as IconSpeed } from './icon-speed.svg' - -export { IconDollar, IconWorld, IconSpeed, PayoneerLogo, PayPalLogo, WesternUnionLogo } diff --git a/src/apps/wallet-admin/src/lib/assets/preferences/email.svg b/src/apps/wallet-admin/src/lib/assets/preferences/email.svg deleted file mode 100755 index 84a793d3b..000000000 --- a/src/apps/wallet-admin/src/lib/assets/preferences/email.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/preferences/forum.svg b/src/apps/wallet-admin/src/lib/assets/preferences/forum.svg deleted file mode 100755 index 92e12622e..000000000 --- a/src/apps/wallet-admin/src/lib/assets/preferences/forum.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/preferences/index.ts b/src/apps/wallet-admin/src/lib/assets/preferences/index.ts deleted file mode 100644 index bf54f01b2..000000000 --- a/src/apps/wallet-admin/src/lib/assets/preferences/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ReactComponent as ForumIcon } from './forum.svg' -import { ReactComponent as EmailIcon } from './email.svg' - -export { - EmailIcon, - ForumIcon, -} diff --git a/src/apps/wallet-admin/src/lib/assets/security/apple-store.svg b/src/apps/wallet-admin/src/lib/assets/security/apple-store.svg deleted file mode 100644 index e0ab6869e..000000000 --- a/src/apps/wallet-admin/src/lib/assets/security/apple-store.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/security/credential.png b/src/apps/wallet-admin/src/lib/assets/security/credential.png deleted file mode 100644 index 914e8d04f84699f81b11476072af3b1788b1750e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47241 zcmYg%1xy@F7i}p}iWF&~6k4RXQ@psl%i``%afc!;?kp77MV5udU5Yytmqm&!x;QKj zuiwkd%YTxaIWx&jCO4DZJ7?}hsj0}m!6Lp8JazxV`@w%AiY`<%<{1>bEarD*BVlcO(q~ijS`cSo%c_Z{uaRJB3t?L5^XZ4FB<5N<#AF4 zZgUp}pUK{-v&mX1$U8TNJ`{wse!@&lFZLUnJ%eJH#OwEslFABCbs9M&fg1z=TbiRR z(}SnoF9I+!H><&@I67Ad`2sI~8p#XyD={W5fAV{D0mxzHsZ@owdWycyhg`vm{obP9 zIv1~(Z33pksEcP+zT#7C&02LlaEJPNE-@%zJ|>@Y1=)++$uO-ZiN&`fqk6xn^zuGpu{TNn&x2PugQ?y!6`LajPk;#Ss4vkcAJV_ zNmSJD^+{U$A0LQrse^4lSo7dY2%~Qj7qB{8 zS@(*G3y^M@Vr2W6mX5i}E$>4X3n61O9-n3z^g&9oy>a^x6qGAviR$PXIClg|x! zv_itd8QI}VR{^%B_&%3>+1stJNJ)nVB5`PvotnvC6&YhFW%_*gvzy1uW9g!DsDEGO zeAlDMuf>`O__tdTsUg2xrGvHJ8t!EL6ADT3BCKL#GQQ~IRy{xt?IvxNmYIiqn7FBdZ|5E+#Mg%|m{g zJhk^dHX;G7askbgNG?ZcUXhDmC`0KWI9WqpV@=mx{U+p5@{vyX ztCe>b*m5)9Ir-H)3+`@Mg9G)(VTgbnRjQOTzA+BE#M6AZt&j%Tdxi3k%3;gP&))my zwfW&h&#h$!=~eG=gp2H4ook7xNaV?we)>@8zcaRLb{cZ7jCq1Q=c_C%0<7ZDHa))* zUTbu>PP8df7gX-8{5u9TUpH0V=I-TfxdVi0hB0-Y6K7SL-w;t3tSktK#}W?h$~6(?Jh z(9qxF|HBuS|0Q2GO;=mz(b3S_AMgp~#Qx!T_5YIJk<>yzi14e;C>avJ{|B?r|4RsD zeXy7*O?$=EC{g}Huf+cn>iAPaZL!}!L-NI5{)eAG{+Gyn+F*oy|NaKmp;%1gI4?db zW@T>fLtqEP>I+v1}5jvh>{bAqeTcDK6!I=i&O8{h{=-3`+LpJB>o9;1pH-rj!=P`n=ZaMZ1`xWfw_n@bux&5vCwE3cBY4nB)zl)qKb2OcnKBM69$=kU%fHMB=8 zA;s0@TG}X{vjv7pcpHXKGjq|Pp2An*$J5coNsi048QJYKY-r*$(W~MA|?pltRqhZY>c-8 ziVvn*jM*pn+s!1%J+hp z+=f__DOw`Ol0L1bl%6Ly)>|gq=-`2At_z-(2r>HmZC(3kL`TBLYAq9ah!mV8ml8qs zOz6xueuO1DJxpnpmaLV{B+HqXjMV3;q(S={slm{1N*kKnIrXP*Ell#c(`tfR_$7BZ zuYEW+$|N3z`@epPqqsU!%5?4b(HZnaRnJagvdxZ`G<;rIw0an3Csm8>XCh=br!EAa z^b(OuG9D)3EUGO z>IM&m6Vg{GI2G1nucqnziXGAr0?C2vC7=yE9;+}5Doum^(atGj+jD{!Q7>;cbt~XRGrcEd3R!t+Tr5@QiAF zNN8CB*9f~6xEP4xS;m4^@lXmFfJ{#?so*ASTQH-~MG1rqaVwX4kg07el)5iy{A99` z@ig0a&WXw9O62XPqEcmi_L#3%NCA*&kzhfEEIo6BA#O>0r@U^tofD&v!sZlEgC(a+#siOvLJo=Q<>A z=M`mfjM%<1j9gzcxnE=&-V$nGa~+(L02@k64eS_2N#F@W1-71e{cm$d)$^)U=f1ao zMC<8)e55|T2N_e zptM&jUunb(s)X^{AJz2k99f)T z#}wI&xR1bw#? zm*h8=>(;oJ69yzqkCm%nH(D;ewM&`ee8HIptyg4ZRa>b&YMryp;w)g<3^pFoeive% z<+etHZju;0T%vUF&to%<9#aZLpt)#Wf_;?+lkO7cwY@2HgLwkRXa%#D28Q4n`m1FX zSp8D0yhbDtsJ(ZfMYheYXR97q4`vw~dHYwuo3$`k4?7_2WqCq3&-_;zFofGdW2Vy< zOR8=@nzact#n183Y0y9>yWV5ToG~(c)6|Ao`j{Dks7q){Squ6eN4jo_Cv=*B)N=A3 z88z9_9WP={alZ@T1FO&G8Btuq#indSwe+k7PeevKeTVhVnU^K6MJw=q`Sd3E><;ve zZ7l}Uxp%lr;^hun5`ZudEeW_Db zHYdWBqy!)HXi}EXa-`!aV7ijLLC_?A$;h{#0jW5=4fz!KM;m$No}d9zL!XXGjy%Md zH*r5i^;&XqZdhVIr}~xs2=_m!8SmR4lknG~ch;keQL3Zg^;j$~4Ib!xxV>IBZgX*o zNz*}CUm|T*7q?S%gl78t; z?62UpE3CTg9pqXhe5aHS)ELh!Cq|q;@-<>VouD1=vo6Edy=z#Fr>#tG5rPPyzq1*0 z>zGKuu_{rgrBO0qg1r%%;}0RLd3HwLK+us@36Dd5iWU%MD-qFV0EGB6f&} z^=;K+t(A1!rT4;s2*4^E%Qb-&^%Kjdr4!8F5cK|{c*tG5f%qS)GU!9 zt7h06fctTkf)%(lqf&F0$)!39`llaQ(4 z(U)Pi+xZBP+?OCgoUVB0)#ZD2*b<>%2P7yQMPmW zu`$e1BO>RiU}3oeZ?RXOwwQ*tvAAyL>GB$OWFy)J(?-TO?gXUgcq0kCZVF&oH|rvg z`|C@=!7Tuk!1Ea^5p($Fpz+?5}NwhoL@pxs5i5`VLzRI_aISx2?{N{*474p(BH zI+NzlAcmsLrvv9VA-v9L8LvjP@MaZ@qS-L3SYFt4#*E1i^QcG-+-jq!1~Nw@L#_j0+H4;|m}G7k^HTMJ-!~(V zf!zBRAo||u$S?UZG5Aby$T4*oh(lSw+~7lbW%UY`s1* z1+m#(+NvX7MC_&uFvG{8OE5N(XG=Yd(aQ4r=>bc{c}Js0^INwp9-A6rPXDCw0P84W z4*!ht3>aG!twV|)KmWP|Yy=^=n<@z@x5))z8#xbGuxeKGX2gB6dRcy8>A5|Nnflaj z^L~+QlC5zfm(3sk1Y5f9@&c1JHog9no8Gcyv82?boC@dEL(3t)1*Wdoj7$>7{Z*C$ zBE~dLoRopPR$(4`F-!rFb50JOT5UZQU&c`FjP$`SYlHjk!T6^Aj}6OQldRPSPwsib zqAmr>pHdp@)-MDEcTRJ7ED|%o?X@}^NS^JLPJ9I^&+7a6EvIy4nWa6C^rPn64=~pX zjx<)bDZ{R^+EUhqhM_f&K4!$T$b$^baRANVc}=73>R1m9lWrWY=e=)F{Tp)c#6%0I zm(4XcO%S;X6@FR_U>=_Rkr)H6D76`HD9tl=R2}m4AevK={+ON)NKaQrDI$b9E#KhY z&;KZ0Ov32uX(ie%pwDZf&;7;zBeg+u-S1c64ul%i{N6y?cN8e-l+DsqzU7BNJrNmN zK96B3^-4U}XuDjropbmJ>L@&5tLqW*Uo$wW4r}zbz70MJm_6>i<`}Q_J30+(tcwu| zuH778aCE3y7F-)eeK}9I(nn2u7-%1J)C!}^e8JwdKa=J3jM~_ceAkwBRl0#0w-vBl zJC0~kkWYm{w8r$)qM!pn16(lDA`Q5rz9pJ-RGYHypBhIlIMR7hX|kM5OUtY+a5Pr8 zZmRQe5#zP7pxPj5+}046!@#fc6=FK5==9W8RkneQHi)q7TAIkZTWaa0doY@2v!UG*EJ<~2^6C>1@o&iVpgXeDY&ov6OswYH?y zQwM3KoTsD_0c&`#VoMb+@U$hcu5FAD&SeN z?kJGrDL&_7Npw;E2DYCzPje2Mr!Ko7J?71s%bid1Zew#ELZHhA>(^w0S7VBZc#K?4 zO{}D?$4#u4WKNAWZsK?UaMB_dUQf8|E}D>1wyHz_QCGE2!}@4=Lm!|t8#4#Pr2pf z*_*-kWmYR5yKiY$Jc6L^5A(0%$z{yABZC*8i75E*yA-A$6_u^BzJmIVgAtv)HWLB& z>mBU6%$FV;NL;xwgTQ^NR9mVgfno^>K*#j}%QvI5Xnc7BYoO zzg!B8m)#uTlb9aaqq!eS_HdAMM7>LH<5Z-W26(?@VV1Ypdj#Dn=4OgH`YqYmK<1TU z%I})-vAtI+)sI@u{8*lbz|!)@(vVii(l4sNGLhV76ic(RgLC}J^IpkdYMjnv%4^a5 zC(M#P%Fz!rH(EJ9pEW9PR{aLp5bsgPnm)XkWv~7#mNPAh_qg6#kC?(|@*_*$d&b?7 zV@z=Drl5n)#P})jUE1Wb|MWaC*WNuXzV_tt(#09oXA9E;F^6$%aT{Q~p?e>rmb7_M z_d}w^vSa#zpI|LQMR1L--qfiqd*mbYWxZbOqMe`<&p7XzZT8rFtkKi6b8Y;&_B?QP zTELwPk!-SNsblyY$pVI_IorGMf5GM;;K_+nK34+CrYUseF1q+1vf3bYf}0hvnu`y~ zoIGL(Id?inI<9t790yX>sgU)Lm+3mo(bH%KY)#GnwU5Ux*RU{EwpxXt*!phnL&jlH z32fvXjalJVTPt0fap3sU(%~e>kt|wAX&6mA2MpBu2GoR4*bSVwE*#q^lYVV_+7)lx z4MCp=%4Y@De7vr3>;4$o05F=q)uOxj)Ek>pZKd8jpzR$s;T3bVIs8HT~3Y>bel#a zvI#daL`g(c9Imz{tH$0dYseP%_R-8t-|yc;1Om9r#0qhA>Q*sEwD4~WbkBj#4oqH$ zi*}lH+uY2=t_pQs)gCRdy!=Hy`9;?XMz2e8*ph*9U0uf+j{z(Ry4IubWJ+70 z%s>wOnR|7~k|hEfUkfVacC{}^RhQ?T%qnr{(&W5LoRBc8qOEPo#lvpz%{7R zST?%8Ui4KQy=2oFpiK{=BvH30n@XkF$MxUL*hTFBOFd?Xi;j+5MEdkK$m|IPKK)gP z)hfemV-LkT&V$_MHS`QKd2GG@Sde*5hSeQu5 zG@%l_>)@zm-SXry-!J1LzQ^Ur2KHQQ3`ARvwJ2j7a;Fq= z2isZVy7+P?n^f`ryXT8v5vUgWEaS1peXSQ&Sxeh-`jzfh-9e8r!LaP5QHYb)fN|g= z?j}!CRn~1URT9{A{WEJn>a`^|t%_nxb7}Y|0*6$t_t>O?OXU9jxmgZlC5WfC-QnQW z0c+{#ZUpkzCN;@LR37AkwMMk~?mjQ5|FKSd0NSLp1ca%F5iSEOg2>igx4|22}k`4gYdA zzM+mak;j^M2*`|yO^WP=oNIao1*Duap{>xc5~kv_%A6A`c;Y)|teae$ks!2%jE@&~ z6ZOpN&+PD6Dx0#`g2&NMVr6#NOgkOxokhQHu3KbLbf{-Nv8I-VhqnVY`S3JIb$eM( z3SJv7k3`*{0{ z#@^rW{1bTKMVJ{|x_m!%Tk^YlH8VN+b!a`VtkT+=EioMj=!&?>HIduz+*z`mJYV{N zIRJK=r|mu96Kc&ZR)MLnUWGohT}wM6O}f1h^DGw*qiJBt*O-ntdzr!_>Wx^SAE>!i z7M9Z_VqqfiyxTaKE4WlT5uCu&WcH&P%iL?otZiv&H^hE=ItxgN9CCjcDM|LhE0eJok4%gLu6zz3! zNmoD}ks^3&_9mypetjh`i3QAs-{@9xNx5m2%E2yU)u;DLqs-d>q!y^0Hw(s;hI(Gy z6B7L-5$+oQU8T&};YQNp>%g2a)m|_R?@pD~@gvtE2r%*J2SIGLb=Jy?zL?0%6L?&` z*Nby=S}62Ws51tipUtR_U9jW;P)5LlJ*lJm=# z^XMNwkYYOzK+noBT}Jne1usumw~cBJpjK{-2Sj5awZ?W1X%@wjh(MY^xB z%=HrR{f*qpm9P^!BP2|d)O4?-vt~Tp-~U#@6x+Y2On7kZY-g~gkDBHh{`Q;XLLM`o z5XZ2mK^EQbrTQymt;ay_Pi(Dm)l}#DmUXJCU#3l~Ul%$9y5Y!Z;l}Cxu8#j-uu7^*^~LXyGYn#zK3n1{caeRaDD;dW1Jtrx@&A8o1Tuy-eHHxgK=h zc3)X!z~wYPqfR-*799W^RYs2IJ@=-0T`qR}pIEm!bu}b2xB80Bl2FmgDA#RkF(LI! znpb#pIThY8FiWA`jII38W~c}Soei^f{q|CyU8H1}XmCuaWKnITc(}1YM6kiC1YI0p zE*&j^>;ao4u=%JUrMDhc1|*KW5;xhUasBZ?<^K0RF>I3UxyWjc;Nn2Yu~l|byFyYv z!)&gYYxF3x*Sm?=6AfuC;x=)Qh!DMP&+$8uXL5(ek>u2b;VrJ7@3*rWv&`rL`(NFs z%5w(tgbm4?S!3liUd*#*-l{e&P;~y_0qR6YE=B%~EUHbkzr=Q(OB6KMZp&t%P>)PL zs4V4k7)`JEviUS_qokJOsh@J5nG8w;46ZpJRvM-UIj>azQx5*XA;(blETATc}>6EB-oK+?4!jmIH+gtag*c$UPxR4ES@DQoAO7s%WBbKg3$vv$_65D2T+Z&%_v*JE~^=mE{gz>C1&?(r1p{ZtHAmAM{D9Gl3BaH9a-Z)a{W zw;Ugequf%Y_I`EJuCD6@nt_DRwm%un|3KHQ2{XCEDxU?W?dzEpc?67p^s1f=*klkq zTIH-^Cae8K^)j9iJ)RpNkW)wh!))`xXr#EvOR>gg+VZ*M-YlmS0k}VL)Eu~=9tQFX zm{gDRlG_+K)zv^MjoTjAA}S5AA{MfXwFV!UI<)K?`^kzBZKAhrp7P$xc_KiO6P6B% z5?Fj-FS*yPw}`3e(|{eXwN}CvZzlp063baR%H|8`LHzQav$bf?X8!kTMEHh{OSV)vXj zTM(VJcZ!oU;ojPDs>jNCOgTdcu<+Vbi8}vlakxk2hCVu@*dUH>+TsYW7eW_K>vVKP zx+|nI#)MK=--#w1SR@wtj@A84Ek={-IcQ$Q^BC@Rl2do2BigmxdAekxt0P-Sw-kLc zZTb+!eZS2U|B5IwI%2>RXUw{ZNqg7Pw9Tdv(0uH+hP7WapF(D1w8TL*H#J|5#m-ol zd95w)1UhP;(-l1aOM)B-D_P9K%`kNla114DowT*GEw@as%-8cz=t%>(3R5B1pN~}h z5gEiZCP3=9#qYF=6G!$#BlE`ZlgXM#aRm1xSrLl6ol9$v^SwNyY_L3N>kN5tV{i2B zd)MsP!BBzkq!K)=o9|D`aXR{)2^fv})8{FkyqbhB7Xz-)>qV`L&ROdAe$Wbgt&S)B zG4y)ifYx%YyH_7IxDVh8+q(NdCSmY{H2cOz8o})v@l38V+Iq$`UH7o*NK54kZ%ssGgQrkp#T`vcl8&585IxgAZ%T_^gb74*>~lP1wk@|d9AD2%1H_4Yb4 z-`_NXTSm}Y3tZStrq*QB+THhq&o*hA7-}}k6-69kREm^VdUFKYugTty#+sxFy6biO z`3VACl#wveHOr%%f3igRINM#1;8A249ZWsK(bA;-@1sUDNmf_2>4Ncf{xZ*DDrK$j zwYGw`26PPW;GAuDE%X2(0;Jo&)QcAyp11a14>3=!rC2bh5WZl5-4oftJWF{6w_=+{ zLYbGNo$CgzCu$6t^oem_>)xBP{8{Kj5&Fy3FxR-|Wnq#0yhZUizV_^19vD>K z19YmWl+1$x&Vyda;t zi6YE+Ff#t7#kKL#%6pA>z4JQG0C7(7IM^yUzz|a_Vu)s$FZ5b8fV)Zz9IQ8}xXYa) z%^IoI+BYD~2^)Fn3~i&b%XKJG=WQ{O7pBp%w%_oxGY!Yh%@+2&$W8TsFD%`eYdo&;?@AyZsB7dlV(Q=E2l zn(A(kXxlaq{GG%F*`4~h$J$5)Pc8)zbN5jm*vOj;MFco&cM)=%ax{E9j@&M6H}c-q zY^>Jg$$&08BrK0k-)i6A(FWJRPJ)(Y5}a%Cx=w7{wntBDHP?e82|bseJTH{)_o2^D z9V4nC6UsJ8JqJMaKWm!xD!|IJJF~C^T&m9W4INiJ62e)P@gf5NlFom7*tzX=vukk^ zMAkT{!i6ZD%yrCr+!k~kh;>Ddtym$0cb_NH*c?)(O(oYLnqgqYBcpl98|(_*4BPel z;j6~>2tUoN3i8o@>$dA|U*tH&(~Lm0ui(R9VFU(o=K|aPPKMWMNLxj4{US6dIA+{e zT)$Y}lMugJ<;7Hwapt@gvsa%3UZWBl=xNpAx%r$e>p5T|H*3|>J=eKL(8a`jQm8?9 zL&IwYQ+7zg%+BzEa+<*|G<#xaRSI1Co$7r>cQCCI$tJzP z?sHSBL1WC5Tgjzr&fgvhfdq7+4>X0N$YmG$epMLRv#*#%28b5~OV|FylvP(1`O%ft zf2cpSx!_MRRYHS;Yhfyts{8piPFiBqPqD&3p9{{?83kC0e|#=dxnN|+PGhO}r^vOr z7Oq{?zASr*X8e0M=*JONS!l8H=P7^kpq}H*Tgd9wpA>=M>0`ee##nFT;@gg-Ac8fQ}(Ck|1%Z$_5 z^KN(P(N6>N8W$6!_x*yAdu7L0W6QNoYizyoPXg%;u+?i(hbEEsA;z-Q^UF)blGg$? z_EB&GD-k2FBBM6q2Yl2is%Pzf=mNQpv9sK(%Gv-F9v?BtS`B~&BKIuZAv)0xPlZIt zAqX*IBv{y#*aXRUzw4wbhZ2|4W2ez@s!~A??^-BP&auQRZ(mrw3>Q9kO!q8{b+j%T z(?)v16g;OfC~_=RMPSYelg2Do*>#65t)t{QSM8qOAgAGT$!gH4q+pkJpr4jK)MaVmG!NND z+&Q>RDX~~6!u%1KdcMIONR|7Mre}uaN%{SH`I;6Q#93)>QyWFD1D;5WSQvPiF%~qc zvy80>`{(u@uyJ19J-LCcaBVAj?rrSqDYVT=+8=u5q2oA_4f47t<2w47Zfy44zCqBC zmD>8s%JU@n%Y&oii~pX-7l_~8Rbg6`Ib@>+pA zxuKwrM$#G&y;@hE&!sqyHE_DjrOA4jbKnYjkZFOZx4oA!N#!@+>r0FKS`g_+RoDuD z%u$r=t=ShmJmp1p=k6#3rZ3v@N; zX0<}hB#7hAnNXKC<m%b9gl76yg)%&b5MuQUl-n0fmT-Z|LuW-45#-g;Pi#-2Aj z?T41u@`xK9An_T?VMbk`SAc5^bizEZVb`>y)uc%WTtOTE!!STz>|Q(v2WjhedlFV#IyVQJ;HKI$!9Fd! zn)aCanHl>Q?zF6B8Bg6Z1{>w*ask%&S~p|5Ne>L8R#~hDh-$|IqumioCAyfBbkJ9w zh$31^r#@I3DAGruqJom7UePtRVH>d4gXNG0*GdKU7qu82ppxh@VCC2TbBxIar42#r z!bt_LuZ|AgYT?~XQUg>xKY{j2^h$}(pY-*?mv-z5k)F0{8rh4jcCNAWA z?nL{IL-qh^)S1kR@%T!58+}9OIqN(uZf@%6K-j7B;82JN(C9eBd3`{@*X#ZGi!;)@ zk6`K3$Gsfne2RBHUbb(j(Zk#*0vi0MuiU@Y^7Ov6(CUI2m;#Sh?1-KI^>@52bB~w3 zR#T+9spC4(r3MCX%fOu?Zk1h_D0AHpm@V>u6ec} z=zd;tO7GNHnW^JypvBs;lsOV4C+%-zmr+0848OVdZDl$Tn%XLCCog7!_XNuQ3|D`r zW|3i{c3?sx0qzV9_db|Wlg4BoO`xEA`_o?h^OUlhu@KwfiSo3^Jk!Rh!&od+igJ1V zL!|8IGQl2+F;2{F>02ouHr^{aZlu_mQ-yDI7|bGs3T($|xan1RNrF11?xts!zm6I(}A zJY-f{jq=ig@OAQ@=%o`pOoUOCoT)%zl|1~=TPa9SC3LY*Ia3G#QS_vQ7epoczvCC@ zIW`P%P5L-=&-7|4iO6H|*H6(Ie(yJxIr+bJBHM4^D2i8q;4w}G6Z%@?>IYouHv`YNy z=FYz<`&d0c8`WqrEWDr1INuIp0m{5NyZL@z*19t?tZgTk(3RTXKO?;0AyAPf;b~0f zScOT#m~7a3a3vJ)Tw z74f<4-%0Un-ck?hU-=L|IJ2u(wXz*tD)6l!f6&*=DK4`e*&w#>qqc;3 z+iI`-*9uq9*4vz#jJ!Re*1?-KzO5vTv^(un{*8rWsY~kfN~gbdI(qzCV~`;1P8VUa z_Nb=2qFxaxo*rAOYgF+b!0z!d|E*gf5tQ>Pr!W${+bh=lUXD`>XB?5SpD25SLNB7T zlkA3Z`^U6_k|pUgKiN>&Yh3tS0I+PjCi}rd!=sp9g6X3E9pRg94#g^_Y|?zg>Tihy z5tZBte|R#<23Ld+#6*~|*a)!xLhwX;wPhtgQzm#ssVr51T-iUzezyXl0=M3Fk9>$N z_DD)mqq$`a9J3(|;hLKJyh`n7DC>5kXlR))@TnQfj1OPfjXHn#Re?o$*VASwHE$$p znW5#WAT#y#c3Vc_Cphw(Uk{32xa`MwdG!o!koJS3InWh7Td8kr>#;BBAJ^ z*~L$2$pc?;3ZC}U0)}2-Nw)m)yhNj4xU@oYJuH6AR&26YJ8tx->SfH?DZ!j?(6F-L z&yM<)^(8q7@blw4X$N}(c7On+&0xMuI&0l{qo07)jCKz9TzggbOk~#fIfl9%N&559 zP(b7TnU9Il{K+@8vfbamBjag+q0o1GuyR79HW1a2Jdh$#BTXzBbqeR3B@6i56TvHz zz&B;gc`i0fvzp{D^P1sJOdeF0&8odjKAr%Z7f~`|Y3F~%N-FkSgmAp#!oJS=R6WcI zl!Y>%ORZ7$ACOTR&MCN!`U3`A47&)a@T%SRV?(-<96bdcrQl17(Yw?us)YUub!xAG6Wz$96Sgf9+4n zfKOOh33D^Bx$U);tQt(x35JC3E$6`Q$Xym~GJ^CgfX0fe8k~+_=k~0cU_-XHnV4)9 z^mN+xR@RJVi2)yrhH68^R4w|;eHDz=2U$4LO4U{S=5cQd;WBv|W=gPkE7Mqci2 z9YU5m@7`wzpnIQJEHvwDWwRWFfOrhGydRVQiOEzey2{@IJGuq{gPwgKV?Be9P6+tc z{41`_;~zz?W3CipfC`SGvUu26s@ahzWcYQ(pfUwgVz@Tw_et>!YKKR(hU)njXE>1J zX2npZhJ-*caHne6+DZGf>&f@@VPLv?jGV93VS)W$5zcVBqWh#^smLq*{!`y`4vgel z?|3!Ov~AKJChD8Z??q>O9z>LA3X{Fe)L+z4Xog6?iN17IqX{>*Ism(~o0FR zNT~GuUPDOj!guyEXl3vOFKwf>HU7_=8SY7*_@Fxh(KGl)71668iS~H5Y{~UuKaASe zfqX<~NZ9k|} zO>wH`S%}i~G5pTjk>yGd`sG_^=>ihDLB&?e8+Y}Nx=?K1>rC6|M`xk zNpoo4cEjwq`#)b3oL#l;xpbYF_E}vGe&p|i&rN!Ns>%i|P|zoHov!`i;HDL*M#Pm6 z;Gb@GU(Y{130{&bH5cD-Tqdat8Rk?UV#@fD(Ng9^VS`5hetC@IiHD&52ORAGC9z*0 z>|Iv=?|+alR8Y>(?f3afl9!e#{z^Q6nrQz3R?LbLAPSUA?yRnZLFmNwAXuao*F#j# zuU_0RW;yOM=$pmq_+Eq;rku~?tNnfxBK+VVVjar6#Si!%bE-ezycu#3fIxC?+Cw6JBeKybo`2JiQ7_}l59uJeYpGRx1* z2seU(9vrr(eyUF+s*nr5XG-b&+w+$+=Ff9b8R^aIl^sJ_+$cBj^-mmQtK`Jv?>ZS} z0V3h|W)4>Y%72YL=UyaF_dzutU+!h*JBzv}9OD`Vt?Fbn$0w$WiOL)R=&0}5ST_0T zqmIvXjhdU9?g*R9-T8`1xF=BAZ9fYnykxGTeyKng{G=0Qn*TDYM`lknAvXei1ug5L zzKdG-{7sEQ5CxcGmx(~Bv7qcy&-M`r%#oH|_;wC~MUkR$ZZ07tSz=a@>2dTV?+8&% zw2D?Yzgc;T7L$CU_p|t|CjQXP?P1uXG#o$w|GAnZ-)~iOGI9c(ad;MF$A=`pDidx4N~ zoddtu;MIjC|8VEkW6E!v!uxx1QJ>LD`Xk#|_kp}_GaYoBalqu3+>@hN;H*E2mGB{y zK<#??@xx^C$6=naRN)qAIo-SdOaCexEE>Op&w0KqK;JIH6aS4j4Md}7tfnX#G~3`% z885O1kxi^{Jxj~=zg>57egK(Vb^Aj;c&Z4#U z#ng(>MIxyZOFIQh)@eE-Rhl&Wx6#_ENF&3mUwUT9aYtFbRPVGW=pbN;COpxz;!`KV zfpl$rPUE=RsP}*8_BI&{A3nMpkDtxAoUqR+|42t4_0&e_jA-MUE~o($SA=c0)R=U{ zQrl6rG0`M3wYXt@d!sIBegBv4Lrr*Kn$a4@uBiSU4gw15ztTFC zT5^raTq1i!Z!q5ITL;^ZYk^@uqEK(;`$F6zvvxZ_p7nP*dSb?e4q{WtngIF_*?$tA zz0SXn;+pwN$fmi|w$p%CYzP9BEWd0XaE#tw$Y-Zr{rJV_;Z3{wl05!KqLz)6B1=>c z-L0hGBQ)IwCqQV-hRw{OyWp*7Xh@Q?aez$mOC_Fqr3QNj35G!0wM2@cZAR@ZnpSFI|3di%>JSCr3{$Nl- zW9=h)3R+j`$A}aoq7z)yY0^)*3*B_eWt&)~s_}Q~t;>zYnoMUIu?%A|P%&=Gtm#T^ zwo8T-=~1ibG@$Y@qnDp9dNc zziHOEL?e8I&t51|3VNz^)&DQ0Db zGc8e`%fYKuQUdfrbwzTkaB4UNyE8fzf}*QDtj53&X5yu~ehNTdqrbcfxTN;uaJ({q z!TqY@oykoyhI}?$-M(gRt(J$6Pdaa>%Dj>ZUjPFofWqTCl`;q(>yfXrw}UU`0D{SY zv=@1^;u=}`c2TdU2J_!e>8PX1n^V5hSyoE~^do0HL{|=h6}zt3G>nkm@6mB^5Y)^e z@y`r(E!g;vIQad01F@^dkYsD3o112~414`}%78Tn6Bd^%aHmS`n-D0bObcnGdB75F z+8|3{x6xd_3VBL8mew?i=Q=w_OMglTj|%l%?oZD9TriUFtrM2>r>5FJLjj^KInD{+qsCX$-St zfOSU4@!uRoAU&DAf%3-+92srNwR*LL_50trQ@Eb`8kSIG$-mXcWY?DE zD>3IJdkDlA^)93D&>lL)&-#rQt~qcujoh1*eCtx_4{23d`5-@LtruALER$g+>^Wab zZr~{TI0ZzWI6T>uZ|dsN1emF72kH-oCBu)t4o_~&&j*eMyV`t+*v1&NSGJR4bOuW zvZtvR)) z`Qdvef5h}@%$&lWl2fr1WjdYh-g$#XA>MTVhpaD;hw}UTw(pExqOpe(qGTC^C|g-l zmXf6`2{Cq=G4?fPERo0(rM{Fc+1Ig8_?9B;SVohqGnN?)o@@Hu_x;?@>+x4jy5>69 zb-p4IAnE^fn76)EC5R`o$zN84-m{RLIp!*c_bxW=WSk<01iL z^!==iEd#hoFLlr|Sm(_W{MuNBsMu7J} zY1uHv+MadbGk2@sH@l7zW(y5;*Jv{N=i^X#HiN;`Gkfs1t?a7| z=0$alRxTb+vmr&_BDb{}FV8!V7Avz<=WzWujc(x&-jUf{k95&)-_7fv*ot9)4HQ4g zyM9)E@U2Gj_=or|e*@!p0jK%L^udSv;yHB(OFW@XCBm43ARc|B?Q8h;cgXgkNLA_m z_wq=lxGF!ZjMkeL?+?v?H{GkhiO^(zZHkmTU^jZ3*&q)t@Y_U8Guzs0S@-GZZ>T4G ziF5Y{q%yxWeLg0Hz4FGVBCUTq$Hutj#)G=sWf1#zGolXqMVL0vnQ@(PS#6Ds@t<1O z+W&lhJ>32KH!!shD+eP8Bs?Z?XCXYMr0-3E4W&f;&GNBqqsjPb?IN6PIag(q4OGm3 zxA>1ZUv3;8Ue(Rle-x+_96WN=@swOaIJfifY-cm+$&ao0U%zKxV&tafZy|re!T$uN z@%3J_xXtq?WS3war~QQK-y)9>+hnKfBlYyYC8Rs3BMW*;^f{C!5uK3##Zw7`QCfL2 z4aX{KJRD0?dcJTaf(H5Hov`f*ft%gB}gf57}jx%-*-|IPNTjc^qW;tBF{@ z)a(CoDB|vQ^ObCq@dQhA*a3`iEEjJVKhc~W7>gMX{y~*(G8uQt%~n=c^pQpSHsf0- z0)tQFehgR7%xfnJn`M!lM)#>tWncAUD$lLGY;ki}|M&9g$Y2ZP1B>8p8E3=bI8;9h z=$H^Xl)nn9S5{mzpXr@;iCQ8Xe#!`9JDIuYE424vHq83M@{ooX>5+p3=E(Xn)8iHc zg)2V{?9T})6+KkZm4BWQqDKiF6>srpT+sIYTUwu*ltk+NzW8LsouiWe@|7}ulm513 z+jAeJ<2xTVQ{N@qMXHL7>^YoKPD=gv$`U&p@MB-TMHOrM$I~tMw*olSNEd#oOkbM9 zWLnViwf-Ke!XU=(KUbzV{*kfh-@1OhI(k|m!&L#Tpxg2J^EoE0jHIOG{Bml75Zm+u z-_QN3!q@#QFU&GH|6bhLs^#pbU*faX49SIWQ!+jJF}hG;T9<%oCz&boJkwNSmuukY zZw#h7r`mDxtKq;`_rG6f&gDIV<5oo9eZDX`FB6)}Zwp=uAj-b&ynec8rU*fz~*dW$aXxw#M_r8iTE;~K&2&8;Hy zbdhXR+9jvDtM~M;pPmcYiW}v7W2GQiSzd1ZapGx2>WC1h`+1>Y=k&w2wwcx7g+_}^ zp$?!s0tN$g3ma{Bd+1rzT(`=pV?irj`W zP0jAoc?1N61cL~pS&qPsnv1BUFs#V)CP4$*bmh+PTs4WCe|Sd}OWE8VUly%8bs)r+ z{(x8_=(f8fSZ0!fSV_ACE7|fVwwTL1NbP4P2I|AV?e1^&Yi^~Mo5D%vnkpcqGmk`0m^k7TvSii)r=`rIMH z%e(bWe~Cl(n7UETml5gauAfjiVqFpzfYo~e=!VrS;+mWAd%8l#OM?}s#RvDnIx2pU7pd6f>^#QbUUioJDB)oqnsYM^82@c|J0?y za#k_|Y|6^2mUqMX%szxKy)<`u>?)Wb-ln$zf9jaUQ_F7Y&<5}J2eCGu zBg5=W;J&17e7D;3B(_CKNeR!pZ+RaVg6r-rGU5n;jleamcM$iJ!~4&Q80@L-BeXB~49}FJHd&sT_>9 zOqb2QaL)sGI7eO^MIP5(aH#F+OF!dnEP?ohSbHc@XPR|ZOEh#Yyd1GpDiKV8R-spI z@AAy)6L*H=kkL0YNFnE!Ri#aa0eTqn)u2Z{GCA$F{=k?gu|d zoE+jB`X!Jv%dmXk&o$~Jniw9PHPu|6m7@g^vbrjAw_<#5?uo$J8~*!qL1Q<8iUDNH znc(l%i14d5RNTo?(Jb((@~jU~vnAXQhx#G+vCeA=Vo&_{8GCxA0u#@KRNGkU{@9!J zEP^sGy0bDdJrf_Yb(wmi_?hi#K*)wD?(=k&a~b>I3)i|0)wdBCiJ8Jqs@6~}b^sTS z(uBVpAY+weOs#mqb!#F{VO_sQi~ItAeW}S)_ncartn>Z)`r61zo@XJ9Oh>jB27aSN z_i(LF`^zSCiXal2xP<{t z$1rowAU;~(?gvLy9+7f8g7)bLxPfVPLVR5*qR%TAIU#lPR7R8hPQAG}T9+wVOi^q|TM93bz&p0sD@8&7u!(<5T&r=a4uVn_KrNaQ)OptR!ibHU6W~gFhq#kD z2c8MXz%(1Bf#%Kj!?jdr>g$Sa%Sckz#%XN)C87?XT;D&fVAO5%d1b7;#orP_j5|7* z=m=3$6yd4%S%&rS8UJJiL0sxQh66Ugn@) zA&1=}?el_gTHs+-DP&OWGZpXs+DLk5vbm-F@%PWoKT^+!9_5G)SIeM(z4V(>TQYW4 z6-(TbGE-vv=+M0gj@pt6qDrj~U%}1Xy?1YxpFbu3Y1d1ag!D{0H07OjsiUTN&uFcH zTWWCNQrZ=5PWsm;1IEzBoC-AP;${Ob(&DTwr~Z3%!>iD_Nkv`K^)RIryVgY^3mA1# zq{H7Ulf~?rjJXe<-h5#ua5VVX{>FLRN`H6Oxb1MGkD&wd!>=n-AJw{82$QXp@S@9C z5jBp$UbXeDOWQ`0=$>2~0l6aGfA{-2MnXaS)t}MIZ}Q)p1wE@gwVB!Fp=%i`l$Z$e1d+3*jKUrRQ0G8K^<$T$KGeF|E+m224@w)$Y7v~00o;}f5M#m@;> zE43_jrpl=C`doKB=j>(Ifxy47DpnL8eCn5tJ#V7?o7ax~|1vJi=GsCs?j=)>73A;M zJHDCMpdK|$zLAzS&fqT=ZV5kW zavWZU#R`l$$VOIs=?4O*&&MEQ$gKq3^e}5p0eOoc*_(HH;UBK{f!T`4PC9LH%}n~$AHJI1l!KPiKrQp+#&5Tn zY_rFdu5jEBj$IghrB)p`{s@{$H~G}gd}t1)61hV@$_Rt1*fTjA{u7s25Po{~%w|~P ztKe~mCxOR6q^XxU`fZG`M?{Y!x#Rqd6l#8>VvrDHW`# z^oQ_v@+~hfW$;)Qw{0Re1D6fLi*MuhM?$CU+DXr$;Xpql6>h+vm%z@(z8Ee0?V_|8 zNAYtfK3mkHozeB>8+dIS=`ypvW;KWNS6{x*g-3*mn5;>n>$M4T+G_?f~C5tq>U4!W=C6z?GUqjHrTV1Tvev#W^o)=x+sVkPq z+6xUc-v)a#R0KKPi8-v1KJ9g*F+wbhDat@XcGL^3k=glfn#0>0}S*@ zgKIi7qxJSZPPbZ-m|7AZ1drr_Y<0Fn1cZ4*FjA43mQU|cl^vJdeL&jOVt)aN%A>&zG{#k&#n=KCs#ZXlNP#%-XT-+ zDs`bqYJkpeYta2#kB+yy$A9TydSO4!rn0Mg98SnM)*5pTs=_y z23ju6)l%tdQa8Iv`Mi4$KXWA!8f`TYP2=d2nV%00_9bxwde|F~mEOYMEA2$?PzmO! zANrzg(%mn+|44zv+hj&R&MvnGFiM+)6i)uwbp%9~{msNWffvThY{W3B`2kJ0 zM;Mddz1y(=eQ=KmY8`{_e+`{7m2V#z(wOVJ@xsHi@YuFxzEyplaWk@I`A+r?2P50T zUh#ac-?@~V!*w<1c7*g8O)*Enh z8BM6o1@bBMuo}pgV}keM(64XiD#6D-wGUzQ7SMgS@-WKfgHi&;yXy@k2)4DM3txq1CUr6fXzB*3| zfF>kUm|Bwa8Ao66t(f`zu;m2 z*4_$?&da!W--*h9bFx-TqtmX4TtcT7&lKJo{O9}ojX~+XGm@TqrHK%!;A{r02Jbne zq3dd8Bc)Aoo|`eo{!&5sYLTn(Kh;%WIf3wdJ~i>v)qNbU38PD0kt-<^&-DL|le?KM zC^l30y+)cxbDpl;$Rv@cZP`fc#T~6GxZfd}c z7yZ*ouJ1V3=SGCc4#1Oz1jlaJ^FKDJWycQO!48w{ousm zyf>y#+$olNJWKcOU*0s2M(16}#WTBJ<$^n?N*{EY0L|~ttGK_fxIMWM=23#!TEk6^ z=cm;a-G`^7nTmXvxmTS7&tgl1b6>l7?fvcBeQ)2ky4T7W+S)Ur&qc*ana~YJ_~R0Z z#`B|69#LxDDDOX9-ONz&3CdFSC5jYyrC7;IwIFaOeBR_Z$=F^H*zgYf6WK9{Z=JC8 zXB6F$jGGW7DiHr8MtiH{HeqHWIk)#RA|{ZECLm*D0L*zxt5SZm`)*58_U*I^x3bG) zIp)cNsSc|bvS+^8s=LXaz%A5$`YWX69HiFbxwvJ_Z=oge94ku@>puF6Px-G8S@Y`VoBXN>{$!i_R6?g#K4E-3K;2dTfSZOMy=V*JfrOVxAKkhxLc|Y zXVfzmt(cDPbIZmSzobXWO6n!?H5IZoeY{p6t3}>_mnJ~&U)aPPR)e9>LcvZsWU|_l z+Tw!22W0CFr|_O5xTcB@d))gpfzTPl5QsCdrL0~Vf6qYdsXjO5fp~&83^#EtzVuo= zH`v$qMtLRLboE6>z`+vgqr^wsbdUf@jFuF2YKwjNq9n!Ik$~fU@tnkHpcQ2JQa3}e zLqCxUbMoT4FN&7X%l`{~ABm^c5!k-`WF4_dWEggmqH=7upSwG5I}m)Qs3E0LNk6=L z<_`GVroMEskw2}=AAMRWjG4i_ir)V1qk>wS4d=H7H5d1@eH+5Fe6{MY4H*r|yrp}s zD=?UQ?@a48!M|MxA(b{P?t^R-jU1T=@7yljdtB?QQTSNzU{8KMCeX4DVy1t9!5TiV z{&`ujSpW59a$lgpUmk)-CKS@wF~J(a+3%g7 z0(4PORNr&^llXT)?@+<*39%#-)8tFFLQG4ls*7+h#FG6~;W zGGOpfJAPl}@>dAwVqLu*O>r+(!rI?CU&iMxTYJSV`F1V8aNG9K`Dcw4peH0w@0>9! z$qYXl1){(7$T9j^r;YuWgGo+NZRxj!LxMuuZPDe74+lJFoT8f$FN$?(%)vNY+IjS4 zI0x21`X@4~C$TnLXhjJ;mgd9j(p#5R70peQGsWfkK65}iOGh*vYF~i zeXB3I`$%19UC?wV-XSAioIvzu-Gdf{e?2iYLbi-6`s3nps+Q({mE2c}Wh`F%RqMo; zL6&ZjdA&s_!PeHp%?_N_w^NE?lbS;qK0RmXNFqb~=dkVJD=k(0T^g~%?3GC@}7q>+n+m}Zsgjb_M{zQja8ey;z*eqWd z_8I^*;UU-y4UO7M5z_eHqN0!~uRS0qn>q%-!L)!SWPvnP9)UX;Zm7g1 zJ*l1m8f%7pZDdG(OOfG7LUoNa$oo4QI##f{{`2uM%*fVwXO`<^oda*6;LV6ujJp;o zrJBtuwyS`i4VfMuP8o%4-ypy8C+Rmn<>^7eki4kP=hvw19fbQIiaPd;kv!CT^3wU3 zlg*POy-DhDW+b^~1f?N~g7s|2SL^~o{Sg;1#fPTy z1`YW&*(4VG4Hznz9Fi5e8^q7%jvA*7l!Bwvm>{OJ2=eS6kZF%X_i9j0eyJTRH~j*7 zC>V+3e-U8e0LCw6$(XOBTQJAK9S-*X0PaZGw>@H;&^)&9Yd_gltAF1CLWm;0 z@HVl)W5$s+yhx}$^Wf&EF^i9B=;bBKB}Ac?G>-6IIF5PdkcdLAL4#A7Nt2_I<8uJq zZV4LET;d8HRk8UhLV@bQ1{l{@-5;)ODLO(emX8o%c>6$U$6k24&iu9zu=!rPbbK>t z*0hCJ$a^RMw;^-Sl0?Ju3kx!`=TY%PKN)R#9}R54ot-e%$%Ws1dJ?8#-ABK!bgU$; zssl}CWNH-j9E@Ax(*>6g9;R#cWsu6DyNV4#7;UIq6E5_$c^LwCHe# zFhib}zxfhR!T~H(?){Lw`%hQi?3L>s4LY#Fi(aX4-NM03&zw9&>5jj(EZX?STKF!5 zIA|I9%|d%cmV7hxPiifHD2-&I-Q>pfI;#Y{y6*bN_JhX_;wIYJ5j0ZE622(#G4g$W z%SJO6sJZ^$TR-9C?R+>jQ6RTFAcNB6OawYH4~n>7o6GH>y65KJ51sw7`v!oBGM9pb zbOydO8UM-uy}J7-noBa9>rkdi`(tw`zXaLa)K0crU<3owTl45bH<44b9j6q9IxM4O ze@f2AbBauj-}75%J8mKr)`hljzq0u1(gg4}${}B`iD=0&ijow6svuU(EuzK!JLyAq zz8dSuF=jlqaz-meuq!a-jJ$YWgxH$|O8Jf_2Bn6@o-}sRNSo>FV!m#E;n;iPUk&kS z1=-ZAj5EmH3g>mwmt&qGrq^$8WDA^+hEzKyif4}{LU*LDl*+5@MlnS9)O@iDV?7gu z-j?S{-|2LUSa%0_g(}VtAluV+Pg$ek%n^4E<2hdw@)a>I0KU|-AxKo_~=3M zvxqfo_~A4zL^zsN_9V>@j#=l1Q+VI$oS^rJ{Suo>9350c(E+^_Ht*QRvP2m_-pgia{|W}OLnebg6h`_5=DAgV121|l zN@jhceXm{_x73en5U|kfT1OUBG>t`F?i8Q0D)4FU*a)r)+)Yu^n9R$T%sM#UA4P6t zm*Xq}5#f~f_&w%D&&^JOA*}6@Bg%NjCol-o>peao5`}p`TC2o*LQF03qM4QUrzF3d}$pwz;k{dr+F?^YEIEIbC z5;wXr=a5O-@7X_u>Q+VhqmSc7oF3V{y#d4CA0S)a%fQ`<{)Fa4GcV%THi0RJ)(0eDoj#x zj|;L8{(9FHLWF_zT{NTO4d9Wu&S!aPZt3<4N`4mu6K3)<9&)1#bI-6IGV>~meWS65 zuikXma@TE8_|xRAkUNA4 z@>lOdb#t(q@^Ih0tUq64Z-$dXN3;lyBpXm;Qy`)FaDYTy1h|6YivZm&n{cU}spb(34W~Ci0|Zddoxf8KuX4dnW6B zE_1b#q{W^E8iV@NTB&zTL|N?Xw`vxBlwZ9Ywl5AVTQU{f(!7wr?hNpLuY5Er9F$9y z+3YNpi!JC5_2Llo(X0h%LBzkIYkqc4Au-Sf;B3%m(ofH+lwl&=hXZJb){_O zu(%nAY5bmCOK2n-Ygk?{xdHhZ>w3SajepUwY=8kjCeR>%qe!C41ftoWk$}SyWjDGC zxrQD9kpZ?)-8vxQ>W#JI{3b(kV~a|tajCdNUYaD3h7^wM}Fbs^{0pNw;5{VbO( z18Nk9n{~&zEct2*uAPq9XewXtI5~cTqi61_st&(FrjhPy=rrl4{W^259VbVduXei& znL0`}S1apgerfL~jkfD%l6w0tq1rP=s{G-a=~ZF9=L<*=0$=)P=cR>j)UYlNLQOyC z8lTFU`nhx5$qJn>w0L+Dv#c5^&^_khQxGBA^PaWp6C37u1RSxp0CvBAcGrK}w_$Qn zzGw|RyGzO>0j#TP5EFB4LR7ZYg>byy;?DgFrXz!TUh@hloTXGzHeYX_OuK>` zIQ2TEEXK)GPF<%zoe=LMGaCgugfeWv9dOH34C#I^ZKr!m~>o7V=z*&Z&G^UiU75pQ@4t;Q4fZ3%3C9&&B@sD>-4Un`Es`cO_GiLY3-5!5Bax(|+% z;=v~w3a5X{VBA+*B7liUYCMr;X@+1DENb~Y)mpra6&>&R4I`E6AGwki;iTb}p&Z#> zS+C!&C!Jc2j}3~-?aF}ftA%s2A{R5?R>^O`EbRYqnGX_1Cj`7;=}JX7VDD7#;aJ?O zeKYi6v#{V*L|?=D-l~Yh8;HkGy~sy14D6P!+%fXXZ0Uh)s60*ehn915u%bM_3tkJL zF22Geeu)WefwlVAi*p)6NN)@fNzl%3NHDWr7i`}V_)Rc!V zEHb4_gqKu`w@Ga6(!u94qkN)nRC20tN`io~+#u1t10nH+Rfl2XDp|&9?^!=C?cy`Mu=6z9eVsClIn#yqk+1-eL8sl!axVL> z7)MEroNoaL?ggMbJ|9cmr*pSRcssGa{_SINZ_mwRZSXj@`^Vh&1gr;BHL|}&q>j&3 zwVhR(pX%R^G<}x_${bp$I^xT7S_lJ-duMr6O2_#U9|D{F7O&vIQ#Q-T_4r@Oe(z41 z{k-m$9@O~M{tk6vuA=wluZw^n{@?xhUjsp~u$zM>X7ir5llT-+@;B_bC#cuu8%>3mSV~$KP9^o$L6yOogFu*e0+xoWKC!jVrJI6O+qF znv*M#ze$BOEnR0AkmEkFoy*8>JGUi$?HiuE0ebUW6)O@Yliu73d~qv;H>dhKFZw(a zdzazo!b--$-B{fyR17$g zpcBhx5M3z+$xs&~Q08Mn%^)e_MMWq&=xBH1UPMF$&Sy3er2|ae?WDx_!3uJ7%R7MN z+DFS&55IUqZLV18o|HkN?;=NFW&NI^`(M^*d0~65K;P0qATq6ce)<>uOESucI5|xd zC|F|{91Qi*=<{^}(2LkyDy8p=(IE|OA{KK-C$AQ?QbE`_O-*PJzDmm1az=9mp*bSu zMs51_oBd@(ly`PY2Vsd4C{DJI?3PXZQ|NIW3wmO6|qLY&8 z5A}6B_>_#*6--A8DD&*M(qEw&HGs1c#(5+4qWt_$XuwQDdC{f{K9N8A4@qk>5&U!j zktLqFi{~ggK#3WAq9ZZ$UNqvNiajJU#yQkHh}T(!SVyR#&hx3DA}Ha@u^3SN^;4uc z8Rdx?0u|UUs%FEM(l*9E<5}bT%R}~!RNS{$Bt;}+{hgp{@JTvXB7o+?!X>k&HYnQ_ z1cXac`6>d>d+Qx<3~Cx^7A{6udl9==y~bk36?t}){~Ku;JA38pJ`0(aAm{tkCLjJH z|FCoR1=6X4w!_)Y&%LQ4(=^0eep_;jh8v$Gd$+8A4jlRoWY`ThP#V0M#{C5D>D8?m zxasOXp(qrma{^#ceC25<>add$^z)O~*xJvBsCDevhlaVwG{muN+iSpCa&P|rtHM!|L0@GU zu^uShJ%1*P0(0f2p2&l-qUONP$|B1aCjhJ|&os2}UXahSY*Pk23WHL*q|r<EvdMhZ#=n0nF@mKCc=rH=sLucvI8EuhzZ^4nOi7I=3mJt zYb@X+>TJdw5O3~8(d%0W1qEq-W6F}Z45#3Lyu2$Hw+VGz-KOL}^Oh9LQ0HkrFDy%~IuF%>qY$ zWN5nX;gHB_`1bIu&+%>*vMno~IriD~la}FL09fI?b=In5ed~YNhENs~b@cS%NrjN= z)=vODYlhPd7wJGsM+#?D_xMsx<6In=62$|OY!v324JJ$2GEAM0Y6bSU%;+1&EOaJ; zDIMCNWPDmdQ?NAm8if{3Jd%>GB5I{m7$h0A7&Cet>smMorL`DTf7nGp=L*SN?tRobsQ@tsbtD zx}t6m%?KqWznXBwVzPX*yBgj_k-kWyzft-3VO*U;QANlD= z)Ni*lDReG{G^;OE7U z+!_bcU?1nBKX$WY4)%N<2S2LBqwC~jZXFzLB5|J9TP7dn-NEGu8an`l`%4EgTx5Y_(!Grw{-b!YUS)G zRmvuvQYUpxW7R(6A|o0mqUB_G-!NNzY}Bc)o7it6(+P99*8$C1v@~F-R(5Zxtp< zId*E5VL?3o&qT)>HVq&H`7(N{ackjtk=Okac}`$2sUlUh9!DbLbT$)tKxP489C(l$ z0;7jOC8<(H%~uJeTB<*O_@0Quc|mz$cJhEc%wb68nVmRg3D8ZepVVkXvho8!t6M-L z%3gPC>1C-NtCKYyb#cPUmipT|-vZ*~$j7S;7WQA?>|Llr{0w_JrxH2b)bT6n26f4O z+cndTVZ{0>Fq&22)j<$@V*YomKs}SFyak((e^DTk`|&2D#Kd9Xnb0}ZA<^5}q=a3B zUgT&?V?kM|9(m-jce8iYPDpuhKjQe*=)?84sqIFL-rtPC_1Q1gSil7b{0RT&h$dCO z|L?4(e;XMVyk&vgrl%K!0O_9rl9u8zvC(FX<$p53INkp`!b$qSQ^x-}%ZUb{|5xs4 zfaIc6;q4(_pQ310T_XH;4&|XDsEvf#f999Mn(B#5WLO2G>mtl9G3+!H{KQwLQ23(; zPJAg?d=wbVM06CR**y1P(X*oVvofVOOuSX&Q(QI~E;M$EQHf#kktCO|aO7gEy@U`)o;B2h9qgI&v z?gu{v7T}VQjiaL5)>Ti{5*ge+{)4a!)pI#AvXWetI@QWcGhVK>ACPH;e+H()Y4_Zf z>D~vLc(kE7{lzWG7+MO7z}?haOp=+&Wm+9RCHju$$2)TH}4)^+j)A6BQ8(t82XHL!Gx0%LHUue zOMsnF5GDmc%Y(>8fud@FN$&q46+9>(xc^0E*ECY{U=;pK{Ui9jfKo0Jm`59R%?g9} z{MvqGP~`RYO?3dCe*JkB{LNveOmOq0NB-pZ_cp#=G0c0h64cJQ=DX^lapNG)z$-(~&GmhTG7jOXF`@+< zkO&^s!rgNI?2}hvxu|cJEfUZpO&odwP#!Ztj`yKLKne6GM~yFC^~;_$9q_2@D?I;f z0;nP=C0b6!&E{;jpjnaXK>v`z*J_`izkLcRnp53ak4bjD_M4+xLq)aW%u&79&V=cW zIJ_(!p4&>evh#y+B(b#p-71jnkwd!K~KfTO0oGx<^aq&Ig&Rxp5^=eskJeD4fli8^iznUnSr?k=gt6vmAmFJ@#y#4s-yt8t;0!xluP0Bl!B9@f{e|#3T9Y6x1!A7T?MWD`c z@=Aobg@bAgPvB9^o(_#!cB69#7Y!6^>YeP5wrCi2%_9MTt=WMBT=5?PS!Kal^d3imyY} z{0r@)<;4V9VR^(-e@TZES)TBy%JLg|i``3XSluL{Vo1nbxFf`R_u%0q_@7tZuGTknzZ)1R4Fe1l2_(*!P^n|(Y{)y3TfbL zmSo+7O5q%&bPCo0$VTkb`C^!&w-hddd!j0FM;oC!oC#_N z)RG=A&OdSPMuLL7D~Dh;xVsJoq&DOE2v?~S4drqVR9 zLm16kw!lu`V@e;7C*Oau!kFU$ahM0K)LxxuVm96J|#8D6nEAHW`MrD`uf0CWs#4c9t8#ajCwv{>?t-WH?=Q2_0u`sKm%V8Zd~ z>Z(|2ZDNnWEXlrADats)p-+ZuzmLNuVSSoc5;UnKM%$W$geA;%+c)SJyi_W!G$@mt z4{JL~Aoa+cG^+yLvM=HjNKu`O zUWf1{!Ya<$hD_(+Sa&>HD>anwuJ|XLB$-I<9~7Vg>ZUv`^ROOr@w4xW0m?LNCa9^F zMJ~2r!<PG&qMVHs77U=2-LmCnrI8d9)}sK7ITg zUi)e;?r4$1$VKZrmVM|d`Mn?X3~mW0lqHmoc{)6dVLM&ELgD=W!pl#S)o(1@hNW<-$ydtj4h;o-1#Aaio)_shq&VGwkEJl~XJ=5udPV)NY;LaRn=-6OCTf?C zwEg(CoCO|YsaT@Pn^?na5Ec!kYc#%Zlc=CtX*Xm#du#MNBl&2lYaoq~>Rlv{i!4hG z*!4XHl3;a(WncJck_4h&birH&%9|kZm+839vG9AZ`eXx4@McgPmqD?(B=ZP6qm(~N zj%#B9RHKgh)xa8Q^;G{7*P|TA7sC%`i+$J<7hN!?m%I7d^`S=CZ=7Z^e zst@F`3luph$HhvxG4a*qjc^qnD$VM*`h3n}EfOglFwoeSw^Ukg1-OC`veio;-Z~9S zsFdH(fQ_zGbU3>2pH7slb>A8zpos0W%M$&Ush^5e}2wQJE2E)yG-BJpDJSeX_d zeF2Gneu;>4yy}MnpZbA))T5!JODn=7&Tzlv(mPM;{sq2t{~1HC8bVC`vQV(^oYj}? ztf$4)oqJP-{XOhreln=Ad$5f&h$78nZ5NE;x!^C$v3CoVbKi3nTJxfdKpyA*T(bTA zzw5Bxzb$>Du*H~hInYF)``sBU$Y*Q-k4voJRnO0yC1DJSbc^bCox0+&*~30bY^(92 z7hqD}g>$Y6W{}QrtR3*{ic*ObG=~wFgLJAKXpgZvJ*+^ume|2532`sJA^$2U(}o8R zVC}8da1*g!1I?ik4;|qcyE6N0RP6%!4m2mR^pYMa1J!sl9DMXf2_GGVzRzj0u`^~F z>Sj@B%F28_j~(QCwZL&ZPGBgVWJ6FGpjBXHC=nFyD~XO|7@i^}iz_{&Sti!yisRT) zZ@5`Q3q6(;FDg~YKKQ`8$RG%28ZNEtDR2Fx6WsR@gHKjnOLe)w_0V`Ku~iB)df;&V zVRFjTH|rTot75#Y%tQG2r`sKYL_DC_p>86O}LtR7nmk|a6@KGv|s zhl=cKSDR`I;-w|;E3tJ;=eHgP^c%pHWL3r_bggb=V+r}yW$}>gb7Xj1hUHN2<#4)z zZM8vGBMYq>Tl6)klzCw~Lw#_ocF(en-zPC)ZeK%$Rl@)ip6?B|{)C$ohR7Ev+AHJg zSq7(631_1|mlk~m6KgY-cb_pVL^FW}B`pWasO<L_noCCO6ZwVH{ z_Vu>#6U}#G&LNigxDsdo;mt=sU-Tb5%p1jSg%c<2uQ{=VUw^xaq4Z;ywypA?00XHn z(a$vY>qK`IGe`PIs8+YeO42bt_dg!cWhsz=H{P8mY)0_vZBP*-^n`&vU}!~WUA0)9 zK96#ioz5kgcHsyE2$lS8O8saqXmczjak;Q6SJq#`F5j|9BT_s%mzzz(GI6^xhSABI z0^#jXWEEcu->yyMDEc_cx{~$TQa(~ed73b$vk}hFFSo4DGc}x;hDnqGDKN}0Qx0U) z$xGr6(9tMw@r5PxM?^JdpD?x$?FpTgp7RmkJ_K_q0e4w^ob*tYVZ@GgalbLrYNBrm zO7TzqG!q6;1(ik*7s{> zc87mIP{%$?WF@NB^HB2OGb%irgv|-3!|jspevCEB6o$}x%!j(SNmApvdv|8;Rw`4i-1b%LYadr-RX3|1&%I3W62xQxhaXQEwpJ3iOC-Vl58uIb z;bA7wjaFnPgt|!1>K;Gn#W4G5ve_g88iQ7A_x$hN^6mheP7~l5qc~0H`5O20|r%8zv^P-S(nHHe$GB9YkaXn}id`Bks{CU?SS z#R{cG`@l&)>DhHUWj)y$nL#gujH!ucUCaP+)b#;rf!x83K~?1L*InV~!Z0Wf@&9A* z5dG4(>CAQtac{j2=d~YTci9LSsD9_u#2`TH^BShu#A_UOpdv>%TD>x;eLT|)gJqv} zj;VjbC3v5QaNddSI(ORp(aiAOu8Cd5?s&m`jO*~P=E{-#Vv%nEh%X)K_iD%bf3-ibikMwcdx_Mb^l&F z7IWHO(skt=?RyOSPX8A<%q{8)`cE~#{eRQW)xa@@e=q-k|4_|B%cy8KQY|H8FG+70 zbNJGM_B&kOfCs(Ahs__y$?Gvv;H9gpy8!|E5CBE%`Io(Ickk)BK9_R&0bIKCv9)rp zFCZk?D#TS;w9kB@Lv%zKt)K{}+78gdvyV#pbgZcq{FhEZB#s38X!8U&OYI))g!VF>Al z!}s@J>#YA->+Dzei~a03d#}Bo=f1D&bH#IzUb;6hxBlU=4%d(6Q8CpnHQ1mLfS9OR zE26qH_vaVa)Be34cpacR9$#|#b04D0C||Y{xOZO&V}K{k%GKMo8%yS9vsW-dnhe|N zIZX}w6`Pr#re<9mqXmgGowxP$r^C}47G4M;_X`=b;hYg(NonT^ zJ2tTiNf=H)MF9bLe54B-@`9G9tIVR$-sy<6T6*qRf1)59>pxYqxIFTxPTwy$pLZFv z67_}^SxH04KMO?I2Qed`V2YGHOHz7%d>SR5+s~5}rAgGh9*RG;aYn?iPy&#TH0yyb z(|3e=mh(B|lM@16zf_buAoQ~RmDQXDR3jv8M^F9|{gX+#c!$8m2?0;lw6p_0k(4N9 z=19jW{g7S}$v@m@Tgd_@mYsi#E==sG0Tuz!Q>IH&8S85I+E3ZVJYP*FLP~Jx%vlSi zt(wXe`3ZjF^?xqWtDL%#G zIOHeopQbbX);idn4nAuIZvApMeXCdBfu5A!T^utG!Nz5HDgcq6{kTJ>)dwau}9Q1U8gZ0Zn@_PhOB^u~0Fu2Ub@$ zJ^y*zcI>;gLGODvXI?Jex!Vz|tO(u{j;3ALx+|O~TZG;mPS4ol z1W)0tA{vBjU3&r*-HUD`#7d~ziJ9Xq^19)Nt59cRgXh)Zyb7UZbimz^s?$kz|yI*rpgkIK^MhvY4s5x|+`Y^jb%Z zdvTSYr|fykUZF}5rKbW+E*)Wj2J6)9Ae;7^OyIkQ=H78=6*2^6p1S!+{oHi*uz(s3 zNXOs@ViX`L`UuFVOyg6cV3(`!0Ya#3$LV>kaK3O{R%jeDsWLW*VF|<-6eF721$e~x zN&qFxtZGfHniqT^`?~CN__?8?|1X5jis2CQvat}6J{Ioi%eg+3o)%>2}q z&>tq7y`hsk-y9aOEeSXQC4u~%Yn2hvb$(j>Yy>afJQ8{%2wR1yCqX>6GAOndW74en z3j6It-5;Se94ll-*?|lSTwfhB8z*;sRtdaz|4AS}3m4*lbv4RIr*CucQE zj_44$q-C<4P~~8w+%gL}IoXHginzW2M>I^@*;U+UZrsShJn>-@R1JvPIvKx_{Wb$RX>cwzLG z^*Ek-cE9%4vI4v9)uaELbmM6V2b)G~ud7ub?tFD9sBFY*87vWq1nKmw}4VMn*n~inh|N|tZJHlN8@F+-dMW`4e`x#|EAdw6OStTxq!wb4$VrSLCDp{*D|A;#4#?K z!0sLj_S#2xFNbwUmv_(3j*$aqwh8-v9&JQxQ)#6{$7KV7n5j`G)0;v~L!StB7=ry} zArZ+|_(6XS)*0q8%`s51^OtAKjK&n2{?0BePY`LW@-Y)h*0cDHv`WJyPUs`ec-e8F4~RL`B|5&aCy|6EuYP z+uypUmCm443IFV<^g;UrxTH;B)2>*9-SHJx&{VMXP!-R~U#(4%0-MSW19&g8roIE2 zb3~z4QZ>sFecQ)c7rbEhh+oDQ2enq3uhU=yQ{+6+eTJ1*)L>OKXQeTH_WR0dQ!pHC zO){cZA^>0mEkUQgg`vAoRRI2%R&;_Y0%JiLn;_QYnUo6nR-8f%?&}sXb!;#4Xtlmr zuyQ!V^sUl~f*ggS9p_+{@le{=>A_ZVO{e-trEzkdJs8}Wr=;V+EhOwBV+Wl~Vqny8 zUs;>l)~S9n!-)~Xc)mQaRjRZ$0T*|&yr8ddDef3LK9HWZm%iTS)9;T&?YJbE_)SW0 z?YGPnp);*Q=HeljeW#T#g zcWNrn&_HuGPkr*w@q{@C&V76an!}}&hEh}+PDsv$DgW|4QFRtv%f{H`K2B-yh3*ft z0ZPlK0XUa%NQxQ&AC23>I{A%+$4J_i&|^weu*24B22&ks1``@1HWPWNF!K=`Xz@}W zAmGx_f$wDMec*YCjDH?T0PMOP1z;T z@axQqmz30uSo0>^SWeMQtm4$GH*hd0ol|8pton!=09%yDAage?R1)GtYfQhLExfRN zLfI8zHf01n61Cuy7)h_*=GcXH?)W!09sLrSG;g4{<>Le0X3}yL8wjR-D1!10pNl)9 zg6Ugs-g>kVbWYNpNY6&k;h*E|wy=O42n(6w56Ol@Z+w!=dbbCzSYe=krBJ;?v$7(Y z8q=lQ*VEIjhN$7})9>ST4_rl;;pJjRmcWGklCA)+>*e1 zT&bc?b&}`Pat;hLMffz6qfDjZ22i*N5B`16%E0(Egr8S)@>SV%XY+nw{zSfpbbqsR zYe+Gh4hmV;Fgqpgvz5WIIyL9y`zOx{o#c-vVMN{d-VAx{t!Y$ji=;bhN(tb0E_T~D zIY1ascbp<0SHr(0j>*4kO+(GsVM|-tnvG+oC!3PRx^To`Vr7&Fy%SBqrpfV2Ov_Aj zz)%SE@HFjet>pygs`+2eL*cCeQAv0+!@FL1>hrQxA9v9v|9mSJBu?Q0+` zgx^5D8>?3m z4Z)Ut)vzd+;1a)OvQ*b4b*#}k{3vJAzi>Q-^1j-nmEBKv&S11Ndf;Nt#+PSiX2sNd zCj>Uqeo}pmtzv&`hoDk_xtOr#V-~)!)dpB;s3*Zq240`I4-|P2qa)3m!eLOHYmvFa4Zz0QS39n%7&%c!Y7{9(LYfE-e>abCx zUtH5*^|b!sp%JD4;IM0CMQbwy4(`AeLaK!%Pu{+=2C@~iy!I^S`>g8N79Uzkl z^OW@5ry8oUJSRyHr!mivE4(x*I3&?4tBV{txqTwFdR#vH>9)aJZCJ%MgMre45H=Yu zx#?chU&6uVUS zn&y)9^?Y6R$RJ4#tD$n0X8}rbZU-`K30grm_MY7%NwQ;*&|+KBr6D@bO5rfn#NWet zt<98FSO3+QIIF6bmeve1IZkB&-&THt?mpH7AGO8H9O)E^*WAD>y zzl}qpbppS2(^GZUx7FHz{IQ^l7LV@C~4jtodaFx>R$0-I#_F1K_})THg9 zkb7bp72@%9g2-m^&pCzUu9=jQ>7%Xp9+P@E1I@mD{9C}f8>yqv-jPW4q!l^o%xY+} zWZj`C#wG<8qK-B|3mzY~odGmeS#R}@>YbW3ezNutvMtzIgx4A-zCSL0lYuf{@^#ki zaExuU#j@_6wtH0ipG>u__NF_LO8jeF8O|wg7`4Rsj*`l+T6R{~dM$B_I`Ftp4ux!4 z@7~6L&`AUGLADLwQXZQ9(cugm#1$9je|!LKsc5=7eGhG*w>GrNvkk`T`vxdRgTX!~}q#iE}_zl1-cVcbMvRDO_3!m_y%LSc8Zq~SbkLx_tMAo32);cd`0W_$g zdwKDCkBk)Fp`fZ7+V$rC|+G2C9ckor@g7wfzbLukmxj zqPF~1XcJvT!MBFPH)2kyTP-zIt9;dy2BvmN@#pc`|6&ys%qMXLwx|(ywUteNW81*9 zKlxiHYyNGe^~*p95EcdldZ+_^B4HVKBgebdi1@up?#a7Wuk&}tTYF1RW?wli=p2s( z-gF0eUg#aqH-c_KlqJxZ^b>M3p~9@xQCpA$rh4)xdI(^DXzM^H-s9tS?_EO0o+bOl zI(!H_hJ!^kP~&t4q#<>FSoDk=7$l&oAZS+5>+Ur?S|L7TiD(>+c~}UUHmuvj3V1X^ zgoD03%YzE_5!Y2o&ee5gnC>+~AOzb&5U>OGF+L2TiOU&~-VnoAsiZ32%clL%#t+u-s|Ka-aINgKYk8zHx_}8 zrW}GZl2FV7_DX$sBT&S($mTXzJ&C>q3;^sY>t3oN``4?g;Uk-MMqOgbva^%j;UmGk z$-_R88uev%_0%lKMAwa zxkmv&spxP_)^<}sbJVRsR@stCI~HYDM@As(k6+dhs3y3bWvsOo$KWv$*R$^Uq;*PB z{nuAn2L3q($LoBCmF!IQcG%qJSHZg4@Ari!4ug(swmed&dclqr{k{xUa&;In`Dg3Q zR!3c^WjoAU9rmejTsqL;p8a${_~P7hD>zv4IB54~%CY6>DW)c}=4Se)m~Xqs zP-n!JPNXP#%niS_g?_BcqEn#(q9v8wYm~hF1LW~og@#*K6%G^QDYYquk+7b_T0o#< zx2o@Qa`5r=FqNaVgwZzvJ?x4>zjuiLMZ#|RuHU}=Mel>?8tkH~168-MO4tWSy9!SR z-`u8+iSY^Fxg(>7EeFP`k=d%2P}^i-H8S&84UB9OHi_tE+dKgJ zPBL3Xmp#4agKK5Sw~onuhRQWD-5_s=)Hjpc5$@g0Kj+}+?b!=!+(p{K)_hE0&r`on zUp`^`>=m3)9HyvL7^`OAS?bpz=IdL64-;j+?1Zi~2D8sF3w4v*-S(Uu_LEVj-MlvY z*i9`Bfyct_(ey_7ldU?QRqg1h zk^OY8)U);XSPX^M=3Ac4L8_2mH!_Hdgb33u(CwZI6M;~;&&T%}|BLK1 zsq5;%6iv8|vU0^HrV5w$CnJJ*7^GFXPxN^;ZN|513V{`Xzc4PEu`Hiry>yiNLVgeT zhS88?`t~-ujyK9Ls~eD9Gzo+tjws>WQ2;tzcC1FT7_y57=M%rHmDWt|fv+JcvPP@rLZdGX-+mlKG#EnvSRgp2v z2+j`+!e<4PeBe!Y!WM8q(uaO@vQ$UE&&ZeC-Oq7_XHx(N&fEtpfOyI!2zmefbjV|aHnX_3$FF`^z0R?`gO3vkP$ zvhvo?Q?K5;J(N+YcX{0d{AB=Ys50~5Whf%^pb=C;4EfONsFE(gWISlVsM$Zb+KsQe zJJ=)jDr9aVeeOK3ul|4m^xQDZKIZGPZQCH)sxHcC=$0%jxkz43Dh(Zdr&-C&ew7K`~9)h9(%rD z(kgj9o;MU)7ehU)x9a}73KM2HB!D@l^mS@bHgGA+0rbGg7UJV}y4scv$FzIrla*Vx(>-dmX_HkXB?x1~5h0l1q02ZbTL{1`i&3YcdAtfHboO8DlNAss?E z8`%8LkTF16`N^RzyVnd%6Q}MeW$(L#3iCgR$e-F%+HvS?+4+?E4)%4%e>Vu*?A1)~ z{%;51zbW5vj27Qo=*&bb&@1>}o+%uU%$Yj5Qdi-weVv>-U!p@`Z!P0zyw`5F!Y0+A z$Cp&cm*HQpR#Psvk9{+%Hg137b{zC}JoOFR;dHwdRH~^D>Tga41w97ZUAJeIGSF-@ zP1w@H)|TswSoPEiXeWJbP}Gp&d@C__^sJSd;sv1htszND(ls7^GkGw;S4DU{(cv-S zS9NYQiiVcEM4sT?quh_*%Y_}qiw<|5U6J$znSIMI{@CQYHkpQVtVUb3?%#%LE{V+8 zlg@bMxZCg7jN9cK;#a}RFFLJF0)vF@B2lbW%iSE%z4s@R5?m=7>g;)r)zy98tF-ul zRTYQKt3`tllKIKPl?l{+)X`5ib1eo{Xg1)U|MYoO!T50z>2I4CuOD{f@$LLXO zh1jk`mR09vWK#))O!nuO&|B9^u{f`)^sxX^oH}0q_8bREi@~+{}DA@RoqO{ zYhDiQ4DB^1pMaH4h%qa}dESvQX&9JpWXzs|cnLb*=P?FlgPbLPFw#5c47TN2hC&g4 z_nYh-62{{3yi>sBzob&H#1|M0E<_LrP=~{ z068#}I;RF2d#sPEf~8p3y-;SRxISviBdYll1Tv|to=_K=WrsBMMoYW#fWArIYGSOG ztn6Oaby@?snz~jcjzamsje{OP-wrh&F6cndC*FFDNjsI_oFJtq?~-*n9OK6N{clo* z34r1lx!Qk?C%9&uqhrmSPm9U%QVVXTX2z*(!h_{vdUCfsYHA+<09R6lp3h*ER~K$4MA>xy@x41AdAH9)X%lxfQEuNd-s2?HGhZjE-sw#9&y;2cB!rhcmLjSj%45(aIFNizpnqTD0x&{ieQk zr{UPX0Y8O@F6ayx$-Cx<7n-aSocJxLC$3$R zn0}84(wzrgP%@@(Km2Ljy=IEg;0ni3w=dSP>tui<0W$Y-9UoW;%WiRtKmrTRp9F-x zPH09QDqORjfBYDHXd69T0(xKg6J1fK?f89MZ=(WHxB}M{0Y!kN)+=T|Og5>0jo+^i zdx_B&$n992;^iBu4Vj}`=Ej+S_NBq}JUJ5>g^$KY7U!O^) zq_3tO{nqG7MPa?-!cm7pw}@)wYnqZRbjA(?-Q1J~g#x#Fm0JBcDmmUd{4f~+E-&t+ z<_Fu2ut(|DHd?nSSmLvpMCl=>khZ0CUdcgrvIU#^Da11r7NpOt?oG!rjQqD5^|++$ ze$IlMBCg_3@^GXFZP~{S$YN^Tuya#4m6QMPcs8}ITZdoef98W>yCidZE`im*l0_5N z8B)4|;Ec`kJzlDAqu#-FyC}95juGrCLzHGswPDPqx4XI#wSO}wt~{|qR}rR5>5jY> z--lr(%o{t*LWEB|>=p~&(A&En({x5v4F4HI%VqNbU#bthuF?llCk=^`iTX>~C`A3; z|LF|Z^O1~L1076Q^Kf4|?%hA*=G@MU%73Szweh+ZvAGjX6-@{3P)Eg2i8oZWbJO$m zDe{+BidtdIIG9(6P~c*A4gtsXQ499(hh{uo zkW`k`?+yKZngxe$&f+L?Bj(*~$tOj)AmEVwxj$=Ea>*EqtEG8`y$>%5R` z9MsPJgH$uTVGBD_`0@`hE51|P?tz1eIE>u!+57dSM0Fj@ddC8jR4;tm#X{K|^LyBy zusVpHNJ3>K@?0Mb7gF|JuM1CryEw(?CQj+7XM4OZ;PTop9V0%Z3hw57+%o=aZcuQ2 z_mG0&N`vR>vF0Y%%NPehq2uYEAxIDyF#U7MMCDZPLbtJR-525>$-trgCrisNNp=WJ z#|uUmYkv9cj*uyUP~P(mZ>%kk)e*07Q(&pkl#$joohIQJxm1)V6i%(lo+{-ULwbyQ zVZFH3MCD<>8#eb-J{717HG2^}-R)3S+gPToHxLjP?sAF8Bp8(d@2GRz*K{;Ga!Ccc zV0qXyGnLh35rXlB&FoDUFsMyoR0G6Xpf2*5*E>97NYhMbO1136_2>hyEN;@-+PQ`T z9Zp~L*W}P*iep{x7qLGbM)r;Ah^?L+%XrngNqQw)+QG%B=h2^xs`C#q$#gA0NM4SqJ*53%lWsF(QZ`&*_vK<|^RFy>LPZo{s>{eIn{l}5=Pw4AQE65_ z)F+0NN7j`<5;0)h0g~o9v`9e-uGjEv<9XJ!R(T(3eav_utQH`KgQ9e{CwTb#z2? z>Es!{?$r5QgsE1xCCqZp3jZXWDrY%TM%<> zC4Y*N@IrORc`EWnx5Wtoy3hFF7v{f83th*bsy+npaAo)=g)29Os36od4^+BcG`r)d zQRTdGCHO8)jlD(}*R`Gq-7xZB_q6f#jqw2Iu}Wg%i2aY=QzwE3>+1tkU*vrqANevN zKjTDxN#?GyxKaYcl)tW8bt%&Ga7$h5mKfFS{w`*H@h|qP*w>e>KM8!5e(NUpwfM}x42u{qLA+_57G_<8e3;`qQmiYcv4%-sT6>jXAjF= zmf^85?wD@6mNWCT>~&|YlGNhDDca1PgU5g3q{~KanAaHw#tji1gW^nqlplopy+}So z#~zott|`D#M#d@j?x!-_>0H(jGA8~WiVMRb`@5>jM++UCJwa0B>csSOpGen@Mf5zy z3<7hUKFYVUeW2%}xC*P$?P39@a0~jUw(twT@%bKhgk0(*YcgQW-&wFG)gGy>WAy*+ z&K|A3XYyPaW>!{sSGtUsnf6akJF}AKObQLXj7x3lcjtv^|G*N7p|E6_{>Z219uka| zw#2@iM5waO>&&>NDI)K<-!I-+p-LaI(H1-mSXYXYov2c--m#_e)My&YOB0;PIsSB{ z@_c-!$})F-;omw#=X!Ihx>)CL#ZrFpwO$!|o-XI7v@*<9+eI(JeY;u}{0YeLbhb8A zekRP!ep9N##rD}o)795|gmRPw#6dZ(ukTa;GE#cdm2mmyTZx+26xnka_WZQxi+xwH0EvT7*lC7C3ff%~F%yrN?5z zUr2@r^@e`V)(DoaB(qlT)ps6prv${Wh-c#McAj@f?JU#;n9sbneEdX0u5UiC-&XD6 z?ATk@9pgl^&9UmNbD2@n#cw4P_bc0Ec%ykAYJ!KUOQk<1l|LuD>Ke#wm?)yry-)n- zYF=k;%yCr)?6dav4Sy^pirE`a;s-7}02nl!jeE-r?iVZ?`y(@^AN)*0OvtriqI3FXI?(wDUJO?$5N+;(SlTpN8ta!k;orm)+9t2YQpF#jomSw_Jow<{ zt*XrEnqfPfd4#c=UtY!#yPk8ZP3JRCI?Tjp+$eodD^OefRLqq{yR#r;+HeCYUe_!& zXx3Wlv~DUtELM2#4a)c@>JoK;yqmkRCMcTmH21Zg$SDW-}w_q|sY`0(sF}&gsL6kI%S%Jls3@tXOd?C)2$RD7aOyMgAWw z>n)|`R!L_1mBiye^%?=of5J9+SvFg7fG|0w>VJ2hKk@QeuB~?_Vs459WJoM%SGgrp zWeB-wR{h(0$L!7gJ^y8!)cKx$l}-{A#gH6O5YA%QzN4AvCQJ#1ieoXr3L^J8g7JsA`tu@|PH6(F&Z0Jv&jn?R zUC0S7?^R0&zvrd-f;MxF>n9!~!ZFE5(ec9ZE@mgiwLcmG(f6qyzCQPX%hgIoyCKH+ zS6(}CdE!cOAJ7S!UP;^aN0fB9lr_SS`T{2#rPW`3)M=aE*7%U&AMU)DzWU)m?+_&A zB}Pv)UnZ`m%xd>ilzHvB$?=!!I8T0X)(-;$F8MD*FvBoni*~8V%F4B4Fe-idO zv2L^cLp2WMjep*Snvs5E^b*n8w)x1}(>j#b!dlzEwDR(sn81aFF67W^FygzmE07m%YG9&g4c3WDJTWCAMnY7JW~v!K3yNL(e(o#>96qe#rdiE!(W&q zX}R7H?cJf0OuD6cfvWP3-&l6w5)`FaWyTI`uas*n4seRJ7N z@HHpl08)8xsGNJV+9<6tp;)kyyz_2=J5|?5d5By}+M+qZJ4TbdB8sth&t^pIo`3!? zde*30Q-8R3kt%axv-R;6=AP5@Cp@tQgdg`gb(q0rS}r@z zrk8H%>Y%Ja7;PUA^T_2TNZ8hZ!@b;xJlpdh>HQ~$CF2C2Qq{&DB7isR8yA1_UT$1C zo9?E4s-DOGZoD9I-)-(a)&0HM|KR+UFY9+UFn%Sxx2pJy_%o2jTNzWxAA6$w2xj6SEhWG}^fzfK)uMURht zq+~sRKK|+LV7>%!@A|zp@nA-x34^ z-aPr|8Tf$3s4cv0R8BPJ2{rw6rq_fr_n4ZY!E_dL_>1?Wbu7EK%0r=JYHQM*szEYj zq-m{BNlQoc2135R8}inYvTjhB+@lwJ`gP@gg!8{@03{P{EwdF=6#tMgzFU@W(R{i( zO}wn<`Q!7pLD_)&-K$*+Usw#9_dC*(8v!iEGM8kuR zj=tpkFWDm(IJ>gR7bko-u`{%5;TAh) zMtC0xjN%p#D-h`}Z23??Hzww~E|&K(K)}W3H2u)bF#i&#%dquJ}n!zC1(B3M>@O z$5VxF4j717FRe$g(8T&?=NinEh`8&uc8)C5nZpvM#{{gj zeCEPm#U61m*IArXDU)XtM}?F9HS6?cdK7ziNTRlE;hav3LnQ|JgkVp$e?({KG3-~M zClmeSf)0@_d;YpRTIcaAM-4cdb6tz0qR z_Q0B7M&CCz3K?M)dzRY(1u~o6n#Y~4>$Ks=0r!7I?sk^-7p(_kWqM~Gj*owor+i4H zVyZ;v0~yWM|8n|Z=`7N{Ry*$Yl zi;kHS-e6?Oky#%0iwt=7O!AO8j76$TaMLj05ps5(k$96&W$b;E?u#S}QgDoyLCAS} z>073pxW@7KCPct|ucP4)Pc_*S&NFja4ViAGfXFN1S!t{Y5s)Ad2FRud#e@eS~5 z;Jd|_KT{#~Di&~`?J91z6gOmRk*CwVq0VTDnn6Jp#xrw7nfFa_iH@wEK3###_qhl> zXza%5^j;9ce9)u!I^s&%TE4f2{aVQ=eq>>JJsRV zNu@7rOqdQMs-M_D-!y11ef2&n^zJM1S)yXLKM!1cOJYJ#Li9xEV+b6gb7@6O8RFY+ zb2Z-)@IM$#LA5A;mRGcW#KZK9(XHF)zp$G9_gYN<+y1u8{oe&kwC;bAA6{qwFJ|cf zQ;;cn5%Vy8Y`?|0{r@h$1ybG~18#YU|1&3j5B|Rk#h3rXTRtKDKi-+!L&E=EM80d& zF}&p<-5>VT@hl(twf6HLKi`dPv{Mb%WTO-jpCx*IWWp2m=iq)Czvgh0qH)emL1uH{ z-oe5p%e9f4%1X)Fbjv;3OEu!m<%e_6zLzHpXjwq!mE!c*iYmSnFGk)|RMn8wDrFsi z=y&yaZ0x)bZ_{9oeahh;n z)SIz~78vIB(*)t!YV&9d|K4KS^5y@GF?U|kDWt@F(i<4RmwZ1VCL|`Xx0y_yP<-#L~Y4Yv+U1z-6S`ol(nkfi=GB zLj{qy-=gGMR35JDc|JB_`Jz4sV?oPW@|Hne>OOivd6eH)SEK1)lGs%<*(j1urK~>d uRm$flJ9BoMp29AR{sr1n_>jq)2m2FJQs2g2-o0~ssVHd3m&$$o`u_kn$x&1Q diff --git a/src/apps/wallet-admin/src/lib/assets/security/dicelogo.png b/src/apps/wallet-admin/src/lib/assets/security/dicelogo.png deleted file mode 100644 index 723b63b2e6750ae6584a86cb94ba3af2661f00da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3067 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

Pt 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 ZmE{2L6bCHqzs&JBa?;A5swGW={|Azy8DRhb diff --git a/src/apps/wallet-admin/src/lib/assets/security/dicelogosmall.png b/src/apps/wallet-admin/src/lib/assets/security/dicelogosmall.png deleted file mode 100644 index c2be4e4a804a6d5327d68c4ba816e25f5a1c469c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6681 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 diff --git a/src/apps/wallet-admin/src/lib/assets/security/google-play.png b/src/apps/wallet-admin/src/lib/assets/security/google-play.png deleted file mode 100644 index 5311fbf0314dff6dd64b120666457eb7eaadc86e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7000 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 q85kH83aVt~BN>0000 - - - \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/security/unsuccessful.svg b/src/apps/wallet-admin/src/lib/assets/security/unsuccessful.svg deleted file mode 100644 index 29ae859bd..000000000 --- a/src/apps/wallet-admin/src/lib/assets/security/unsuccessful.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-check-circle.svg b/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-check-circle.svg deleted file mode 100644 index d64ba990e..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-check-circle.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-earth.svg b/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-earth.svg deleted file mode 100644 index 998adac47..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-earth.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-us.svg b/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-us.svg deleted file mode 100644 index 08c6c8a09..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tax-forms/ic-us.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tax-forms/index.ts b/src/apps/wallet-admin/src/lib/assets/tax-forms/index.ts deleted file mode 100644 index 8fe4627a1..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tax-forms/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ReactComponent as IconWorld } from './ic-earth.svg' -import { ReactComponent as IconUS } from './ic-us.svg' -import { ReactComponent as IconCheckCircle } from './ic-check-circle.svg' - -export { IconUS, IconWorld, IconCheckCircle } diff --git a/src/apps/wallet-admin/src/lib/assets/tcandyou/data_science.svg b/src/apps/wallet-admin/src/lib/assets/tcandyou/data_science.svg deleted file mode 100644 index 4a5565e0e..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tcandyou/data_science.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/tcandyou/design.svg b/src/apps/wallet-admin/src/lib/assets/tcandyou/design.svg deleted file mode 100644 index 2e344e11b..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tcandyou/design.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/tcandyou/develop.svg b/src/apps/wallet-admin/src/lib/assets/tcandyou/develop.svg deleted file mode 100644 index fe60ce444..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tcandyou/develop.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/assets/tcandyou/ico-ethereum.png b/src/apps/wallet-admin/src/lib/assets/tcandyou/ico-ethereum.png deleted file mode 100755 index 907388abf20f521e040aaa6251f1b9e3aa2cb8fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2649 zcmV-f3a0gmP)Px<6G=otRCodHn`ul{NfyTs0wTDh7`M34j4j}Tq|q41kscex<&$Pwzv{=HZzjg{ zj7y>){3M-e&_px}ZXJ!NOkC(jyKTVHh&~jIOM}X&;0m&;tTpFfW8E&^yG$P3dE6Vk zmB732)N)V#-lp}lV4Q?6KB+eiaGWdQ$=zPkV|Qgi{j z04)-*SlzWi7obG~7OT4!=mNAzz+!dR0$qR>30SP|TA&NiA_0rlT?_2b06RN7Ep{*P z)g5htMMXt)>h$TrQ?PIc#{zu!?j02sU7?Khbb9gPg@rB#wyI+R78DdxLt`Voe)EQQ z@7^6)3Km*-B*16Sp3#k)Hz_0}lp-P`DCg8EDlRUz(9OVAbsRuU9QhY7QG0tk1qGqw zK?K>ZUAw5YwKZ@gdfBq$0D3$n^!V{(wmx}WBfAwA7DhKc9y)&fcrUvdxN439*wD~G zzZMiwaB#3sj{$LSW+pv<{@f?ePJyEU0$yER9Ss=Zs}?Ns)vH&Ok>RWf#aC0Ce$L9u ze^W`xEeZ_{?N)>E@NmlgXD(g4cCFhoeVyS5fS52YUb;jb9UVMz$eH6K2mP9-6V8(p zN?IDdfB(L(S()C?5dg7ddGh3mF>wGC{9tEihXkfW9XN0x-M)RBj-NPT+M&J{+77Uu zeI6d!04#`oUr2BWwY0QQMMXIk78X!-)iY}31vM-WAn}I@W#7Ji^pw|&b|>2bUjDU! znwpxW?`v*qqO!75`sJ6O*&mh=X63fFHma+wm5+0nVDKFi${RePWMtUQkA7cl*`P{g zWhIrCl`+6Z!gWdL0Y3)%1EnY%j{(T*ic_jO>B+f{B$y8$PhYv?i{UHv4R#Z zTxf&#|GlMc05MDt9y~}_c}9h0wYRtN@+H{tgJwhkQwTw@)OXFz@_O60ZIqjvOGAg+ z6Ch>V5C)7%lPAmB6gw|~2bsUAnE?XYvSrKuCv<utFp#0T0D-UdqoFg#i8*|Uev zoj)&^F_>jl!m_4{CklM31j~gFT)lcVZQZ(+CQX_oEwLx?Ra;g+Z4Gb3tXgvGmVD~i zv112i?%hiX2?_L`tr6=&?*tjF?u##ylpesV`)7ublwV^Sk%%iN9~&BL4Axx+p6xlP~~w=gysCc0z&#xM9Nv!MoBj@9f#L zA~iKtOrA1DwAZg+Pg^!`)|=_`YxqD%0K~+QedrLc1FIzjfJ~T^Kuc0m zi)RdHUe3_gEnDOeww;=$fTI9HuVKT6Q|i*Cnu`pPJ%D9a zG#~)6e*08XKmeZLr~$KW`ThtsAOPRKeJi(~`Xey!K3M{YgCXqEc_+1l;w|rw-z@<| zkPjjX0P<5!mH^^GzdK*2LNoe1Skp&2?EDsRv?yX#!UheZBtDD%?6YWNa}&>}#n+3; zC>n5ud7#OybN}<7cE*Ocdu0e1f`>XTwI(I$w)7} zIuSi}s+7S1plb(@qT76kg+u&Vvu05&KY!y;3x`<<`kc$VLsLK2F&g6w9}C11Gvg+!NJhXnKNna+O;ymAUiu-Mr7#)FLB`IK0ZVr#egFtBaJL{nvXc;w%3|9Yh>Pm z0|(@ZohpPN%*&TAtFQ7CQC%e*u&xpy<28zV3@SM}nHKTi_3PK^*s)_$@1?vqId9%P zI?s_42r>3a-si#|k4K)jGDzdNm90o{X9X){$Z)|{As#$a#jP2XE3%;2ls;#XR zSy@@ecU+5x~)Tgkh$dDnQ;|PuK zxM1+&%$YNW41MEQC|FW_>G5|mdKK=tcpCOD$QT)}^)EKZ~ zU9cEf=O7-^deY-olpMoxB?GjnQ>G9$T$PUaWOvfV z2dWM~d#MuV=4qe44Od+uqlit75UaK(lpk8FMyqNCMOEyPHmdfhRkf)R#HzhV zXtip@9x-dY{vZG6{qlae_dd@#&pG#;d!BpGxiQ9Y9eP@JS^xk*ucxbJdR>kGFKDQ) zb3DU-`E?=lHPz7oR19$vuSM*7TI!Epk!{(6+|A9|FAcuCOYR(yi2Taq-7U zLVf0G8~^d?*<7n-K+BfaPS*VKw$iF9!M`=MakZ{)Y)qdLw#`X)1J_#_+jOvE0pqhL zW97XC28aXrweEGSu#o>Z;HQ8@ZKauV@NKSI&Ni2o(Xy*qU--7^Q`k6+rh{~7aw)SZ z?cdO7&75Ch!>HXhyc;T1Is~zVc^bZzc%SH&tCm7`Z#jE3|Mty5NSpOHsS)AT#l}hQ z291x=n1-MR;!{M<+-j$&zcP0EgpzzM-TZ3XNA}X<5n)p-7{SrZonwE$YRWo(^`m@f zCZHR1e7C6Umr-PGgT;;qWA5e*EBbAmN7Z(Cf8=4B`ALg%<71;pk09Y>EOzFPz@S9g zE+7y7$X=s})txwUshb`UNeOxtvk2(liuvzsf1pF5vDLj#tqA5m=|>D1wL!c1MLPN?uy8 zcYG3)htqz0WN?Q{N5`S8GwO0rs`n*6w~e2DucKEQbT7ul#B~lAxBTHR*9q8tPB)yM zwgjc3XLze!rc2!X3$mnV<+n|~ZhL;J>wvDl+VqU86f)pIP{?NK@OKFzmYu8uX-7Bq zBo8agQx74%>xuaBTXj&*bNm4J45LN3kRZWo9F{9t>0nPA@?r2u$^f|C^T5ig1oAe+ z`n;4%DY#lJi#fYI{fzJ`_dH#p1YC+3?~@eMo@|Ru!1RANVJmo^4U5_qwknbhm4B&H zKKqT7CZe#5^xb^k>?CsK8_vHebzsZ}ZDm#gw-ok)-zl)<-2am2R6S6E#F;p}`j zhysTHu3adkYu)tzMY=M3{Jq`v$QS*wVvv?f3a&_hw-hD4z^6`C?}ya3N4%s_yrNPw zCia?$#^eJXDceStpbd>%AKIrJynB6QypqFMD^(G9s4(vd27*?T8l$qr&+oBoYVS_q z2ftG2FTKT9Lf(DTxJwQ3SCfGzN6aiAr}rbyH_|&AW4zZS)N=(p?9Y)PnVx6OCqIbl zQdwG1gdd9q9ER?i8*uWN@~A2%Fv-bP3mzm`MHjO2h?`h)52t7OJ050Rk@Q^IUZE$V z#pY*hCHnFJ3Sxcnv!8E*4PteY-yiUJPVoKR6b~}_(H(uuAfI4!WOrPGA6|c?m-$6o zbNY4a_mr|Tf=`0ji@&LVqV)O7lb-}js%tYhG(WR;DA4Z}H|2PjDZVamD)?kN-96fD zKaE0f9-c}DYnn?M>K$QZs2zC4F@`^iFpNsVHrgBQF@5oe;ZY4%;Jz;%_8=Lilr3?h zGGFH6sn&#bad%HF>VCn<=eNULk)!HFgW@_yWTFJG!n_kvf3-jfVma{ISzY!-#Pr(ovK}WXF5EGkoZ06YcAWXLxeNoHnl+?IbRQ(%jv!BK z0|44?1B6}XNKBFHQD_RXai`-ulA{Gcw0*+IWSIdzt54NFV0To%; z{pROHhFdR@<}+9(Vd}4{lOJBVD59Xr1Ua5vT`kTw+6R6)hnNo3CtEs$+2!`LYDAFK zX#D0MIW?nhk7|Fu0-v(0o-JIUPS~#&R8nx5OaMbsHJf$m@7Dp|umHBUIO!#&?D!1d z7C7!3vGdqmWnhgfvUVwV1o`RJoW=<}e-I$U;s@oK+2aW?;v4RUK1%Cci$X~qKyXbL z<;b}`l}bAjO_SztJZ^|~{A>;)W9T<$JV%L>v&$lC*8UB27`L3EA|FA4V*p`Sfq>?+>oq;K+}P{no=i z(Vbx;r*34lcP-7#@(6gUi?BpXM6#u2|If>=E}~qM`UrJTd2z8?K!5@xBV%HEdf^L& zsavY&s07tZ)`g}Qotv}O5jJ&0!&Oz%&8I6Fx&{XL^V5UFGwi0qgi=x*hkVIb$DR@f z{)fdNCZXalt!MhXrtg-Ie*}JEm-qa5_4-ee&ctY*&I@JLqwR&#?Z#E^+JFORLG&Jm z<3(o_y_s#jr-!lUd17CwB_cqZp7VimAMjBExAMeC1>cFt8+U8I$8#y^!jvWR4BbMr zRDx|1cr)_F{>cdO(%QUDi~Gm2Vom$y_PjM0^p!D)g9OD$A}b!obAGgtP#VA80Ln2^xJh2Z0b_3k*1%R^A$1JjuCW^c%m z%zz?iha1!mU_{`S?85fAk!qv?4Uo_EdwW&rRY>#25n*kt;Kq;O z@LSl~Uijv{h$DM0e!SqH^^=P-VRF%5go@F_I&pon6q6MU4&3XyJNc(CCC$KKp*g?| z2}H{&M0oH7?S`={`V}jNoHww`xF#xwUN$=m8(yUtsf-FW`Yh|eoK{Phcw91DYu_GD zwcOR!6=GTGHeEqQCs6$<^fG{%i>qAv{{2Jk?pD$m3~Me7|2T36L_0&2Le_Gk{brB0 z2!{^{bslEGGR8#aLkcUwR!eO>j!KvMeDUTdCI6e(o+`P?=R~;n*>1-TN!JzD67&qI-EIyq*N26Lg};AA^Gu;3Bw$;gy?zlq6Bx_uncu#HY1 zdy&q{dw3?Rwh(1}tt*RZmimyrWCXYNJQ~rNE^aGx0|T}W4>(vM-m7&P{!|HW&Xb8k zjdH%R#$L*pW3eYfG~|bPpz5`h3M6LL@>Q%Xeu&8=RjFrJ~!1j zdZ%ea0*q*$`Jx^AFjRDfQc5+nF-OUphfj4au!d2ArUD`M_{LGe0^Bre0%(Z zJue^?XDBr$I>lC&2VZO%janM9ndvTev4#nN$;pM8XL!k$wplS+5RaFP`{a}7$FVHp z9avenb0(4{#R7B}N3OV+FiX`-11UFNeGCh|GnmrgS1x)5|TaGcd&xo?6)7 z2T2~gWlAG`VQgZ;ARte|5HkooXm|0X7$)KHyLo$g2`VLq$+1uHsm-ST>Y=^btE;O^ z<@G;0921laXRik3f2x`CDDazQM?Z69kb+>ph_$8isJQZray^7fhJh*>hgk@f$e(UVSi9F^B=ur>8fX zq0M!4F=1x(G<0C^ebk6K9ghm<##DJHij2I5m=Z@3m!V!q&kePz_MfYHI+rk>wYt1a zl=`fL=`6~=-oT|k;Vk!kz5Kc<<}+v_lfd{|w651p1F9-VEMr(fV%T*HT_dCEwo`TA zoKFJnhTD99SB9u{Y(uGi_>H)e*D&in>Jo`(QO^f9roU`7O1b1ehf^eFGd>s+HKqac z@b)=jRd)IwR>Wvj7vAi5J*!bU>XMbw25vwhw-=Ne>W9d805yw3ahYb7QHM$bKo%75 zFA(x687p^QBK!X%-6_4@i8dDh`qDs)jPf%|YrkvqKfVFa%!2r!A nrvH^#T*Y`(9Uusj04P#o7b#K2j%C#-iDlVoijz3`>@P~(X561y~J{x=P>cT($2OraauPkKnU;gL!vAVGdi~ob=g~e~sp8*g5;qiuGc^o|P z^kMw7zxNa@yN1nN4k5n}UU*Qcmaw_Gh1sQLlq(gqrjvZXjaW3o>u#Y`EF%($z~;5v z8dLCw;`siL-p04ScLt?0Z_be~hyS->^Sg)duGtmbHPnNH`?uqJKRko3t~6$+*HPfL zYPA~dYEkbs6pW*{y9NK|pML~>1Ksd>Jn;Iw@Oph*hs8_RP_0zuf~b{>*!uHtqxcs; zK%VzkvutA%$3lQ>4e@$j{%pzbTU!zOz2B01k&BezaFJf8f}{KU@pqnl5UE5GjSVSW zy*`EY%}v-gKg8oPhLiVjF%~aatrQRrRq@3yK8Y{>@*@cPe5mq6S8 z22%@5e7g_DQWZ;^Sxn6?qAe9gJmAA3jk~bEj(o8Iz7<`~4fwTBzYo9tFF%TCB*@=* zxEy|_<>B8gRC%u+uLo`IY5c-7@55LB;MefX`;VYfu5m*>bbDLZwpFdzAaF zRjb@+*>Jz?JlE~$8vOnMU(3zr7cGw}%qDpp5B&Ve%O(5x-MaI83&D7tPRGx(`rfZ!hn=rbe*5j3!5&TMa5+>7~(P+=Lo`S934rmDk7>^@S2U z4yv?VfDT22)+8W!V_sf_ALtEo*D84X%m{w(|Na>N;VXZGOeU}HE}`V%Yvgj4go1~L z=Z(aY4Y=>nP9(xXbTK?q_LyXIEuL}9y_t~&LYJFEq-39RKj0<=VkoC|Nb797nbqP`RjQ2 zefyX)OnULC8Qo~{NT~YsHQZmnEKm0y;vt^|37hcEWZRqCmV zpDo5K(}`-_7>NeHwx+JBd4?~qmJ4)s8{c{EJgUqkPMIl^`Nopn;f~t1G7ZPeJCaNM zy0TKKhHrfDE&RjJ#Zk^>nEy&@z_yk~BvTEzYezqgOT%q#;lj-kl=+_ah9vSq3(q|H z0OGMYy>1H`=FNsg0-0hJzGwh`E`EG$)?^LMeaxq&4TfZvD+*$5qkyk}<9R&y>N{vo zH}bwKy7}^2zdx#}T{d2vLn21jgG*48j>rSEr|{k3OUTNh)B4l=wX}LJi{Gm_lHrW!J^R= zHnIhzlWD%*i!zN7jyB@k&t2fcx$kzF#>+uBoKX-6B{4md!Nlzq=0qFw3v+NJI{0@< zMZ5tnFsk<^o2|K#Ym-g)ivz%fqa|J~#l+%u#npW#-+K6Sk=H3ot>jw#;=g=t$nmstu*4c}dtsHuLdeK0yu(<%qCne^=06ndRDIyUG=uI)xU}GzbDi`bZ2U(25 z`g+N8QmsogOhZc=oo&mwJvGb15YyDbq^%KCEaf#+J=}yUBgJ7(mZBi{T&?6-SZPFh zn_Wwr>TOwWDS)|-ZEHxHqR98x9Q_`F+_MGadzKp3tLeiOlbVYL#4~)4zyBB$^%_o{ zx?yq#|L9|=I&ven?4+X^LGpmlA3{1E#{(x0V%P3o)aXM0{eS%sSvq}%i!IdxNX26G zo*I(z1Y*Gu%F!tDs^4lCO^7=|}HlqE8?Zi{elI^v77tY^rNZm~UliD0*cnn_+m(5IfoQ zE+9ffhKUI`HkrTV+Pq}IZ=skiBCxfFrO7qS%`9VTZh^*&ptZLXhYue_WM~g|4{bwh za~gYg_2I9-{sOWzo`j`Wt|DvaJ=zko%+VoloCXx{Ahs(r*C`6sI;zH$f~TG+MmJzq zy-VPXq$9h=T*$oUXdyD@FY{XBeGFXqo-1$wUrF+y=NERICl+M77cmj+{^-2r^Zp;SVMDj zHxjI*%i?L={Q@^BOAP4eLW}Dwn7{Ha$}{t@1oSdDg>nUWR#$OxxE3|*oNd9Uw%J!(wrbx%x4iM z23(!Ig^|}!!B2xW(vy=NZSWBORGEL8S()1y>WM2iadBo9r(QmT00eeoRj z@9e~zZ{5_eS3F|f2}0vx@+HQUg2C|iXf=+hVa3VHgehJY2}x1)P#42Vfsuls3_uXE zBWAZ;TxqtZ0P}i3uODC`aeQ9=EZ>={6EqIpRy?7Wr?-b#>LY4EN1~WFQPVeDx&y1r zTlnhNU*;mJ$PkwJ7^a^-wjEzOx)bRg9Vi5=>gGLR3)yf3CYKhF=fBrhmT~j^IV@kj zgcd@8Fquw_`B4grRQ^<32a823w)J*kpsyX>=ifz<1;tdJ{nOx&Z`i{Kw(W!w5r$dz2kxE&$bC~k6jN}7C=n~ zP*xJ;S3qpxaf^yrR{*NV8|J2R@#S?SxWMLAob)TFRmp_0SCeLyAC@cC%+ARBc=27N zNWpY7{DkfSLXVui&KvZikt`w9+=!*cMR-@1kSb)6>g+&*SZ|e#V`6$vF<4V7g=jp+ zqLhKBlE=Wd4h-!Zf|YE>AAIv1&R<_JRZl*Rc}l=OT|hahaN>G?y91uJV%9WbJTQ<$ za=edu-RC!uM$?mJy9KA77HlmJS_v$dV(`0PaG!Xdeu4>a+v9Ny3O`6cQ1SweD0zUW z4WIbf1K8fzi2Zx}$ctm#4VTU+gfJnlp~0_JzF&(1D{OIT4dDQI^&$?YMY(1as>}1cMPoNVSG9593>B$B<8S5lT4dB%U0d&0v|iPjIEM z3gyFYj_&JiA}WZwxy%$mxi2VBH@}gM_SPx9yXk5G7os@sTJ-NyMClCjg0fWy(}DqE z0JV??TfEX-R|=L-c22_7HlPo&L?aIE>&K4%cKo~F{AFCeIEMdmZH&9-Hx_j7Mqz+u z(EGcD?#}8m&V1|p*!lNAfzIwUukOMi=C*2~#1L&qV^1F%#%`m7o>?VL3xt9g=;=VJ zp#imA9%<&nM-T48uKs?c=(%97k_v9Q*}f(sg2!(n!V&|v;8VZ!5MDif4Xq7P{LSB< zC5H8BQBioK1)`?WT4%@tl3c|7>5pT@?OOK_OK zl=w@&q9Fs(1}=i1lP{pTsf{V2fv~8G#y$HfM5It&TZhAf6zaMQp{*r_HefpOkamg0 zR%P+f>+v%K{s_N}HW##vLL(r)?9}raH@j9=PHbdLiIS9ow2})0KBaR%4~&iUVw>>I z74_F1*9%LIhzn4|i>zg2|5u3R3c+_bC`lw}j65u|#B zGS#HeP?n+*Eh}iS5qI<}KRJt&k3E2vG>s)NgMXKsLN(9#($Q(`8pTnYo?T^b+*n#g zQglXowKq+(ROJzqz45`3UFrFEWedsk6i%W{_q6E;`S92RAp zsUjF9(odqqO1-qUX@pESuX)Abib<WtShXj8O8`Pn8D)2QD|s-)%Em2ynw zGQd+Sl>AE|4nL?yh!Kwm(B7I-jX^>|D=}YOVxIihfBb#CGBJx_XODV{O;58cB~-Rn zQD*+LY3MRNyu@8^Y->VhY7!$C-ofyhx3N_!>HYiT4N3(a=E7UICP)>xG%S^odkhE` z!N6iJzHnt4Ynh_?n`ODI#G`-rs0QfNr2@C`FbtU|RHY)ZI)Hn~INz;P*#D7m#F6_^md z@R>)^)YO0@M|Po$hLFlw6O2YfFOjQ;8`G+cI3;NnF!ZRJfL>iG=dj7}D%m2!Hc};~ zGDG~=%U(tM;BEw2ylT`0WygzKD|r3VE%fxapr^aZ$WC?Gp#@ZWI8sFod@%qU%6wF= zcV#U_f|j9oO6A-VV{)Te<9kkH8^gMV!s~jVyhQ-CRAtL?`Lo-n@$~ex;-CD=N6}93 zJiof3HzGQ$z#D^X{JMu8UzE%tjGkx zV%3bmRde3EZq0pf-Taay1UbkWeVPlnz_L=glG#;PzB2I34Ik#7eRuWXk>jkKk+6!K zXWt$r$1Q5On2wnTmuMU@oWOzVMxLrjk)Bu9#rjFqn^KL4)*{HxZLs$;fk--r3?;J2 z(mIkOKZ0Qo#%7kV{pejddf$Ew^tPj`qY?l8e|(Rh<8$$+tj1D4s+;cAqsIJR zU*pun*U&C8m>SBwxUQ8L&sCsKgHvj&z*OFv3yD-Z)9-R=$9S^Or%~a%O*Q5M4bj%# zgr3$0j8D(7Ql-@GZO3SV4y&JB{M^M`q<8E1kH7tS4Bwo>`%dn~`FBS#Grz`g4C@Ep z=HDIaf0lq{=FN(Pu>#bxUMD~qC?1Zof8j@GZyRHkRgJ|dGSGr>Zx`En0>I1o<8PnF z#iarg4GHx3v}uq<<6#X=1E$qZlHScmyRyHL7tE%sl`Yp9jGNVcRk?FLH#({*x?agI z<+^pRa|FUTc<@Xb@wW%D7-)~9swzVn0A=;@@~?-Y0sC0;(}hOLp@wdy(s z9BYnrTyX%VeQDcnZiitIhKaZwTGq+GJ9VkPp8McBsap>$$M9JZTOleAS(5MKjoa#N zx?=(-{7Eiu*${0m+@?23Xp4bGOjyqp@WSaEdP5Gq${5}yIIO%Kl(;N5rfh;z>1`B? zRD%h0gHra1!9rAEY(GPUUYO$VQr(?Mw6-JG(WPxgX*~V6Z@r(Zk4bg7L(Fj`MST&e67owkh2__?|FUC;jy( zcWb$0USZc0p+YLvw~g{3I7vx~NH>8v^G=!tp{OVyE`)I08`LJd>-s{CYp!;fARm3? zgitd=i*i{PQDJ{1li5Hp8Ap^VjGv0Lmw?vchlisCy2N{)KtLf+jRyRS@4b%e(<|Cy z6^wR!dd-Ba+v|`t;L{3BaaQuIBz?zr^*y(Fsa-gqzQ(EgjMB{4h^Q)?BnuWmT^CqQ z6UsD|R3Z_|=n@pKRqu>AZUfkax7@RMt^v}nB2$-l#PbFQx^Q@3Ql|)t8=E+Lae^Nt z0E40FwKeHTfsj_BR6g;RBZnw{?!OCjb`eedoBAcwXOZ-1iA5sFj$UJXD~m)#b-oI{ zvz5)KGSz5rkVc9%;~OttFy8Gkf@!T*RS)rcO`oHlW`uSfKG4f<+$JWcmSHNhJFejv zHd1Del2(LJLFFB)bUE6zQUzMW-BIHSQm`sD-Sko&+SDa20nU~I)J0xfI2MHu`i;T+P9ASb67#*FB6g<-SH^1|B=A@87K)xp6CaOA4aa;flmF~S>18WS9 z^r-QinkkeXuGMdu8dA0laHp=M!i8K-4698kMusTdK1KAEvYQ@BqN(4Udv}WA7Dizs zhpVGg=q0$gi=KV! zI*U*UPqm)l%RJb*qZ8L|%%~n{@LpBHJ=_T_DI}G0mEIUILr+%3zxe_hp8MrAkeRU^_sgtu-`ZYitxl>~loe{|&PAN2HMO zOOm_D?0)eIE^={t8o~S`o_g#!CABx*!csPU3(GMbGDbk2;_Jv*0qMphn%Ic??hoF; zj)8W{TjV9p-rQeGK3!`epW<#63M6s|Kc}3f%{lE zl5D0+yD}P!V1D{GHVb8hn$ifzk z&&^@-_5_~%*e9t+HsUY8`K&Z0P0v<(z15<&C=EUZmKRp>M}PbrR@S$0V|0PP^SjED ztw~FK%pVNHa#d{HA#w(m$VH?oO97N}tJ7cletN3~o#C;ykeE~ublwYNDL5k6`Ht@G z$Jt9a`AJN))crJ}>J4}d43JVz&#Ut1DjQDerW6Ku^y61P_c(gnQ_Occ^{5QLXVwWh z$VfJpX4p>jAWCmb(-6H~4`OV57#SyzFgdr!ZFp;YQTny+qN6RX*kI^Oq56f7AII*2 zX8haV{Td1)HEAXG>z72I3_MNFuDkk(>mPNd;WCsuvePid&;M6%gt~nb4ReDhB`?Zf z{7MI*l~_@6wcmSlauT=ZXU&k&wr!|TNp_+sZ2HS6GT-G$ z^`bPkFc~!nxpwK+B(|tAR*PBW`M#s~?Zuz{?=Rs;FJ8fSe|VmZCB*P8n!%^Ko!r2( z-0q?IgJ8U>qPB`oo5c-&wngGMO0`c#2d>ffxaO?s-*v>pOnVq*+BICA$JCo^D%S~^ zZ*6VFGfyAK+${SPR2WoHsF{gQ(|`5oq>@yxr$6=#e*ZsxNx@~FIIp?21>3jvDGXYt z_}SjlqKdT)Z>%gXQN4+wfi25h^Ybi>VXRk(7sIVcrdzlnF`cn1(^E@A1qo%cl(1s4 zkb>MYmGGJ`%usT0UO^1Gwt&u# zR_3NSKXjF!RB(qaO}L0Jf96w9;MYI@QC80a+Xkd&OzbkQySTQhZOOK_faW$rLREUV zwYOtxoFYYUpZP#r29DwwCk_mUdeBA{MgXnhr5+8*R&EGmUyYyc=pz=~M00y5#fKo% zM3CND!*Bn~XYkV(FXPmk*ENqB!NXSJ)^QhC;pRGZSf+H(jCBaxs!L#&JBDc(tSdg~ zkf~)juUmoTn;5VrK-%)$8T$yj+}m^R%`5n=fB&brGBQSVEi@~p?L$WzLaxI5iBCO_ zFMR&P>@NiM&DrdRcFA%yX4O?P&YV4iGjG4GB59WSuy6NnTsnIm@lXi&>_3P!J9GJb zNq2dkDszbx%NvX#5J@u8Q;KI{+IiQ09bCFPK8Z5JslfCQXMyVP>!&euSXf>lm$tc? z$MN)&`|lV-4I)&t-V3%3ua~Bj{OFfqcb^ya`k{2sG9|@`49& zO&KZ>wNx09-^^KihbM|(UR+tj?EI>#U^*`?T`+n?h$_kF{_bP=%r8EKwe@u@FD+wa zVv27L>wJp1aXX8IHeMK7Z=X9$PbeeZ+NLeg`GsZt#W%l;k39XPj<-pJs>&oH?NaeR z={b27Mffz)7npy?uV10iki-fbPF1e{z|bCo?J%2FNg6lBrhNd{M@O({s2d$^XoIl12zrn~b7@F5H%4&Z-P`pfKp+&*Q8*1P*JBkoO8%p#R-S(y9!h+&8RD1H zfm7qbbx7N&Grs!{A<)?h&jxo^0FbCIMilVVixOOvg)e^LDg44y_v7a92nAQVZ8U*a zhNe&O7DGQtHKo0|Sw}MlxAo%b#~;C!OP6r*@>N~L@slSpF*<>3!^7-ttP-SGG^dD8 zC_|O<+4F=TVsy!!nN@QPLi(0XEp4jSN6Ud}mb6<9e*)YJ>If#wQ(>w(b~J!XOHCM|h=&VcR7ItUkhz&wcI}@S%tA#qh`odi(m&-PVdW_Bv#tZRr$xCc$4frfw_kTgx%& z(}?3EH!waqt^JOH-fk-25sZ#f=C9UtyeE}xK$;T2@Yu%2MpC{US%T^OLip%$0JqB z)~0np&x+6;aPYtm{OadEjMdDVs?046Kbee|oO57s2U@uZF`gvwt*s5*BKVaG*GRQ_ zXlM{uFJ8e2y)I8LeenGcVC?EO%#vj^rW?r|QkvTw9mB2a!I#m=Ddd!!hG!4u(~-uj&llC%1 zFmTM5`WQo31c5T|Q`(&ZaisdQ4~Sy@IEQO^>B^a!W1%F-#P2s9yOoapy4wT)9NFuY^+7y#hNGx36eU3fqtZAwV{c{Ze^|>?B@?TU$gcC>Ez7^Z3 zq9V=eu*yHXws)hiqZ#wd%WAxIGC>2(D^Q-L(bgDxWrmwjAZccnXwXO`irqVR;M&zI zs4xtU-F*;mu>#8kf|qyq$YYOSd~^(xlM|Hq4V5kcFnZ_g>I!5OG9rB0)s6fA=EeL56@x3AWlDy{_k8@zgdqp1-j93*o<1x)RpJC7* z)z^H!IG%at5h~G(T8XcZ-qagPGKAyKH6%waAs26D5~bc(2w~=tRrqMY3i}!}+?cJ+4ZL>iHlkc) zu~<+L-9Sn=(7zowZ;f+f2HA^F;NsQGsPR7Y%$b9|y|}H3jFtu(h^e9N8wB_VEghCFEs-+zKwSq z8nI08U0#4jG3VhFBe*7dANiYXw^ z8yUWO9k0Fm8XA}*C+B9d5sM?sgq~;fsg?AwVK!xWEf+gVPqt)=D#5LzwDs1NtH@Pk z7?e4TP)JW0&{_fFOVu#lx>}{DYs3XjVG$WjQ=86HJBF9nY-Q2)^Kx)uk>FQ3_vm#~ zgir7AHRja{IhT1l~-TKGfzFGVHymFh(DJ|=ME|F7RVIh>$kYU63)GSEv)Km zcwyoK%Jgt;;uANT6H8(ca&#GSs~JT*5DaQKTX%4v+qL_-!cuYt*!q87Rb?zasdT6F z;xos`e82X|AIC0-`2v}cQUc*Fw3Q^`9X*_MP zDK8TXF&a|Zic%C3tnN21yn~Y`j%(f(Lu_sm3r6|HG$U@0@aoE40)bc^T`6CM1Ue_0~hW0_QBc3J^($^nV8ko*|nz|_pqqkM59Vh zRxQg)=fyI!#L)h|*wKF)(-XHay{%sr6!ElZJf*^Chy*_oDQ5)+%R?$?m863)TW7anP_{c|} z#*cpVEHaFM-8;4uDiJ`36H3Dh#G+fIkW~t{K|-jj4EaHVd^y0=%?4PX``oHG8ou@2nQc{h_Y2t`?I3Lj9eeaPoI4WKf0Pn)45SRI5dujcKUGFl_;F! zJ=mx^yYD%oJBWIy@wGeedA|h!X&x`PrLTm|Md0BPG0Wqk^8667DKn z7&uCg%T{z?==E1l=~0JucH7oidD}X=bf;UGBC>@Ny0`V=l~-OtTTc(g*ErV6mt|Tk zOqlWHlaJyQIrTFi`3PCYhK`7AQ6>|wyEQeXVBE*>7EiBKQ`)pI`UKpX!Vp%m-yPdf z8@Ww%X%BeZqY^sl3UkQD9V`;|RnvZm3FDH$7cCt1Llf>0Qq6=t1%;5#m?gDclY@#eYnI)R@g3~5d^Q1(w^Vrm9S0`Rv^pH2IM>G~aQ)U4A`J=Ga0YHjUze|7r>>@4SHG%; z^NK(rgYOlB_bNfXoLE@gTnj3_Np3)a3c%!Y`t;t`xBvnIjmd$xE-c^6PuRame!pNe4%@&Joc*)-O5K*(!)5K>5N{=C8*wJ1G~ zd_gZNAhLdjOx#^uTtYP6*rekXU8GiXvvcIRWh{)0QUe?#Gg(7CoiN_Zn~KutQ*-l3 zr<+x#O4D0fRGRW5&DUI|Vsr4wVTBs4gfmmjjmB$DPlUP9=!=30VlRbwrH2CdEJnQIFeAaG$>t z;({c%?IGyiN4U~OFA;jR#QyLgHN6I^FLJ{3#@KB={3nZUXVQx-8JT0cSM}NB7LSrb7)QrY48?;UzHzu2 z!d`qrtVNn%U0>#cJ8+#`cXWob5O+c}Kyh=^>9^I0UTKJXp^QN34Lw%ECq%&Qr$==X zdSu8tuH78Q(5@lgd{v)OAmV`#cY!R!Y+p+CEGi(j^_XjtV(B{d#{@xrG8)oR+8W>U zz|n)Ge(M+@7~i>LK+{PB`z8x)PW4kVTV`sIhf|2ISEC~1a|wiWZ$g%N0*4;sayxf- z4qU^WK9aB&)kscf>Y!%LRXU zk)`3Hq*Su#e0EFojm)D+nJ;n2>qjDja&Sd*p6n)ZQGg<2cXt<#o;bmLm&Jt}Hz_4W zH1DjftYUa{oMLKTj}yoSXyeC>!sF1xjqS&DRVO zT+8zqLM(JLyBKDah)yZf{s9`^43nB(aTVr>?wtmaU4IIaoNlU`oG6OC*BoAuO%UN! zS+QN8VlawA&6Olnsuk^!DM=o_qX|H6ZeZ7L3Zs7Q4U6aHxWLiTTP!Ad^G4f@eaVMr zH|23nc`6^&+?Y&7S(f2NN&ez3ee{+ztLr8K^dT0RoK!*HP@dRO!l*zdl2=2@1Z#<4 zcnt+9HeAREsh=!xo?hHc5#lAn9O*4jOVOeC38Yye1IiLDgjswV6LB?wgmTbhrt@Vc zrt0P`j|)32$G&rNLmP2A_G>21WKE(42sLwDBMD`!a3hLt7+V_Bj?T)M$EoOxT*XF{ ztc5)X52Ky^4oUEFT30HwFbBm@H3LZmGm6WL5#(75;$1FHBzVs2DRrS-jqHa^PEDh4 zpil8tzEIM<5)OE2^n#Llff6Faw-ZQ90cvAqKYQ*Sy~g;|9L3UA?A>!0T9YxvlJY33 zCRJLbVJ}kwa^6^sFD=h7NkFkVrP!|Gm~qPr+@lThT&zmfJvdaKDyW)?tx8#&O$uRn z-vYGtH86dVf@3&wPGOA+?SKTc*BtxxnB6fa4_iAs(Z6Gt?n+ZSZ4kXa;;E*)ubw8J z;xPk^l6b{iiwt!!hOmz<_Iy_;FZBqlp^&<7&u+^8P5Pj*O?u7r%)Hxed+&KxB5AeJ zV+-`g7&q>nE7vq%%A{3eS1*=m$fcPDH1cmj8eDqCVdmnoTelQ3j8n0`J->hgMb(<- zPq^(}d5Dfj&(GaylDqj(pNXS`2tET}ct1KGVwq^t2oQoB znL<1@+odp&$B6iGGPx;xuNQTsKLBM45xz+dJ^uORwl;ewcaq zE-KQ3568#H4C~;vH@9+{{{%Ya3q`%hF=ET+o-WvgPDVYk+;Fo@E_BCu4dbvCRNJn~ zEPWfVv`z`96kTY!@IWSNqA&-py>4EWTqsR{Z4F5Asf%8Ydt5ZccHMRgshu+S(%r%4 zwE@Qhc;OxcC`QKi9P5gboX_!<++R%m?QWCmL+GeFptZ#w`v3J zD4!qJuA&%9T9iIoHpILe*jj}hO`__~Osn>yPwg;|bJAzd_yaBwGDkmz(Fk12>$7U= z%>nalg;+y_^h``B)`vrq6fR{1qmMMIsi{$`PeBgEkcO4YCCw)_J#^z9d99f9zb(Xa z?X7J(aJ0ZmIZh@aGl;vlZ^xkn`*eiE?>94hOq@9N;tR}QOPXV@jEyR+kPhDUTVt3Y zNS3~aNcV4@KTn--3wIyCp9US*Cn8D9bA$X?Qo#7_Y0ZlQ{I%gT)Q5^dF)E^4;wjs> zA$ND9skVk#ei^NH9xatDQspf~OIbw9RJw~KZn;hE_1e64MIPqjc=WidaA`51&I7`D zx#b=uYhaZ6>WS5Ifu>aCb;?FQcFFwQ!8{9svz5n-KYocc&x;NUm#K784U%MK46vaj zG)X0N-uOB_Od3Z*b)sDC{e&?w{{4j)UZe-DW7l1~DN`-uh39{UCVJ{Ul=~;=<|rHime!OI^vG!`X{H+$TJt(LWSvEeoRbBFmE1B18fAlh z%#T$$7b4GZh_jPNCo6@02=lXjI$|b{>NZ`(i1H*!X*zE)ulY#5(!Ak&j~&6qGiNb; z@tWqKD^?9{%xz)reD97O$kPL*{n$c6e}!Ij;nEdT?YO%i{@{~HHMQz9v~G+~VrF_q zAKLiiA3u-PtpfJ!+e;d^p^xAc)^clh7E^>kd2-)}Kkz7uOd&rc9(??Rj~OV%&FX0H zLMqj${I!+2;sYSz-nYwX91MCpIou5bwEP6lWb`F-o5aC z?1>M2S)cUdH5FDVOij+E-D3CMoa+#X%YRT3F#a7PL!?mtFMRz!e1 z-_|*&c_KwN5uIC5g=O!~o#^RoV@EHhNm)jJrCA=J2gPahj+R!eEUx3O{rm7szxYx1 zk8k4U@D0tqdk-JNebgBOqAJXb z&;CTK^}$2?@%vx-s#34E&OYqgHK=(oB9jOV+C{$KAC98DPAI~K`zyBgHgZ*}KXilu z!+(oKL*65Dn*1m8h!4K+KD-g~VT9E_O~CFQ8$*tXRMxuAHPkFWw#a-2h6a&Hw;;zh zh4&L5`Oug3@QHTltBTfTG^0Wperk0NOAJd9MTPeka$6|V>bD3x5^PV#lPRTDqVsLg zE95Ho96hQHbD1J&p;wBCARRSXn9Le>QzY$WFGLzi#j24i*V&d7DmOJYtV)VJTkGJ# zgM?Du3Y#X`8Qf$JUf~S~nYd4!I7uqFrS~Ih&M|&wmY*TTMHCguqe2%ghFy{OrE(PW zUYV~ck;TY$&fgrtC_VmmW)pKXW|sHBW)70Xc11F;B7u1iQ$?b=#XO%uo`1toZ>49q zaP0@l*p`VNeDahEIUXes)6xgDy1fI;uc33HfNQlWo%#%fB6>VannMLvXTe{;`s@G1 zZNNAzIL+*%bfAUaI68S-Dc(xP+`K%|qp-22-cylk$x69>pbf84I1uB?YiB0LwG!{y zJ*462W8Rf^t4uKrlA;YyPSPXe=#iEJx2; zX1WQ>Q$tv!d^C(;#A)91g~>S1WCU^ua9dwL z3&jj3=!Gx;^vBqJ@DQ=tx;AE731Nmu7qlR>G9+aFCBhrr*qB8VJ>vMi_pr+sWTkAt z8sSR|b76`Q;q3W$Fns+6t0eoo46)X*N&Ld8ukmxF=h4Yx5f2~IF52?yA{v_-jbbG2 zO%@lKu1GR$^#S2BE-a~oMaIW`xUjg2Ym>8BWkbHTjj4sXS3FHvmJ~RNq$DFkHa3OaAK0VImK2qf8M%DGqSa`EQpxTA&V-wmp z7OIxZW*LUfxXp#h*v5;`zpi6pd-qa;Cy&*$!(3$~8rFv@%Gkv!Lq%XjcZUo_hOtJm zRjRFMf=)TF)KHY5PIls?FDVR2p7eWd__|irNvbUkTx45&n+{Yikjh=SaDly&Rea#F z4^aIcLC-)xGTtrq=7FwG&AmnDuAiQIldveyE?+P9^Zxd+(G_Qbkn<+;h-Qz^oH6zX z%(GR+>vKXYW!O@5QEsT5WlpvvnClx*nIA`l>iPWT>&(GrdB|+CwN*8 z?QJmGULHj-=EE|fNrY~_w8#bz4cOF}#n|S-p^OzaANkeqQ$JZqo%6;@! zIl3jY%^RBxBOk+`o+>-K%5eGd-#&--o=)6Lr8ynXD}|HAw2%_6vieFM%`pwkh?v?) zWxSZf-Fx<6grPVyahspjh7`m3^5ttdcK>~txP6;YDvPeRHhOmpZcWePp=UnK@J=xt z6G##42PL0#!$jwtrzgvNVj(Z5pJd#msu1;tNF*WH@Ha>hviB^gqr2WLom-gx0vOpo2t;uWGV_|yE61{eFWf5x>PkZ_LtbqH)IkYr} z@vU!uT{l(ILW1EgJrWr}lA$0kQH9q%Cv2;3Sv5^4Y5wyXMS88!NTGUj3o9sZ%JW`< zAtHwOxjDpE`8uYh4B1)8s+ot_avfEX(Jb>UWU9?2o$jn!Wo<1=2VYK!`Pg7@E!8km z+R~kilLz~0;H`prGOjR`#q}bJ%$W&(YIBOhC|N+U=4g+##2u8yU%GG^Gqa1T+yq%! zWpphZk7>@mabpyn#GO$ZGeKAsA{bw1^6q8MlOtS{1;?-F4+GnP&L^`S)D)#1?#>00000 LNkvXXu0mjfJk8&7 diff --git a/src/apps/wallet-admin/src/lib/assets/tcandyou/index.ts b/src/apps/wallet-admin/src/lib/assets/tcandyou/index.ts deleted file mode 100644 index 7e4b90ac6..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tcandyou/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ReactComponent as DevelopmentTrackIcon } from './develop.svg' -import { ReactComponent as DesignTrackIcon } from './design.svg' -import { ReactComponent as DataScienceTrackIcon } from './data_science.svg' -import ethereumCommunityImage from './ico-ethereum.png' -import ibmCommunityImage from './ico-ibmcloud.png' -import veteransCommunityImage from './ico-veteran.png' - -export { - ethereumCommunityImage, - ibmCommunityImage, - veteransCommunityImage, - DesignTrackIcon, - DataScienceTrackIcon, - DevelopmentTrackIcon, -} diff --git a/src/apps/wallet-admin/src/lib/assets/tools/Financial Institution.svg b/src/apps/wallet-admin/src/lib/assets/tools/Financial Institution.svg deleted file mode 100644 index 5736f8c0f..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/Financial Institution.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/Internet Service Provider.svg b/src/apps/wallet-admin/src/lib/assets/tools/Internet Service Provider.svg deleted file mode 100644 index 5736f8c0f..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/Internet Service Provider.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/Mobile Carrier.svg b/src/apps/wallet-admin/src/lib/assets/tools/Mobile Carrier.svg deleted file mode 100644 index eb23554ab..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/Mobile Carrier.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/Television.svg b/src/apps/wallet-admin/src/lib/assets/tools/Television.svg deleted file mode 100644 index 671a335c5..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/Television.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/console.svg b/src/apps/wallet-admin/src/lib/assets/tools/console.svg deleted file mode 100644 index 237abfc97..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/console.svg +++ /dev/null @@ -1 +0,0 @@ -004-consoleCreated with Sketch. diff --git a/src/apps/wallet-admin/src/lib/assets/tools/desktop.svg b/src/apps/wallet-admin/src/lib/assets/tools/desktop.svg deleted file mode 100644 index 1755a7581..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/desktop.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/index.ts b/src/apps/wallet-admin/src/lib/assets/tools/index.ts deleted file mode 100644 index 176aaa7f5..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ReactComponent as SoftwareIcon } from './software.svg' -import { ReactComponent as DesktopIncon } from './desktop.svg' -import { ReactComponent as SmartphoneIcon } from './smartphone.svg' -import { ReactComponent as OtherDeviceIcon } from './other_device.svg' -import { ReactComponent as LaptopIcon } from './laptop.svg' -import { ReactComponent as ConsoleIcon } from './console.svg' -import { ReactComponent as TabletIcon } from './tablet.svg' -import { ReactComponent as WearableIcon } from './wearable.svg' -import { ReactComponent as FinancialInstitutionIcon } from './Financial Institution.svg' -import { ReactComponent as OtherServiceProviderIcon } from './other_service_provider.svg' -import { ReactComponent as TelevisionServiceProviderIcon } from './Television.svg' -import { ReactComponent as MobileCarrierServiceProviderIcon } from './Mobile Carrier.svg' -import { ReactComponent as InternetServiceProviderIcon } from './Internet Service Provider.svg' -import { ReactComponent as SubscriptionsIcon } from './subscription.svg' - -export { - ConsoleIcon, - DesktopIncon, - FinancialInstitutionIcon, - InternetServiceProviderIcon, - MobileCarrierServiceProviderIcon, - LaptopIcon, - OtherDeviceIcon, - OtherServiceProviderIcon, - SmartphoneIcon, - SoftwareIcon, - SubscriptionsIcon, - TabletIcon, - TelevisionServiceProviderIcon, - WearableIcon, -} diff --git a/src/apps/wallet-admin/src/lib/assets/tools/laptop.svg b/src/apps/wallet-admin/src/lib/assets/tools/laptop.svg deleted file mode 100644 index d54366f39..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/laptop.svg +++ /dev/null @@ -1 +0,0 @@ -notebookCreated with Sketch. diff --git a/src/apps/wallet-admin/src/lib/assets/tools/other_device.svg b/src/apps/wallet-admin/src/lib/assets/tools/other_device.svg deleted file mode 100644 index 1fca19866..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/other_device.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/other_service_provider.svg b/src/apps/wallet-admin/src/lib/assets/tools/other_service_provider.svg deleted file mode 100644 index 659b14dae..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/other_service_provider.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - -64px_wifi off - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/smartphone.svg b/src/apps/wallet-admin/src/lib/assets/tools/smartphone.svg deleted file mode 100644 index c8b32fa44..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/smartphone.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/software.svg b/src/apps/wallet-admin/src/lib/assets/tools/software.svg deleted file mode 100644 index 266fc1aaf..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/software.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/subscription.svg b/src/apps/wallet-admin/src/lib/assets/tools/subscription.svg deleted file mode 100644 index eafb3cfa9..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/subscription.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/tablet.svg b/src/apps/wallet-admin/src/lib/assets/tools/tablet.svg deleted file mode 100644 index fa1a0d651..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/tablet.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/apps/wallet-admin/src/lib/assets/tools/wearable.svg b/src/apps/wallet-admin/src/lib/assets/tools/wearable.svg deleted file mode 100644 index 646ae6854..000000000 --- a/src/apps/wallet-admin/src/lib/assets/tools/wearable.svg +++ /dev/null @@ -1 +0,0 @@ -005-smart-watchCreated with Sketch. diff --git a/src/apps/wallet-admin/src/lib/components/action-bar-item/ActionBarItem.tsx b/src/apps/wallet-admin/src/lib/components/action-bar-item/ActionBarItem.tsx deleted file mode 100644 index 1ea2a947a..000000000 --- a/src/apps/wallet-admin/src/lib/components/action-bar-item/ActionBarItem.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { SVGProps } from 'react' - -import { LinkButton } from '~/libs/ui' - -interface ActionBarItemProps { - containerClassName: string; - action: { - text: string; - className: string; - icon: React.FC>; - isButtonDisabled?: boolean; - }; - info: { - text: string; - icon: React.FC>; - className: string; - }, - onConfirm?: () => void; -} - -const ActionBarItem: React.FC = (props: ActionBarItemProps) => { - const handleActionClick = React.useCallback(() => { - props.onConfirm?.() - }, [props]) - - return ( -

- - -
- ) - -} - -export default ActionBarItem diff --git a/src/apps/wallet-admin/src/lib/components/action-bar-item/index.ts b/src/apps/wallet-admin/src/lib/components/action-bar-item/index.ts deleted file mode 100644 index 07d166714..000000000 --- a/src/apps/wallet-admin/src/lib/components/action-bar-item/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ActionBarItem } from './ActionBarItem' diff --git a/src/apps/wallet-admin/src/lib/components/chip/Chip.module.scss b/src/apps/wallet-admin/src/lib/components/chip/Chip.module.scss deleted file mode 100644 index 7a78c4b52..000000000 --- a/src/apps/wallet-admin/src/lib/components/chip/Chip.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.container { - background: #ef476f; - width: 84px; - height: 16px; - padding: 3px 4px 3px 4px; - border-radius: 2px; - gap: 2px; - display: inline-flex; - align-items: center; - justify-content: center; - font-family: $font-roboto; - color: white; - font-size: 11px; - line-height: normal; - text-align: center; -} diff --git a/src/apps/wallet-admin/src/lib/components/chip/Chip.tsx b/src/apps/wallet-admin/src/lib/components/chip/Chip.tsx deleted file mode 100644 index ce17c080a..000000000 --- a/src/apps/wallet-admin/src/lib/components/chip/Chip.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' - -import styles from './Chip.module.scss' - -interface ChipProps { - text: string -} - -const Chip: React.FC = (props: ChipProps) =>
{props.text}
- -export default Chip diff --git a/src/apps/wallet-admin/src/lib/components/chip/index.ts b/src/apps/wallet-admin/src/lib/components/chip/index.ts deleted file mode 100644 index 877198b11..000000000 --- a/src/apps/wallet-admin/src/lib/components/chip/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as Chip } from './Chip' diff --git a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.module.scss b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.module.scss deleted file mode 100644 index b690c918a..000000000 --- a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -.FilterBar { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - - .filterContainer { - display: flex; - flex-wrap: wrap; - gap: 10px; - - > .input { - margin-bottom: 0px; - } - } - - .resetButton { - flex-shrink: 0; - margin-left: 10px; - } - } diff --git a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx deleted file mode 100644 index c5c525f14..000000000 --- a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* eslint-disable max-len */ -import React, { ChangeEvent } from 'react' - -import { Button, InputSelect, InputText } from '~/libs/ui' -import { InputHandleAutocomplete, MembersAutocompeteResult } from '~/apps/gamification-admin/src/game-lib' - -import styles from './FilterBar.module.scss' - -type FilterOptions = { - label: string - value: string -} - -type Filter = { - key: string - label: string - type: 'input' | 'dropdown' | 'member_autocomplete' - options?: FilterOptions[] -} - -interface FilterBarProps { - filters: Filter[], - onFilterChange: (key: string, value: string[]) => void - onResetFilters?: () => void -} - -const FilterBar: React.FC = (props: FilterBarProps) => { - const [selectedValue, setSelectedValue] = React.useState>(new Map()) - - const renderDropdown = (index: number, filter: Filter): JSX.Element => ( - ) { - setSelectedValue(new Map(selectedValue.set(filter.key, event.target.value))) - props.onFilterChange(filter.key, [event.target.value]) - }} - name={filter.key} - label={filter.label} - dirty - placeholder={filter.label} - /> - ) - - const renderMemberAutoComplete = (index: number, filter: Filter): JSX.Element => ( - ) { - setSelectedValue(new Map(selectedValue.set(filter.key, event))) - props.onFilterChange(filter.key, event.map(member => member.userId)) - }} - tabIndex={index} - /> - ) - - return ( -
-
- {props.filters.map((options, index) => ( -
- {options.type === 'dropdown' && renderDropdown(index, options)} - {options.type === 'input' && ( - ) { - if (event.target.value === '') { - setSelectedValue(new Map(selectedValue.set(options.key, ''))) - props.onFilterChange(options.key, []) - } else { - props.onFilterChange(options.key, [event.target.value]) - } - }} - /> - )} - {options.type === 'member_autocomplete' && renderMemberAutoComplete(index, options)} -
- ))} -
-
- ) -} - -export default FilterBar diff --git a/src/apps/wallet-admin/src/lib/components/filter-bar/index.ts b/src/apps/wallet-admin/src/lib/components/filter-bar/index.ts deleted file mode 100644 index 902a76c74..000000000 --- a/src/apps/wallet-admin/src/lib/components/filter-bar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as FilterBar } from './FilterBar' diff --git a/src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.module.scss b/src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.tsx b/src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.tsx deleted file mode 100644 index f5cfd181a..000000000 --- a/src/apps/wallet-admin/src/lib/components/generic-table/GenericTable.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable react/no-array-index-key */ - -import React from 'react' - -import { Column } from '../../models/Column' - -interface GenericTableProps { - columns: Column[]; - data: any[]; -} - -const GenericTable: React.FC = (props: GenericTableProps) => ( - - - - {props.columns.map((column, index) => ( - - ))} - - - - {props.data.map((row, rowIndex) => ( - - {props.columns.map((column, columnIndex) => { - const cellValue = row[column.accessor] - const CellRenderer = column.Cell - return ( - - ) - })} - - ))} - -
{column.Header}
- {CellRenderer ? : cellValue} -
-) - -export default GenericTable diff --git a/src/apps/wallet-admin/src/lib/components/generic-table/index.ts b/src/apps/wallet-admin/src/lib/components/generic-table/index.ts deleted file mode 100644 index 87f854d8d..000000000 --- a/src/apps/wallet-admin/src/lib/components/generic-table/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as GenericTable } from './GenericTable' diff --git a/src/apps/wallet-admin/src/lib/components/index.ts b/src/apps/wallet-admin/src/lib/components/index.ts deleted file mode 100644 index 6f213563b..000000000 --- a/src/apps/wallet-admin/src/lib/components/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './setting-section' -export * from './info-row' -export * from './chip' -export * from './filter-bar' diff --git a/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.module.scss b/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.module.scss deleted file mode 100644 index f44d056e0..000000000 --- a/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.module.scss +++ /dev/null @@ -1,38 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.info-row { - display: flex; - align-items: center; - background-color: $tc-white; - padding: $sp-4 $sp-8; - border-radius: 8px; -} - -.title { - font-family: $font-roboto; - font-style: normal; - font-weight: 400; - font-size: 20px; - line-height: 26px; - color: $black-100; -} - -.value-action-container { - display: flex; - justify-content: flex-end; - align-items: center; - flex-grow: 1; - padding-left: 10px; - gap: 200px; -} - -.value { - display: flex; - justify-content: flex-end; -} - -.action { - display: flex; - min-width: 200px; - justify-content: flex-end; -} diff --git a/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.tsx b/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.tsx deleted file mode 100644 index 104cf01a5..000000000 --- a/src/apps/wallet-admin/src/lib/components/info-row/InfoRow.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react' - -import styles from './InfoRow.module.scss' - -interface InfoRowProps { - title: string - value: React.ReactNode - action?: React.ReactNode -} - -const InfoRow: React.FC = (props: InfoRowProps) => ( -
-
{props.title}
-
-
{props.value}
- {props.action &&
{props.action}
} -
-
-) - -export default InfoRow diff --git a/src/apps/wallet-admin/src/lib/components/info-row/index.ts b/src/apps/wallet-admin/src/lib/components/info-row/index.ts deleted file mode 100644 index 3d72e3e11..000000000 --- a/src/apps/wallet-admin/src/lib/components/info-row/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as InfoRow } from './InfoRow' diff --git a/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.module.scss b/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.module.scss deleted file mode 100644 index 9c5d0a5a2..000000000 --- a/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.module.scss +++ /dev/null @@ -1,87 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.otp-modal { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 20px; - padding-bottom: 0; - - .error { - color: red; - } - - .otpInput { - width: 48px !important; - height: 48px; - margin: 5px; - text-align: center; - font-size: 1rem; - border: 2px solid #ccc; - border-radius: 4px; - - &::-webkit-inner-spin-button, - &::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; - } - - &::-moz-focus-inner { - border: 0; - } - - &[type='number'] { - -moz-appearance: textfield; - } - - &::-ms-clear, - &::-ms-reveal { - display: none; - width: 0; - height: 0; - } - - &[type='number']::placeholder { - color: transparent; - } - - &:focus { - outline: none; - border-color: #007bff; - } - } - - h3 { - font-size: 1.5rem; - text-align: center; - margin-bottom: 20px; - } - - .otpInputStyle { - width: 40px; - height: 50px; - margin: 0 5px; - text-align: center; - font-size: 2rem; - border: 1px solid #ccc; - border-radius: 4px; - - &:focus { - outline: none; - border-color: blue; - } - } - - .otpInputFocusStyle { - border-color: #007bff; - } - - p { - font-size: 0.9rem; - color: #666; - text-align: center; - margin-top: $sp-1; - margin-bottom: $sp-4; - } -} diff --git a/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.tsx b/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.tsx deleted file mode 100644 index d91a0e976..000000000 --- a/src/apps/wallet-admin/src/lib/components/otp-modal/OtpModal.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { toast } from 'react-toastify' -import OTPInput, { InputProps } from 'react-otp-input' -import React, { FC } from 'react' - -import { BaseModal, LinkButton, LoadingCircles } from '~/libs/ui' -import { resendOtp, verifyOtp } from '~/apps/wallet/src/lib/services/wallet' - -import { OtpVerificationResponse } from '../../models/OtpVerificationResponse' - -import styles from './OtpModal.module.scss' - -const RESEND_OTP_TIMEOUT = 60000 - -interface OtpModalProps { - isOpen: boolean - key: string - transactionId: string - userEmail?: string - isBlob?: boolean - onClose: () => void - onOtpVerified: (data: unknown) => void -} - -const OtpModal: FC = (props: OtpModalProps) => { - const [otp, setOtp] = React.useState('') - const [loading, setLoading] = React.useState(false) - const [error, setError] = React.useState('') - const [showResendButton, setShowResendButton] = React.useState(false) - - // eslint-disable-next-line consistent-return - React.useEffect(() => { - let timer: NodeJS.Timeout | undefined - if (props.isOpen) { - setShowResendButton(false) - timer = setTimeout(() => { - setShowResendButton(true) - }, RESEND_OTP_TIMEOUT) - } - - return () => { - if (timer) { - clearTimeout(timer) - } - } - }, [props.isOpen]) - - React.useEffect(() => { - if (!props.isOpen) { - setOtp('') - setError('') - } - }, [props.isOpen]) - - function handleChange(code: string): void { - setOtp(code) - if (code.length === 6) { - setLoading(true) - verifyOtp(props.transactionId, code, props.isBlob) - .then((response: OtpVerificationResponse | Blob) => { - setLoading(false) - props.onOtpVerified(response) - }) - .catch((err: Error) => { - setLoading(false) - setError(err.message) - }) - } else if (code.length < 6) { - setError('') - } - } - - return ( - -
- {error &&

{error}

} -

- For added security we’ve sent a 6-digit code to your - {' '} - {props.userEmail ?? '***@gmail.com'} - {' '} - email. The code - expires shortly, so please enter it soon. -

- } - onChange={handleChange} - inputType='number' - shouldAutoFocus - inputStyle={styles.otpInput} - /> - -

Can't find the code? Check your spam folder.

- {loading && } - {!loading && showResendButton && ( - { - setShowResendButton(true) - }, RESEND_OTP_TIMEOUT) - toast.success( - 'OTP sent successfully.', - { position: toast.POSITION.BOTTOM_RIGHT }, - ) - } catch (err: unknown) { - toast.error( - (err as Error).message ?? 'Something went wrong. Please try again.', - { position: toast.POSITION.BOTTOM_RIGHT }, - ) - } - }} - /> - )} -
-
- ) -} - -export default OtpModal diff --git a/src/apps/wallet-admin/src/lib/components/otp-modal/index.ts b/src/apps/wallet-admin/src/lib/components/otp-modal/index.ts deleted file mode 100644 index 9d973de1e..000000000 --- a/src/apps/wallet-admin/src/lib/components/otp-modal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as OtpModal } from './OtpModal' diff --git a/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss b/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss deleted file mode 100644 index 3a56c65d7..000000000 --- a/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.module.scss +++ /dev/null @@ -1,90 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.card { - display: flex; - flex-direction: row; - align-items: flex-start; - justify-content: space-between; - border-radius: 8px; - background-color: white; - border: 1px solid #D4D4D4; - - .content { - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 16px; - - .header { - width: 100%; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - } - - .detailContainer { - display: grid; - width: 100%; - - &.singleRow { - grid-template-columns: repeat(3, 1fr); - margin-top: 24px; - gap: $sp-6; - } - - &.stackedRows { - grid-template-columns: 1fr; - .detail:not(:last-child) { - margin-bottom: 8px; - } - } - - .detail { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - - .iconLabelContainer { - display: flex; - } - - .label { - margin-left: 8px; - color: $teal-140; - font-family: $font-roboto-mono; - font-size: 12px; - font-weight: 700; - line-height: 16px; - letter-spacing: 1px; - text-align: left; - } - - .value { - color: #2a2a2a; - font-size: 14px; - } - } - } - - .footer { - margin-top: 24px; - } - } - - .actionItems { - padding-top: 16px; - margin-right: 24px; - - .actionItemsContainer { - display: flex; - flex-direction: column; - align-items: flex-end; - - .warningLabel { - color: $legacy-120; - } - } - } -} diff --git a/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.tsx b/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.tsx deleted file mode 100644 index 0a4fad84c..000000000 --- a/src/apps/wallet-admin/src/lib/components/payment-provider-card/PaymentProviderCard.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useMemo, useState } from 'react' - -import { CheckCircleIcon } from '@heroicons/react/solid' -import { Button, ConfirmModal, IconOutline, IconSolid, PageDivider } from '~/libs/ui' - -import { ActionBarItem } from '../action-bar-item' -import { PaymentProvider } from '../../models/PaymentProvider' -import { ConfirmFlowData } from '../../models/ConfirmFlowData' - -import styles from './PaymentProviderCard.module.scss' - -interface Detail { - icon: React.ReactNode - label: string - value: string -} - -interface PaymentProviderProps { - provider: PaymentProvider - logo: React.ReactNode - details: Detail[] - onConnectClick?: () => void - onResendOtpClick?: () => void - onGoToRegistrationClick?: () => void - onRemoveProvider?: () => void -} - -const PaymentProviderCard: React.FC = (props: PaymentProviderProps) => { - const [confirmFlow, setConfirmFlow] = useState(undefined) - - const renderConfirmModalContent = useMemo(() => { - if (confirmFlow?.content === undefined) { - return undefined - } - - if (typeof confirmFlow?.content === 'function') { - return confirmFlow?.content() - } - - return confirmFlow?.content - }, [confirmFlow]) - - const canConnect = props.provider.status === 'NOT_CONNECTED' - const canCancel = ['OTP_PENDING', 'OTP_VERIFIED', 'VERIFIED', 'CONNECTED'].includes(props.provider.status) - - const renderOtpPending = (): JSX.Element => ( - - ) - - const renderOtpVerified = (): JSX.Element => ( - - ) - - return ( - <> -
-
-
-
{props.logo}
-
- - {canConnect && } - -
- {props.details.map((detail: Detail) => ( -
-
- {detail.icon} - {detail.label} -
-

{detail.value}

-
- ))} -
- - {canConnect && ( -
-
- )} - {canCancel && ( -
-
- )} -
-
- {props.provider.status === 'CONNECTED' - &&
-
- {confirmFlow && ( - -
{renderConfirmModalContent}
-
- )} - - ) -} - -export default PaymentProviderCard diff --git a/src/apps/wallet-admin/src/lib/components/payment-provider-card/index.ts b/src/apps/wallet-admin/src/lib/components/payment-provider-card/index.ts deleted file mode 100644 index a3a1c4984..000000000 --- a/src/apps/wallet-admin/src/lib/components/payment-provider-card/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as PaymentProviderCard } from './PaymentProviderCard' diff --git a/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss b/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss deleted file mode 100644 index 8757b2164..000000000 --- a/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.module.scss +++ /dev/null @@ -1,77 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.tableContainer { - width: 100%; - overflow-x: auto; -} - -table { - width: 100%; - border-collapse: collapse; - margin: 16px 0; - - th, - td { - text-align: left; - padding: 8px; - border-bottom: 1px solid #eaeaea; - - &:last-child { - width: 50px; - } - } - - th { - background-color: #f5f5f5; - position: sticky; - top: 0; - } - - tbody { - tr { - &:nth-child(odd) { - background-color: #f9f9f9; - } - &:nth-child(even) { - background-color: #ffffff; - } - } - } -} - -.selected { - background-color: #e7f4ff; -} - -.paymentFooter { - display: flex; - justify-content: space-between; - align-items: center; - padding: 16px; - margin-top: 16px; - border-radius: 8px; - background-color: white; - border: 1px solid #eaeaea; - - .total { - font-size: 18px; - font-weight: bold; - - color: #333; - } -} - -@media (max-width: 768px) { - .paymentFooter { - flex-direction: column; - align-items: stretch; - - .total { - margin-bottom: 10px; - } - - .payMeButton { - width: 100%; - } - } -} diff --git a/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx b/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx deleted file mode 100644 index 240fcfcfd..000000000 --- a/src/apps/wallet-admin/src/lib/components/payments-table/PaymentTable.tsx +++ /dev/null @@ -1,119 +0,0 @@ -/* eslint-disable react/jsx-no-bind */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import React, { useState } from 'react' - -import { Button } from '~/libs/ui' - -import { Winning } from '../../models/WinningDetail' - -import styles from './PaymentTable.module.scss' - -interface PaymentTableProps { - payments: ReadonlyArray - onPayMeClick: (paymentIds: { [paymentId: string]: boolean }) => void -} -const PaymentsTable: React.FC = (props: PaymentTableProps) => { - const [selectedPayments, setSelectedPayments] = useState<{ [paymentId: string]: boolean }>({}) - - const togglePaymentSelection = (paymentId: string) => { - setSelectedPayments(prevSelected => ({ - ...prevSelected, - [paymentId]: !prevSelected[paymentId], - })) - } - - const isAllSelected = props.payments.length > 0 && props.payments.every(payment => selectedPayments[payment.id]) - - const toggleAllPayments = () => { - if (isAllSelected) { - setSelectedPayments({}) - } else { - const newSelections: { [paymentId: string]: boolean } = {} - props.payments.forEach(payment => { - newSelections[payment.id] = true - }) - setSelectedPayments(newSelections) - } - } - - const calculateTotal = () => props.payments.reduce((acc: number, payment: Winning) => { - if (selectedPayments[payment.id]) { - return acc + parseFloat(payment.netPayment.replace(/[^0-9.-]+/g, '')) - } - - return acc - }, 0) - - const total = calculateTotal() - - return ( - <> -
- - - - - - - - - - - - - - - {props.payments.map(payment => ( - - - - - - - - - - - ))} - -
DescriptionTypeCreate DateNet PaymentStatusRelease DateDate Paid - payment.status !== 'OWED') === undefined} - checked={isAllSelected} - aria-label='Select all payments' - /> -
{payment.description}{payment.type}{payment.createDate}{payment.netPayment}{payment.status}{payment.releaseDate}{payment.datePaid} - togglePaymentSelection(payment.id)} - aria-label={`Select payment ${payment.id}`} - /> -
-
-
-
- Total: $ - {total.toFixed(2)} -
- -
- - ) -} - -export default PaymentsTable diff --git a/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.module.scss b/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.module.scss deleted file mode 100644 index dd4087c74..000000000 --- a/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.container { - display: flex; - padding: $sp-4; - border: 1px solid $black-20; - border-radius: 8px; - margin-top: $sp-4; - flex-wrap: wrap; - - .contentMiddle { - display: flex; - flex-direction: column; - flex: 1; - align-self: center; - - @include ltelg { - margin-right: $sp-4; - } - - .infoText { - color: #767676; - max-width: 85%; - } - } -} \ No newline at end of file diff --git a/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.tsx b/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.tsx deleted file mode 100644 index 380286fdf..000000000 --- a/src/apps/wallet-admin/src/lib/components/setting-section/SettingSection.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { FC } from 'react' -import classNames from 'classnames' - -import styles from './SettingSection.module.scss' - -interface SettingSectionProps { - containerClassName?: string - readonly title: string - readonly infoText?: string - actionElement?: React.ReactNode - leftElement?: React.ReactNode -} - -const SettingSection: FC = (props: SettingSectionProps) => ( -
- {props.leftElement} - -
-

{props.title}

-

-

- - {props.actionElement} -
-) - -export default SettingSection diff --git a/src/apps/wallet-admin/src/lib/components/setting-section/index.ts b/src/apps/wallet-admin/src/lib/components/setting-section/index.ts deleted file mode 100644 index a2a2fec21..000000000 --- a/src/apps/wallet-admin/src/lib/components/setting-section/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as SettingSection } from './SettingSection' diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.module.scss b/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.module.scss deleted file mode 100644 index 46c3eb47d..000000000 --- a/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.module.scss +++ /dev/null @@ -1,46 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.card { - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: space-between; - background-color: white; - padding: 16px; - border-radius: 8px; - border: 1px solid #eaeaea; - margin: 16px 0; - - .header { - display: flex; - align-items: center; - justify-content: flex-start; - gap: 24px; - - .icon { - max-width: 48px; - max-height: 48px; - } - } - - .content { - display: flex; - flex-direction: column; - gap: 16px; - flex: 1; - - .additionalInfoPurpose { - ul { - list-style-type: disc; - padding: 10px 24px; - } - } - } - - .footer { - display: flex; - width: 100%; - justify-content: space-between; - margin-top: 24px; - } -} diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.tsx b/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.tsx deleted file mode 100644 index 855021793..000000000 --- a/src/apps/wallet-admin/src/lib/components/tax-form-card/TaxFormCard.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react' - -import { Button, PageDivider } from '~/libs/ui' - -import styles from './TaxFormCard.module.scss' - -interface TaxFormCardProps { - formTitle: string - formDescription: string - reasonTitle: string - reasonDescription: string - instructionsLink: string - instructionsLabel: string - completionLabel: string - additionalInfo?: { - link?: { - text: string - href: string - } - purpose?: { - title: string - points: string[] - } - note?: string - } - icon: React.ReactNode - onSetupClick: () => void -} - -const TaxFormCard: React.FC = (props: TaxFormCardProps) => ( -
-) - -export default TaxFormCard diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-card/index.ts b/src/apps/wallet-admin/src/lib/components/tax-form-card/index.ts deleted file mode 100644 index 4b7cccbab..000000000 --- a/src/apps/wallet-admin/src/lib/components/tax-form-card/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as TaxFormCard } from './TaxFormCard' diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.module.scss b/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.module.scss deleted file mode 100644 index bf64b99b9..000000000 --- a/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.module.scss +++ /dev/null @@ -1,92 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.card { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - background-color: white; - padding: 16px; - border-radius: 8px; - gap: $sp-4; - border: 1px solid #eaeaea; - margin: 16px 0; - - .iconContainer { - width: 64px; - height: 64px; - display: flex; - justify-content: center; - align-items: center; - - background: linear-gradient(264.69deg, #198807 2.17%, #017c6d 97.49%); - border-radius: 4px; - - .icon { - width: 26.67px; - height: 26.67px; - top: 2.67px; - left: 2.67px; - fill: white; - } - } - - .content { - display: flex; - flex-direction: column; - flex: 1; - } - - .actionItems { - display: flex; - align-items: center; - width: 64px; - justify-content: space-between; - - .actionButton { - width: 32px; - height: 32px; - border: none; - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - } - - .downloadIcon { - width: 24px; - height: 24px; - fill: $turq-160; - } - - .deleteIcon { - width: 24px; - height: 24px; - fill: $red-140; - } - } - - .actionItemsStacked { - display: flex; - flex-direction: column; - align-items: flex-end; - - .warningLabel { - color: $legacy-120; - } - } - - .actionButton, - .loadingWrapper { - transition: opacity 0.3s ease, transform 0.3s ease; - opacity: 1; - transform: scale(1); - } - - .hidden { - opacity: 0; - display: none; - transform: scale(0.95); - pointer-events: none; - } -} diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.tsx b/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.tsx deleted file mode 100644 index 74d0673dd..000000000 --- a/src/apps/wallet-admin/src/lib/components/tax-form-detail/TaxFormDetail.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React, { useMemo } from 'react' - -import { DownloadIcon, ExclamationCircleIcon, TrashIcon } from '@heroicons/react/solid' -import { ConfirmModal, IconSolid } from '~/libs/ui' - -import { IconCheckCircle } from '../../assets/tax-forms' -import { ActionBarItem } from '../action-bar-item' -import { ConfirmFlowData } from '../../models/ConfirmFlowData' - -import styles from './TaxFormDetail.module.scss' - -interface TaxFormDetailProps { - title: string - description: string - status: string - onGetRecipientURL?: () => void - onResendOtpClick?: () => void - onDownloadClick?: () => void - onDeleteClick?: () => void -} - -const TaxFormDetail: React.FC = (props: TaxFormDetailProps) => { - const [confirmFlow, setConfirmFlow] = React.useState(undefined) - - const renderConfirmModalContent = useMemo(() => { - if (confirmFlow?.content === undefined) { - return undefined - } - - if (typeof confirmFlow?.content === 'function') { - return confirmFlow?.content() - } - - return confirmFlow?.content - }, [confirmFlow]) - - const renderOtpPending = (): JSX.Element => ( - - ) - - const renderOtpVerified = (): JSX.Element => ( - - ) - - const renderActive = (): JSX.Element => ( -
- - -
- ) - - return ( - <> -
-
- {props.status === 'ACTIVE' && } - {props.status !== 'ACTIVE' && } -
- -
-
{props.title}
-
{props.description}
-
- - {props.status === 'OTP_PENDING' && renderOtpPending()} - {props.status === 'OTP_VERIFIED' && renderOtpVerified()} - {props.status === 'ACTIVE' && renderActive()} -
- {confirmFlow && ( - -
{renderConfirmModalContent}
-
- )} - - ) -} - -export default TaxFormDetail diff --git a/src/apps/wallet-admin/src/lib/components/tax-form-detail/index.ts b/src/apps/wallet-admin/src/lib/components/tax-form-detail/index.ts deleted file mode 100644 index bfe5dc606..000000000 --- a/src/apps/wallet-admin/src/lib/components/tax-form-detail/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as TaxFormDetail } from './TaxFormDetail' diff --git a/src/apps/wallet-admin/src/lib/index.ts b/src/apps/wallet-admin/src/lib/index.ts deleted file mode 100644 index 0f5e459b8..000000000 --- a/src/apps/wallet-admin/src/lib/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './wallet-swr' -export * from './assets' -export * from './components' diff --git a/src/apps/wallet-admin/src/lib/models/ApiError.ts b/src/apps/wallet-admin/src/lib/models/ApiError.ts deleted file mode 100644 index 9b0135c9e..000000000 --- a/src/apps/wallet-admin/src/lib/models/ApiError.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ApiError { - code: string - message: string -} diff --git a/src/apps/wallet-admin/src/lib/models/ApiResponse.ts b/src/apps/wallet-admin/src/lib/models/ApiResponse.ts deleted file mode 100644 index 3221e7793..000000000 --- a/src/apps/wallet-admin/src/lib/models/ApiResponse.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default interface ApiResponse { - status: 'success' | 'error' - data: T -} diff --git a/src/apps/wallet-admin/src/lib/models/Column.ts b/src/apps/wallet-admin/src/lib/models/Column.ts deleted file mode 100644 index 5eb62e52a..000000000 --- a/src/apps/wallet-admin/src/lib/models/Column.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type Column = { - Header: string; - accessor: string; - Cell?: (props: { row: any }) => JSX.Element; -}; diff --git a/src/apps/wallet-admin/src/lib/models/ConfirmFlowData.ts b/src/apps/wallet-admin/src/lib/models/ConfirmFlowData.ts deleted file mode 100644 index 29c7753e5..000000000 --- a/src/apps/wallet-admin/src/lib/models/ConfirmFlowData.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface ConfirmFlowData { - title: string; - action: string; - content: React.ReactNode | (() => React.ReactNode) - callback?: () => void; -} diff --git a/src/apps/wallet-admin/src/lib/models/OtpVerificationResponse.ts b/src/apps/wallet-admin/src/lib/models/OtpVerificationResponse.ts deleted file mode 100644 index 53456138d..000000000 --- a/src/apps/wallet-admin/src/lib/models/OtpVerificationResponse.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface OtpVerificationResponse { - data: unknown; - } diff --git a/src/apps/wallet-admin/src/lib/models/PaymentProvider.ts b/src/apps/wallet-admin/src/lib/models/PaymentProvider.ts deleted file mode 100644 index c78e84687..000000000 --- a/src/apps/wallet-admin/src/lib/models/PaymentProvider.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface PaymentProvider { - id?: number - upmId?: string - type: 'Payoneer' | 'Paypal' - name: 'Payoneer' | 'Paypal' - description: string - status: string - transactionId?: string -} diff --git a/src/apps/wallet-admin/src/lib/models/ResendOtpResponse.ts b/src/apps/wallet-admin/src/lib/models/ResendOtpResponse.ts deleted file mode 100644 index 1fd04f75a..000000000 --- a/src/apps/wallet-admin/src/lib/models/ResendOtpResponse.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface ResendOtpResponse { - transactionId: string; - } diff --git a/src/apps/wallet-admin/src/lib/models/TaxForm.ts b/src/apps/wallet-admin/src/lib/models/TaxForm.ts deleted file mode 100644 index 6578be894..000000000 --- a/src/apps/wallet-admin/src/lib/models/TaxForm.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface TaxForm { - id: string - userId: string - dateFiled: string - withholdingAmount: string - withholdingPercentage: string - taxForm: { - name: string - text: string - description: string - } - status: string - transactionId: string -} - -export interface SetupTaxFormResponse { - transactionId: string -} diff --git a/src/apps/wallet-admin/src/lib/models/TransactionId.ts b/src/apps/wallet-admin/src/lib/models/TransactionId.ts deleted file mode 100644 index a0e860446..000000000 --- a/src/apps/wallet-admin/src/lib/models/TransactionId.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface TransactionResponse { - transactionId: string - type?: string; - email: string -} diff --git a/src/apps/wallet-admin/src/lib/models/WalletDetails.ts b/src/apps/wallet-admin/src/lib/models/WalletDetails.ts deleted file mode 100644 index b7baf2aeb..000000000 --- a/src/apps/wallet-admin/src/lib/models/WalletDetails.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface Balance { - amount: number - type: string - unit: string -} - -export interface AccountDetails { - balances: Balance[] -} - -export interface WalletDetails { - account: AccountDetails - withdrawalMethod: { - isSetupComplete: boolean - } - taxForm: { - isSetupComplete: boolean - } -} diff --git a/src/apps/wallet-admin/src/lib/models/WinningDetail.ts b/src/apps/wallet-admin/src/lib/models/WinningDetail.ts deleted file mode 100644 index 9e07afbe7..000000000 --- a/src/apps/wallet-admin/src/lib/models/WinningDetail.ts +++ /dev/null @@ -1,41 +0,0 @@ -export interface PaymentDetail { - id: string - netAmount: string - grossAmount: string - totalAmount: string - installmentNumber: number - status: string - currency: string -} - -export interface Winning { - id: string - description: string - type: string - createDate: string - netPayment: string - status: string - releaseDate: string - datePaid: string - canBeReleased: boolean - currency: string - details: PaymentDetail[] -} - -export interface WinningDetail { - id: string - type: string - winnerId: string - origin: string - category: string - title: string - description: string - externalId: string - attributes: { - url: string - } - details: PaymentDetail[] - createdAt: string - releaseDate: string - datePaid: string -} diff --git a/src/apps/wallet-admin/src/lib/services/wallet.ts b/src/apps/wallet-admin/src/lib/services/wallet.ts deleted file mode 100644 index f4a8ea380..000000000 --- a/src/apps/wallet-admin/src/lib/services/wallet.ts +++ /dev/null @@ -1,359 +0,0 @@ -/* eslint-disable ordered-imports/ordered-imports */ - -import { AxiosError } from 'axios' - -import { EnvironmentConfig } from '~/config' -import { xhrDeleteAsync, xhrGetAsync, xhrPatchAsync, xhrPostAsync, xhrPostAsyncWithBlobHandling } from '~/libs/core' - -import { getAsyncWithBlobHandling } from '~/libs/core/lib/xhr/xhr-functions/xhr.functions' -import { WalletDetails } from '../models/WalletDetails' -import { PaymentProvider } from '../models/PaymentProvider' -import { WinningDetail } from '../models/WinningDetail' -import { TaxForm } from '../models/TaxForm' -import { OtpVerificationResponse } from '../models/OtpVerificationResponse' -import { TransactionResponse } from '../models/TransactionId' -import ApiResponse from '../models/ApiResponse' - -const baseUrl = `${EnvironmentConfig.API.V5}/payments` - -export async function getWalletDetails(): Promise { - const response = await xhrGetAsync>(`${baseUrl}/wallet`) - - if (response.status === 'error') { - throw new Error('Error fetching wallet details') - } - - return response.data -} - -export async function getUserPaymentProviders(): Promise { - const response = await xhrGetAsync>(`${baseUrl}/user/payment-methods`) - - if (response.status === 'error') { - throw new Error('Error fetching user payment providers') - } - - return response.data -} - -export async function getUserTaxFormDetails(): Promise { - const response = await xhrGetAsync>(`${baseUrl}/user/tax-forms`) - if (response.status === 'error') { - throw new Error('Error fetching user tax form details') - } - - return response.data -} - -export async function getPayments(userId: string): Promise { - const body = JSON.stringify({ - winnerId: userId, - }) - - const url = `${baseUrl}/user/winnings` - const response = await xhrPostAsync>(url, body) - - if (response.status === 'error') { - throw new Error('Error fetching payments') - } - - return response.data -} - -export async function setPaymentProvider( - type: string, -): Promise { - const body = JSON.stringify({ - details: {}, - setDefault: true, - type, - }) - - const url = `${baseUrl}/user/payment-method` - const response = await xhrPostAsync>(url, body) - - if (response.status === 'error') { - throw new Error('Error setting payment provider') - } - - return response.data -} - -export async function confirmPaymentProvider(provider: string, code: string, transactionId: string): Promise { - const body = JSON.stringify({ - code, - provider, - transactionId, - }) - - const url = `${baseUrl}/payment-provider/paypal/confirm` - const response = await xhrPostAsync>(url, body) - - if (response.status === 'error') { - throw new Error('Error confirming payment provider') - } - - return response.data -} - -export async function getPaymentProviderRegistrationLink(type: string): Promise { - const url = `${baseUrl}/user/payment-method/${type}/registration-link` - const response = await xhrGetAsync>(url) - - if (response.status === 'error') { - throw new Error('Error getting payment provider registration link') - } - - return response.data -} - -export async function removePaymentProvider(type: string): Promise { - const url = `${baseUrl}/user/payment-method/${type}` - const response = await xhrDeleteAsync>(url) - - if (response.status === 'error') { - throw new Error('Error getting payment provider registration link') - } - - return response.data -} - -export async function setupTaxForm(userId: string, taxForm: string): Promise { - const body = JSON.stringify({ - taxForm, - userId, - }) - - const url = `${baseUrl}/user/tax-form` - const response = await xhrPostAsync>(url, body) - - if (response.status === 'error') { - throw new Error('Error setting tax form') - } - - return response.data -} - -export async function removeTaxForm(taxFormId: string): Promise { - const url = `${baseUrl}/user/tax-forms/${taxFormId}` - const response = await xhrDeleteAsync>(url) - - if (response.status === 'error') { - throw new Error('Error removing tax form') - } - - return response.data -} - -export async function getRecipientViewURL(): Promise { - const url = `${baseUrl}/user/tax-form/view` - const response = await xhrGetAsync>(url) - - if (response.status === 'error') { - throw new Error('Error removing tax form') - } - - return response.data -} - -export async function processPayments(paymentIds: string[]): Promise<{ processed: boolean }> { - const body = JSON.stringify({ - paymentIds, - }) - const url = `${baseUrl}/withdraw` - const response = await xhrPostAsync>(url, body) - - if (response.status === 'error') { - throw new Error('Error processing payments') - } - - return response.data -} - -// eslint-disable-next-line max-len -export async function verifyOtp(transactionId: string, code: string, blob: boolean = false): Promise { - const body = JSON.stringify({ - otpCode: code, - transactionId, - }) - - const url = `${baseUrl}/otp/verify` - try { - // eslint-disable-next-line max-len - const response = await xhrPostAsyncWithBlobHandling | Blob>(url, body, { - responseType: blob ? 'blob' : 'json', - }) - - if (response instanceof Blob) { - return response as Blob - } - - if (response.status === 'error') { - throw new Error('OTP verification failed or OTP has expired') - } - - return response.data - } catch (err) { - throw new Error('OTP verification failed or OTP has expired') - } -} - -export async function resendOtp(transactionId: string): Promise { - const body = JSON.stringify({ - transactionId, - }) - - const url = `${baseUrl}/otp/resend` - try { - const response = await xhrPostAsync>(url, body) - - if (response.status === 'error') { - throw new Error('Failed to resend OTP.') - } - - return response.data - } catch (err) { - if (err instanceof AxiosError && err.response?.data?.error !== undefined) { - throw new Error(err.response.data.error?.message) - } - - throw new Error('Failed to resend OTP.') - } -} - -export async function fetchTaxForms(pageNumber: number, pageSize: number, userIds: string[]): Promise { - const body = JSON.stringify({ - pageNumber, - pageSize, - userIds, - }) - - try { - const response = await xhrPostAsync>(`${baseUrl}/admin/tax-forms`, body) - - if (response.status === 'error') { - throw new Error('Error fetching tax forms') - } - - return response.data - } catch (err) { - throw new Error('Error fetching tax forms') - } -} - -export async function fetchPaymentProviders(pageNumber: number, pageSize: number, userIds: string[]): Promise { - const body = JSON.stringify({ - pageNumber, - pageSize, - userIds, - }) - - try { - const response = await xhrPostAsync>(`${baseUrl}/admin/payment-methods`, body) - - if (response.status === 'error') { - throw new Error('Error fetching tax forms') - } - - return response.data - } catch (err) { - throw new Error('Error fetching tax forms') - } -} - -export async function fetchPaymentProviderDetail(userId: string, paymentProviderId: string): Promise { - const url = `${baseUrl}/admin/payment-methods/${userId}/${paymentProviderId}` - try { - const response = await xhrGetAsync>(url) - - if (response.status === 'error') { - throw new Error('Error fetching tax forms') - } - - return response.data - } catch (err) { - throw new Error('Error fetching tax forms') - } -} - -export async function deletePaymentProvider(userId: string, paymentProviderId: string): Promise { - const url = `${baseUrl}/admin/payment-methods/${userId}/${paymentProviderId}` - try { - const response = await xhrDeleteAsync>(url) - - if (response.status === 'error') { - throw new Error('Error fetching tax forms') - } - - return response.data - } catch (err) { - throw new Error('Error fetching tax forms') - } -} - -export async function fetchTaxFormDetail(userId: string, taxFormId: string): Promise { - const url = `${baseUrl}/admin/tax-forms/${userId}/${taxFormId}/download` - try { - const response = await getAsyncWithBlobHandling>(url) - - console.log('Response', response) - - return response - } catch (err) { - throw new Error('Error fetching tax forms') - } -} - -export async function deleteTaxForm(userId: string, taxFormId: string): Promise { - const url = `${baseUrl}/admin/tax-forms/${userId}/${taxFormId}` - try { - const response = await xhrDeleteAsync>(url) - - if (response.status === 'error') { - throw new Error('Error fetching tax forms') - } - - return response.data - } catch (err) { - throw new Error('Error fetching tax forms') - } -} - -export async function searchWinnings(pageNumber: number, pageSize: number, filters: any): Promise { - const body = JSON.stringify({ - externalIds: filters.externalIds, - winnerId: filters.winnerId, - }) - - try { - const response = await xhrPostAsync>(`${baseUrl}/admin/winnings/search`, body) - - if (response.status === 'error') { - throw new Error('Error fetching tax forms') - } - - return response.data - } catch (err) { - throw new Error('Error fetching tax forms') - } -} - -export async function editWinningRecord(winningId: string, paymentId: string, status: string): Promise { - const body = JSON.stringify({ - paymentId, - paymentStatus: status, - winningsId: winningId, - }) - - try { - const response = await xhrPatchAsync>(`${baseUrl}/admin/winnings`, body) - - if (response.status === 'error') { - throw new Error('Error updating payment record') - } - - return response.data - } catch (err) { - throw new Error('Error updating payment record') - } -} diff --git a/src/apps/wallet-admin/src/lib/wallet-swr/WalletSwr.tsx b/src/apps/wallet-admin/src/lib/wallet-swr/WalletSwr.tsx deleted file mode 100644 index ae683103f..000000000 --- a/src/apps/wallet-admin/src/lib/wallet-swr/WalletSwr.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { FC, ReactNode } from 'react' -import { SWRConfig } from 'swr' - -import { xhrGetAsync } from '~/libs/core' - -interface WalletSwrProps { - children: ReactNode -} - -const WalletSwr: FC = (props: WalletSwrProps) => ( - xhrGetAsync(resource), - refreshInterval: 0, - revalidateOnFocus: false, - revalidateOnMount: true, - }} - > - {props.children} - -) - -export default WalletSwr diff --git a/src/apps/wallet-admin/src/lib/wallet-swr/index.ts b/src/apps/wallet-admin/src/lib/wallet-swr/index.ts deleted file mode 100644 index 31c20cbba..000000000 --- a/src/apps/wallet-admin/src/lib/wallet-swr/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as WalletSwr } from './WalletSwr' diff --git a/src/apps/wallet-admin/src/wallet-admin.routes.tsx b/src/apps/wallet-admin/src/wallet-admin.routes.tsx deleted file mode 100644 index 1214379e3..000000000 --- a/src/apps/wallet-admin/src/wallet-admin.routes.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { lazyLoad, LazyLoadedComponent, PlatformRoute } from '~/libs/core' -import { AppSubdomain, EnvironmentConfig, ToolTitle } from '~/config' - -const WalletApp: LazyLoadedComponent = lazyLoad(() => import('./WalletAdminApp')) -const WalletHomePage: LazyLoadedComponent = lazyLoad( - () => import('./home'), - 'WalletHomePage', -) - -// prettier-ignore -// eslint-disable-next-line max-len -export const rootRoute: string = EnvironmentConfig.SUBDOMAIN === AppSubdomain.walletAdmin ? '' : `/${AppSubdomain.walletAdmin}` - -export const toolTitle = ToolTitle.walletAdmin -export const absoluteRootRoute: string = `${window.location.origin}/${rootRoute}` - -export const walletAdminRoutes: ReadonlyArray = [ - { - authRequired: true, - children: [ - { - children: [], - element: , - id: 'Dashboard', - route: '', - }, - ], - domain: AppSubdomain.walletAdmin, - element: , - id: toolTitle, - rolesRequired: ['Payment Admin'], - route: rootRoute, - }, -] diff --git a/src/config/constants.ts b/src/config/constants.ts index e2469a4dd..1275e8e47 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -9,7 +9,6 @@ export enum AppSubdomain { work = 'work', talentSearch = 'talent-search', wallet = 'wallet', - walletAdmin = 'wallet-admin', } export enum ToolTitle { diff --git a/tsconfig.paths.json b/tsconfig.paths.json index 21ca9255e..3f83bc357 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -30,9 +30,6 @@ "@wallet/*": [ "./src/apps/wallet/src/*" ], - "@walletAdmin/*": [ - "./src/apps/wallet-admin/src/*" - ], "@libs/ui/styles/*": [ "./src/libs/ui/lib/styles/*" ]
-
-
{props.icon}
-

{props.formTitle}

-
- - - -
-
{props.formDescription}
- -

{props.reasonTitle}

-
{props.reasonDescription}
- - {props.additionalInfo?.link && ( -
- )} - - {props.additionalInfo?.purpose && ( -
-

{props.additionalInfo.purpose.title}

-
    - {props.additionalInfo.purpose.points.map((point: string) => ( -
  • {point}
  • - ))} -
-
- )} - - {props.additionalInfo?.note && ( -
-
{props.additionalInfo.note}
-
- )} -
- -
-
-
+
+
{props.icon}
+

{props.formTitle}

+
+ + + +
+
{props.formDescription}
+ +

{props.reasonTitle}

+
{props.reasonDescription}
+ + {props.additionalInfo?.link && ( +
+ )} + + {props.additionalInfo?.purpose && ( +
+

{props.additionalInfo.purpose.title}

+
    + {props.additionalInfo.purpose.points.map((point: string) => ( +
  • {point}
  • + ))} +
+
+ )} + + {props.additionalInfo?.note && ( +
+
{props.additionalInfo.note}
+
+ )} +
+ +
+
+
+
+
{props.icon}
+

{props.formTitle}

+
+ + + +
+
{props.formDescription}
+ +

{props.reasonTitle}

+
{props.reasonDescription}
+ + {props.additionalInfo?.link && ( +
+ )} + + {props.additionalInfo?.purpose && ( +
+

{props.additionalInfo.purpose.title}

+
    + {props.additionalInfo.purpose.points.map((point: string) => ( +
  • {point}
  • + ))} +
+
+ )} + + {props.additionalInfo?.note && ( +
+
{props.additionalInfo.note}
+
+ )} +
+ +
+
+