From 6a78ab23c044c43e337fcee35931d704cf9c8e5d Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 15 May 2025 14:32:34 +0300 Subject: [PATCH] PM-1222 - show taxes & fees applied to payments --- .../wallet/src/home/tabs/home/HomeTab.tsx | 46 ++++++------- .../home/tabs/winnings/Winnings.module.scss | 15 ++++ .../src/home/tabs/winnings/WinningsTab.tsx | 69 ++++++++++++++++--- .../components/info-row/InfoRow.module.scss | 2 +- .../src/lib/hooks/use-wallet-details.ts | 24 +++++++ .../wallet/src/lib/models/WalletDetails.ts | 3 + src/apps/wallet/src/lib/services/wallet.ts | 14 ++-- src/apps/wallet/src/lib/util/index.ts | 1 + src/apps/wallet/src/lib/util/null-to-zero.ts | 7 ++ 9 files changed, 139 insertions(+), 42 deletions(-) create mode 100644 src/apps/wallet/src/lib/hooks/use-wallet-details.ts create mode 100644 src/apps/wallet/src/lib/util/index.ts create mode 100644 src/apps/wallet/src/lib/util/null-to-zero.ts diff --git a/src/apps/wallet/src/home/tabs/home/HomeTab.tsx b/src/apps/wallet/src/home/tabs/home/HomeTab.tsx index db719120e..4d37d701a 100644 --- a/src/apps/wallet/src/home/tabs/home/HomeTab.tsx +++ b/src/apps/wallet/src/home/tabs/home/HomeTab.tsx @@ -4,10 +4,11 @@ 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 { Balance } from '../../../lib/models/WalletDetails' import { InfoRow } from '../../../lib' import { BannerImage, BannerText } from '../../../lib/assets/home' +import { nullToZero } from '../../../lib/util' +import { useWalletDetails, WalletDetailsResponse } from '../../../lib/hooks/use-wallet-details' import Chip from '../../../lib/components/chip/Chip' import styles from './Home.module.scss' @@ -17,27 +18,8 @@ interface HomeTabProps { } const HomeTab: FC = () => { - const [walletDetails, setWalletDetails] = useState(undefined) - const [isLoading, setIsLoading] = useState(false) + const { data: walletDetails, isLoading, error }: WalletDetailsResponse = useWalletDetails() 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) { @@ -58,7 +40,7 @@ const HomeTab: FC = () => { {isLoading && } - {!isLoading && ( + {!isLoading && walletDetails && (
= () => { } /> + {walletDetails.withdrawalMethod.isSetupComplete && walletDetails.taxForm.isSetupComplete && ( + + } + /> + )} + {!walletDetails?.withdrawalMethod.isSetupComplete && ( = (props: ListViewProps) => { const [selectedPayments, setSelectedPayments] = React.useState<{ [paymentId: string]: Winning }>({}) const [isLoading, setIsLoading] = React.useState(false) const [filters, setFilters] = React.useState>({}) + const { data: walletDetails }: WalletDetailsResponse = useWalletDetails() + const [pagination, setPagination] = React.useState({ currentPage: 1, pageSize: 10, @@ -180,6 +185,58 @@ const ListView: FC = (props: ListViewProps) => { fetchWinnings() } + function handlePayMeClick( + paymentIds: { [paymentId: string]: Winning }, + totalAmount: string, + ): void { + setConfirmFlow({ + action: 'Yes', + callback: () => processPayouts(Object.keys(paymentIds)), + content: ( + <> + You are about to process payments for a total of USD + {' '} + {totalAmount} + . +
+
+ {walletDetails && ( + <> +
+ Est. Payment Fees: + {' '} + {nullToZero(walletDetails.estimatedFees)} + {' '} + USD and Tax Withholding: + {' '} + {`${nullToZero(walletDetails.taxWithholdingPercentage)}%`} + {' '} + will be applied on the payment. +
+
+ {walletDetails.primaryCurrency && ( + <> + You will receive the payment in + {' '} + {walletDetails.primaryCurrency} + {' '} + currency after 2% coversion fees applied. + + )} +
+
+ You can adjust your payout settings to customize your estimated payment fee and tax withholding percentage in + {' '} + Payout +
+ + )} + + ), + title: 'Are you sure?', + }) + } + return ( <>
@@ -338,17 +395,7 @@ const ListView: FC = (props: ListViewProps) => { currentPage: pageNumber, }) }} - onPayMeClick={async function onPayMeClicked( - paymentIds: { [paymentId: string]: Winning }, - totalAmount: string, - ) { - setConfirmFlow({ - action: 'Yes', - callback: () => processPayouts(Object.keys(paymentIds)), - content: `You are about to process payments for a total of USD ${totalAmount}`, - title: 'Are you sure?', - }) - }} + onPayMeClick={handlePayMeClick} /> )} {!isLoading && winnings.length === 0 && ( 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 index f44d056e0..b0d029dc9 100644 --- a/src/apps/wallet/src/lib/components/info-row/InfoRow.module.scss +++ b/src/apps/wallet/src/lib/components/info-row/InfoRow.module.scss @@ -23,7 +23,7 @@ align-items: center; flex-grow: 1; padding-left: 10px; - gap: 200px; + gap: 50px; } .value { diff --git a/src/apps/wallet/src/lib/hooks/use-wallet-details.ts b/src/apps/wallet/src/lib/hooks/use-wallet-details.ts new file mode 100644 index 000000000..d6dbfde92 --- /dev/null +++ b/src/apps/wallet/src/lib/hooks/use-wallet-details.ts @@ -0,0 +1,24 @@ +import useSWR, { KeyedMutator, SWRResponse } from 'swr' + +import { WalletDetails } from '../models/WalletDetails' +import { getWalletDetails } from '../services/wallet' + +export interface Response { + data?: Readonly + error?: Readonly + mutate: KeyedMutator + isLoading?: Readonly +} + +export type WalletDetailsResponse = Response + +export function useWalletDetails(): WalletDetailsResponse { + const { data, error, mutate, isValidating }: SWRResponse = useSWR('wallet-details', getWalletDetails) + + return { + data, + error, + isLoading: isValidating && !data && !error, + mutate, + } +} diff --git a/src/apps/wallet/src/lib/models/WalletDetails.ts b/src/apps/wallet/src/lib/models/WalletDetails.ts index 422fcf029..752acef0a 100644 --- a/src/apps/wallet/src/lib/models/WalletDetails.ts +++ b/src/apps/wallet/src/lib/models/WalletDetails.ts @@ -17,4 +17,7 @@ export interface WalletDetails { taxForm: { isSetupComplete: boolean } + primaryCurrency?: string | null; + estimatedFees?: string | null; + taxWithholdingPercentage?: string | null; } diff --git a/src/apps/wallet/src/lib/services/wallet.ts b/src/apps/wallet/src/lib/services/wallet.ts index ecd20191a..e557332b1 100644 --- a/src/apps/wallet/src/lib/services/wallet.ts +++ b/src/apps/wallet/src/lib/services/wallet.ts @@ -11,10 +11,10 @@ import { TransactionResponse } from '../models/TransactionId' import { PaginationInfo } from '../models/PaginationInfo' import ApiResponse from '../models/ApiResponse' -const baseUrl = `${EnvironmentConfig.TC_FINANCE_API}` +export const WALLET_API_BASE_URL = `${EnvironmentConfig.TC_FINANCE_API}` export async function getWalletDetails(): Promise { - const response = await xhrGetAsync>(`${baseUrl}/wallet`) + const response = await xhrGetAsync>(`${WALLET_API_BASE_URL}/wallet`) if (response.status === 'error') { throw new Error('Error fetching wallet details') @@ -43,7 +43,7 @@ export async function getPayments(userId: string, limit: number, offset: number, ...filteredFilters, }) - const url = `${baseUrl}/user/winnings` + const url = `${WALLET_API_BASE_URL}/user/winnings` const response = await xhrPostAsync>(url, body) if (response.status === 'error') { @@ -81,7 +81,7 @@ export async function verifyOtp(transactionId: string, code: string, blob: boole transactionId, }) - const url = `${baseUrl}/otp/verify` + const url = `${WALLET_API_BASE_URL}/otp/verify` try { // eslint-disable-next-line max-len const response = await xhrPostAsyncWithBlobHandling | Blob>(url, body, { @@ -107,7 +107,7 @@ export async function resendOtp(transactionId: string): Promise>(url, body) @@ -132,7 +132,7 @@ export async function resendOtp(transactionId: string): Promise { - const url = `${baseUrl}/trolley/portal-link` + const url = `${WALLET_API_BASE_URL}/trolley/portal-link` const response = await xhrGetAsync<{ link: string }>(url) if (!response.link) { diff --git a/src/apps/wallet/src/lib/util/index.ts b/src/apps/wallet/src/lib/util/index.ts new file mode 100644 index 000000000..e0c54cf9c --- /dev/null +++ b/src/apps/wallet/src/lib/util/index.ts @@ -0,0 +1 @@ +export * from './null-to-zero' diff --git a/src/apps/wallet/src/lib/util/null-to-zero.ts b/src/apps/wallet/src/lib/util/null-to-zero.ts new file mode 100644 index 000000000..1c79e6f63 --- /dev/null +++ b/src/apps/wallet/src/lib/util/null-to-zero.ts @@ -0,0 +1,7 @@ +/** + * Converts a given value to the string `'0'` if it is `null`, `undefined`, or the string `'null'`. + * + * @param value - The input value which can be a string, `null`, or `undefined`. + * @returns The original value if it is a valid string (and not `'null'`), otherwise returns `'0'`. + */ +export const nullToZero = (value: string | null | undefined): string => (value === 'null' ? '0' : value ?? '0')