Skip to content

Commit f1fd9b7

Browse files
authored
Merge pull request #1075 from topcoder-platform/PM-959_tc-finance-integration
Pm 959 tc finance integration
2 parents ca49ea6 + 13d048a commit f1fd9b7

File tree

9 files changed

+139
-42
lines changed

9 files changed

+139
-42
lines changed

src/apps/wallet/src/home/tabs/home/HomeTab.tsx

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { FC, useEffect, useState } from 'react'
44
import { UserProfile } from '~/libs/core'
55
import { IconOutline, LinkButton, LoadingCircles } from '~/libs/ui'
66

7-
import { Balance, WalletDetails } from '../../../lib/models/WalletDetails'
8-
import { getWalletDetails } from '../../../lib/services/wallet'
7+
import { Balance } from '../../../lib/models/WalletDetails'
98
import { InfoRow } from '../../../lib'
109
import { BannerImage, BannerText } from '../../../lib/assets/home'
10+
import { nullToZero } from '../../../lib/util'
11+
import { useWalletDetails, WalletDetailsResponse } from '../../../lib/hooks/use-wallet-details'
1112
import Chip from '../../../lib/components/chip/Chip'
1213

1314
import styles from './Home.module.scss'
@@ -17,27 +18,8 @@ interface HomeTabProps {
1718
}
1819

