Skip to content

PM-1222 - show taxes & fees applied to payments #1074

New issue

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

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

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions src/apps/wallet/src/home/tabs/home/HomeTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -17,27 +18,8 @@ interface HomeTabProps {
}

const HomeTab: FC<HomeTabProps> = () => {
const [walletDetails, setWalletDetails] = useState<WalletDetails | undefined>(undefined)
const [isLoading, setIsLoading] = useState(false)
const { data: walletDetails, isLoading, error }: WalletDetailsResponse = useWalletDetails()
const [balanceSum, setBalanceSum] = useState(0)
const [error, setError] = useState<string | undefined>(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) {
Expand All @@ -58,7 +40,7 @@ const HomeTab: FC<HomeTabProps> = () => {
<BannerImage />
</div>
{isLoading && <LoadingCircles />}
{!isLoading && (
{!isLoading && walletDetails && (
<div className={styles['info-row-container']}>
<InfoRow
title='Account Balance'
Expand All @@ -75,6 +57,24 @@ const HomeTab: FC<HomeTabProps> = () => {
}
/>

{walletDetails.withdrawalMethod.isSetupComplete && walletDetails.taxForm.isSetupComplete && (
<InfoRow
title='Est. Payment Fees and Tax Withholding %'
// eslint-disable-next-line max-len
value={`Fee: ${nullToZero(walletDetails.estimatedFees)} USD / Tax Withholding: ${nullToZero(walletDetails.taxWithholdingPercentage)}%`}
action={
<LinkButton
label='ADJUST YOUR PAYOUT SETTINGS'
iconToRight
icon={IconOutline.ArrowRightIcon}
size='md'
link
to='#payout'
/>
}
/>
)}

{!walletDetails?.withdrawalMethod.isSetupComplete && (
<InfoRow
title='Withdrawal Method'
Expand Down
15 changes: 15 additions & 0 deletions src/apps/wallet/src/home/tabs/winnings/Winnings.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,20 @@
justify-content: space-around;
align-items: center;
}

}
}

.taxes {
display: block;
line-height: 20px;
font-size: 14px;

+ .taxes {
margin-top: 10px;
}

&.mt {
margin-top: 20px;
}
}
69 changes: 58 additions & 11 deletions src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable react/jsx-no-bind */
import { toast } from 'react-toastify'
import { AxiosError } from 'axios'
import { Link } from 'react-router-dom'
import React, { FC, useCallback, useEffect } from 'react'

import { Collapsible, ConfirmModal, LoadingCircles } from '~/libs/ui'
Expand All @@ -12,6 +13,8 @@ import { Winning, WinningDetail } from '../../../lib/models/WinningDetail'
import { FilterBar } from '../../../lib'
import { ConfirmFlowData } from '../../../lib/models/ConfirmFlowData'
import { PaginationInfo } from '../../../lib/models/PaginationInfo'
import { useWalletDetails, WalletDetailsResponse } from '../../../lib/hooks/use-wallet-details'
import { nullToZero } from '../../../lib/util'
import PaymentsTable from '../../../lib/components/payments-table/PaymentTable'

import styles from './Winnings.module.scss'
Expand Down Expand Up @@ -74,6 +77,8 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
const [selectedPayments, setSelectedPayments] = React.useState<{ [paymentId: string]: Winning }>({})
const [isLoading, setIsLoading] = React.useState<boolean>(false)
const [filters, setFilters] = React.useState<Record<string, string[]>>({})
const { data: walletDetails }: WalletDetailsResponse = useWalletDetails()

const [pagination, setPagination] = React.useState<PaginationInfo>({
currentPage: 1,
pageSize: 10,
Expand Down Expand Up @@ -180,6 +185,58 @@ const ListView: FC<ListViewProps> = (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}
.
<br />
<br />
{walletDetails && (
<>
<div className={styles.taxes}>
Est. Payment Fees:
{' '}
{nullToZero(walletDetails.estimatedFees)}
{' '}
USD and Tax Withholding:
{' '}
{`${nullToZero(walletDetails.taxWithholdingPercentage)}%`}
{' '}
will be applied on the payment.
</div>
<div className={styles.taxes}>
{walletDetails.primaryCurrency && (
<>
You will receive the payment in
{' '}
{walletDetails.primaryCurrency}
{' '}
currency after 2% coversion fees applied.
</>
)}
</div>
<div className={`${styles.taxes} ${styles.mt}`}>
You can adjust your payout settings to customize your estimated payment fee and tax withholding percentage in
{' '}
<Link to='#payout'>Payout</Link>
</div>
</>
)}
</>
),
title: 'Are you sure?',
})
}

return (
<>
<div className={styles.container}>
Expand Down Expand Up @@ -338,17 +395,7 @@ const ListView: FC<ListViewProps> = (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 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
align-items: center;
flex-grow: 1;
padding-left: 10px;
gap: 200px;
gap: 50px;
}

.value {
Expand Down
24 changes: 24 additions & 0 deletions src/apps/wallet/src/lib/hooks/use-wallet-details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import useSWR, { KeyedMutator, SWRResponse } from 'swr'

import { WalletDetails } from '../models/WalletDetails'
import { getWalletDetails } from '../services/wallet'

export interface Response<T> {
data?: Readonly<T>
error?: Readonly<string>
mutate: KeyedMutator<any>
isLoading?: Readonly<boolean>
}

export type WalletDetailsResponse = Response<WalletDetails>

export function useWalletDetails(): WalletDetailsResponse {
const { data, error, mutate, isValidating }: SWRResponse = useSWR('wallet-details', getWalletDetails)

return {
data,
error,
isLoading: isValidating && !data && !error,
mutate,
}
}
3 changes: 3 additions & 0 deletions src/apps/wallet/src/lib/models/WalletDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ export interface WalletDetails {
taxForm: {
isSetupComplete: boolean
}
primaryCurrency?: string | null;
estimatedFees?: string | null;
taxWithholdingPercentage?: string | null;
}
14 changes: 7 additions & 7 deletions src/apps/wallet/src/lib/services/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<WalletDetails> {
const response = await xhrGetAsync<ApiResponse<WalletDetails>>(`${baseUrl}/wallet`)
const response = await xhrGetAsync<ApiResponse<WalletDetails>>(`${WALLET_API_BASE_URL}/wallet`)

if (response.status === 'error') {
throw new Error('Error fetching wallet details')
Expand Down Expand Up @@ -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<string, ApiResponse<{
winnings: WinningDetail[],
pagination: PaginationInfo
Expand All @@ -64,7 +64,7 @@ export async function processWinningsPayments(winningsIds: string[]): Promise<{
const body = JSON.stringify({
winningsIds,
})
const url = `${baseUrl}/withdraw`
const url = `${WALLET_API_BASE_URL}/withdraw`
const response = await xhrPostAsync<string, ApiResponse<{ processed: boolean }>>(url, body)

if (response.status === 'error') {
Expand All @@ -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<string, ApiResponse<OtpVerificationResponse> | Blob>(url, body, {
Expand All @@ -107,7 +107,7 @@ export async function resendOtp(transactionId: string): Promise<TransactionRespo
transactionId,
})

const url = `${baseUrl}/otp/resend`
const url = `${WALLET_API_BASE_URL}/otp/resend`
try {
const response = await xhrPostAsync<string, ApiResponse<TransactionResponse>>(url, body)

Expand All @@ -132,7 +132,7 @@ export async function resendOtp(transactionId: string): Promise<TransactionRespo
* @throws {Error} If the response does not contain a valid link.
*/
export async function getTrolleyPortalLink(): Promise<string> {
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) {
Expand Down
1 change: 1 addition & 0 deletions src/apps/wallet/src/lib/util/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './null-to-zero'
7 changes: 7 additions & 0 deletions src/apps/wallet/src/lib/util/null-to-zero.ts
Original file line number Diff line number Diff line change
@@ -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')