Skip to content

Pm 959 tc finance integration #1075

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 2 commits into from
May 15, 2025
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'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import statement for WalletDetails has been removed, but it is unclear if this is intentional or if WalletDetails is still needed elsewhere in the code. Please verify if WalletDetails is used in other parts of the file or project.

import { InfoRow } from '../../../lib'
import { BannerImage, BannerText } from '../../../lib/assets/home'
import { nullToZero } from '../../../lib/util'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider checking if nullToZero is used in the file. If not, ensure that its import is necessary or remove it to keep the code clean.

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()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider handling the error state more explicitly. Currently, the error variable is being destructured from useWalletDetails(), but there is no logic to handle or display this error within the component. Implementing error handling will improve the user experience by providing feedback when the wallet details cannot be fetched.

const [balanceSum, setBalanceSum] = useState(0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The balanceSum state is initialized but not used in the provided code snippet. Ensure that this state is necessary, and if it is, implement logic to update and utilize it. If not, consider removing it to clean up the code.

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 && (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a null check for walletDetails to ensure it is not null or undefined before rendering the component. This will prevent potential runtime errors if walletDetails is not properly initialized.

<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 && (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a check for walletDetails before accessing its properties to prevent potential runtime errors if walletDetails is undefined.

<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)}%`}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment // eslint-disable-next-line max-len suggests that the line exceeds the maximum length limit. Consider refactoring the line to adhere to the length limit for better readability and maintainability.

action={
<LinkButton
label='ADJUST YOUR PAYOUT SETTINGS'
iconToRight
icon={IconOutline.ArrowRightIcon}
size='md'
link
to='#payout'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The to prop value '#payout' seems to be a hash link. Ensure that the corresponding element with the id payout exists in the DOM to avoid broken links.

/>
}
/>
)}

{!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 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive class name instead of .mt to improve readability and maintainability of the code.

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'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Link import from react-router-dom is added but not used in the code. Consider removing it if it's not needed to avoid unnecessary imports.

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'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function nullToZero is imported but not used in this file. Consider removing the import if it's not needed.

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(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming handlePayMeClick to a more descriptive name like handlePaymentConfirmationClick to better convey the function's purpose.

paymentIds: { [paymentId: string]: Winning },
totalAmount: string,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The totalAmount parameter is typed as a string. Consider using a numeric type if this value is intended to represent a monetary amount to avoid potential issues with numeric operations.

): 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>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Link component's to prop is set to '#payout'. Ensure that this hash link correctly corresponds to an existing element ID on the page to avoid broken links.

</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}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function handlePayMeClick is used here, but its definition and implementation are not visible in the diff. Ensure that handlePayMeClick correctly replicates the functionality of the previous inline function, including setting the confirmFlow state and calling processPayouts with the correct parameters.

/>
)}
{!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>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider specifying a more precise type for KeyedMutator<any>. Using any can lead to potential type safety issues. If possible, replace any with a more specific type that matches the expected data structure.

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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more specific type for primaryCurrency if possible, such as an enum or a union of string literals representing valid currency codes, to ensure type safety.

estimatedFees?: string | null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The estimatedFees field is currently typed as string | null. If this field represents a numeric value, consider using a numeric type instead to prevent potential issues with string manipulation.

taxWithholdingPercentage?: string | null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The taxWithholdingPercentage field is typed as string | null. If this field is meant to represent a percentage value, consider using a numeric type to facilitate calculations and prevent errors related to string handling.

}
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`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider verifying that WALLET_API_BASE_URL is defined and has the correct value before using it to construct the URL. This will help prevent potential runtime errors if the environment variable is not set or is incorrect.

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`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from baseUrl to WALLET_API_BASE_URL should be verified to ensure that WALLET_API_BASE_URL is correctly defined and imported in the module. This change could potentially affect the endpoint being called.

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')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using strict equality checks for null and undefined to ensure type safety. The current implementation uses the nullish coalescing operator (??) which is appropriate, but strict checks can be more explicit and clear.