1920
const HomeTab: FC<HomeTabProps> = () => {
20-
const [walletDetails, setWalletDetails] = useState<WalletDetails | undefined>(undefined)
21-
const [isLoading, setIsLoading] = useState(false)
21+
const { data: walletDetails, isLoading, error }: WalletDetailsResponse = useWalletDetails()
2222
const [balanceSum, setBalanceSum] = useState(0)
23-
const [error, setError] = useState<string | undefined>(undefined)
24-
25-
useEffect(() => {
26-
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
27-
const fetchWalletDetails = async () => {
28-
setIsLoading(true)
29-
try {
30-
const details = await getWalletDetails()
31-
setWalletDetails(details)
32-
} catch (apiError) {
33-
setError('Error fetching wallet details')
34-
}
35-
36-
setIsLoading(false)
37-
}
38-
39-
fetchWalletDetails()
40-
}, [])
4123

4224
useEffect(() => {
4325
if (walletDetails) {
@@ -58,7 +40,7 @@ const HomeTab: FC<HomeTabProps> = () => {
5840
<BannerImage />
5941
</div>
6042
{isLoading && <LoadingCircles />}
61-
{!isLoading && (
43+
{!isLoading && walletDetails && (
6244
<div className={styles['info-row-container']}>
6345
<InfoRow
6446
title='Account Balance'
@@ -75,6 +57,24 @@ const HomeTab: FC<HomeTabProps> = () => {
7557
}
7658
/>
7759

60+
{walletDetails.withdrawalMethod.isSetupComplete && walletDetails.taxForm.isSetupComplete && (
61+
<InfoRow
62+
title='Est. Payment Fees and Tax Withholding %'
63+
// eslint-disable-next-line max-len
64+
value={`Fee: ${nullToZero(walletDetails.estimatedFees)} USD / Tax Withholding: ${nullToZero(walletDetails.taxWithholdingPercentage)}%`}
65+
action={
66+
<LinkButton
67+
label='ADJUST YOUR PAYOUT SETTINGS'
68+
iconToRight
69+
icon={IconOutline.ArrowRightIcon}
70+
size='md'
71+
link
72+
to='#payout'
73+
/>
74+
}
75+
/>
76+
)}
77+
7878
{!walletDetails?.withdrawalMethod.isSetupComplete && (
7979
<InfoRow
8080
title='Withdrawal Method'

src/apps/wallet/src/home/tabs/winnings/Winnings.module.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,20 @@
3131
justify-content: space-around;
3232
align-items: center;
3333
}
34+
35+
}
36+
}
37+
38+
.taxes {
39+
display: block;
40+
line-height: 20px;
41+
font-size: 14px;
42+
43+
+ .taxes {
44+
margin-top: 10px;
45+
}
46+
47+
&.mt {
48+
margin-top: 20px;
3449
}
3550
}

src/apps/wallet/src/home/tabs/winnings/WinningsTab.tsx

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* eslint-disable react/jsx-no-bind */
33
import { toast } from 'react-toastify'
44
import { AxiosError } from 'axios'
5+
import { Link } from 'react-router-dom'
56
import React, { FC, useCallback, useEffect } from 'react'
67

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

1720
import styles from './Winnings.module.scss'
@@ -74,6 +77,8 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
7477
const [selectedPayments, setSelectedPayments] = React.useState<{ [paymentId: string]: Winning }>({})
7578
const [isLoading, setIsLoading] = React.useState<boolean>(false)
7679
const [filters, setFilters] = React.useState<Record<string, string[]>>({})
80+
const { data: walletDetails }: WalletDetailsResponse = useWalletDetails()
81+
7782
const [pagination, setPagination] = React.useState<PaginationInfo>({
7883
currentPage: 1,
7984
pageSize: 10,
@@ -180,6 +185,58 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
180185
fetchWinnings()
181186
}
182187

188+
function handlePayMeClick(
189+
paymentIds: { [paymentId: string]: Winning },
190+
totalAmount: string,
191+
): void {
192+
setConfirmFlow({
193+
action: 'Yes',
194+
callback: () => processPayouts(Object.keys(paymentIds)),
195+
content: (
196+
<>
197+
You are about to process payments for a total of USD
198+
{' '}
199+
{totalAmount}
200+
.
201+
<br />
202+
<br />
203+
{walletDetails && (
204+
<>
205+
<div className={styles.taxes}>
206+
Est. Payment Fees:
207+
{' '}
208+
{nullToZero(walletDetails.estimatedFees)}
209+
{' '}
210+
USD and Tax Withholding:
211+
{' '}
212+
{`${nullToZero(walletDetails.taxWithholdingPercentage)}%`}
213+
{' '}
214+
will be applied on the payment.
215+
</div>
216+
<div className={styles.taxes}>
217+
{walletDetails.primaryCurrency && (
218+
<>
219+
You will receive the payment in
220+
{' '}
221+
{walletDetails.primaryCurrency}
222+
{' '}
223+
currency after 2% coversion fees applied.
224+
</>
225+
)}
226+
</div>
227+
<div className={`${styles.taxes} ${styles.mt}`}>
228+
You can adjust your payout settings to customize your estimated payment fee and tax withholding percentage in
229+
{' '}
230+
<Link to='#payout'>Payout</Link>
231+
</div>
232+
</>
233+
)}
234+
</>
235+
),
236+
title: 'Are you sure?',
237+
})
238+
}
239+
183240
return (
184241
<>
185242
<div className={styles.container}>
@@ -338,17 +395,7 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
338395
currentPage: pageNumber,
339396
})
340397
}}
341-
onPayMeClick={async function onPayMeClicked(
342-
paymentIds: { [paymentId: string]: Winning },
343-
totalAmount: string,
344-
) {
345-
setConfirmFlow({
346-
action: 'Yes',
347-
callback: () => processPayouts(Object.keys(paymentIds)),
348-
content: `You are about to process payments for a total of USD ${totalAmount}`,
349-
title: 'Are you sure?',
350-
})
351-
}}
398+
onPayMeClick={handlePayMeClick}
352399
/>
353400
)}
354401
{!isLoading && winnings.length === 0 && (

src/apps/wallet/src/lib/components/info-row/InfoRow.module.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
align-items: center;
2424
flex-grow: 1;
2525
padding-left: 10px;
26-
gap: 200px;
26+
gap: 50px;
2727
}
2828

2929
.value {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import useSWR, { KeyedMutator, SWRResponse } from 'swr'
2+
3+
import { WalletDetails } from '../models/WalletDetails'
4+
import { getWalletDetails } from '../services/wallet'
5+
6+
export interface Response<T> {
7+
data?: Readonly<T>
8+
error?: Readonly<string>
9+
mutate: KeyedMutator<any>
10+
isLoading?: Readonly<boolean>
11+
}
12+
13+
export type WalletDetailsResponse = Response<WalletDetails>
14+
15+
export function useWalletDetails(): WalletDetailsResponse {
16+
const { data, error, mutate, isValidating }: SWRResponse = useSWR('wallet-details', getWalletDetails)
17+
18+
return {
19+
data,
20+
error,
21+
isLoading: isValidating && !data && !error,
22+
mutate,
23+
}
24+
}

src/apps/wallet/src/lib/models/WalletDetails.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ export interface WalletDetails {
1717
taxForm: {
1818
isSetupComplete: boolean
1919
}
20+
primaryCurrency?: string | null;
21+
estimatedFees?: string | null;
22+
taxWithholdingPercentage?: string | null;
2023
}

src/apps/wallet/src/lib/services/wallet.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import { TransactionResponse } from '../models/TransactionId'
1111
import { PaginationInfo } from '../models/PaginationInfo'
1212
import ApiResponse from '../models/ApiResponse'
1313

14-
const baseUrl = `${EnvironmentConfig.TC_FINANCE_API}`
14+
export const WALLET_API_BASE_URL = `${EnvironmentConfig.TC_FINANCE_API}`
1515

1616
export async function getWalletDetails(): Promise<WalletDetails> {
17-
const response = await xhrGetAsync<ApiResponse<WalletDetails>>(`${baseUrl}/wallet`)
17+
const response = await xhrGetAsync<ApiResponse<WalletDetails>>(`${WALLET_API_BASE_URL}/wallet`)
1818

1919
if (response.status === 'error') {
2020
throw new Error('Error fetching wallet details')
@@ -43,7 +43,7 @@ export async function getPayments(userId: string, limit: number, offset: number,
4343
...filteredFilters,
4444
})
4545

46-
const url = `${baseUrl}/user/winnings`
46+
const url = `${WALLET_API_BASE_URL}/user/winnings`
4747
const response = await xhrPostAsync<string, ApiResponse<{
4848
winnings: WinningDetail[],
4949
pagination: PaginationInfo
@@ -64,7 +64,7 @@ export async function processWinningsPayments(winningsIds: string[]): Promise<{
6464
const body = JSON.stringify({
6565
winningsIds,
6666
})
67-
const url = `${baseUrl}/withdraw`
67+
const url = `${WALLET_API_BASE_URL}/withdraw`
6868
const response = await xhrPostAsync<string, ApiResponse<{ processed: boolean }>>(url, body)
6969

7070
if (response.status === 'error') {
@@ -81,7 +81,7 @@ export async function verifyOtp(transactionId: string, code: string, blob: boole
8181
transactionId,
8282
})
8383

84-
const url = `${baseUrl}/otp/verify`
84+
const url = `${WALLET_API_BASE_URL}/otp/verify`
8585
try {
8686
// eslint-disable-next-line max-len
8787
const response = await xhrPostAsyncWithBlobHandling<string, ApiResponse<OtpVerificationResponse> | Blob>(url, body, {
@@ -107,7 +107,7 @@ export async function resendOtp(transactionId: string): Promise<TransactionRespo
107107
transactionId,
108108
})
109109

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

@@ -132,7 +132,7 @@ export async function resendOtp(transactionId: string): Promise<TransactionRespo
132132
* @throws {Error} If the response does not contain a valid link.
133133
*/
134134
export async function getTrolleyPortalLink(): Promise<string> {
135-
const url = `${baseUrl}/trolley/portal-link`
135+
const url = `${WALLET_API_BASE_URL}/trolley/portal-link`
136136
const response = await xhrGetAsync<{ link: string }>(url)
137137

138138
if (!response.link) {

src/apps/wallet/src/lib/util/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './null-to-zero'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Converts a given value to the string `'0'` if it is `null`, `undefined`, or the string `'null'`.
3+
*
4+
* @param value - The input value which can be a string, `null`, or `undefined`.
5+
* @returns The original value if it is a valid string (and not `'null'`), otherwise returns `'0'`.
6+
*/
7+
export const nullToZero = (value: string | null | undefined): string => (value === 'null' ? '0' : value ?? '0')

0 commit comments

Comments
 (0)