Skip to content

Commit 385e909

Browse files
committed
feat(wallet-admin): add payment provider listing
Signed-off-by: Rakib Ansary <rakibansary@topcoder.com>
1 parent b393f92 commit 385e909

File tree

14 files changed

+636
-115
lines changed

14 files changed

+636
-115
lines changed

src/apps/wallet-admin/src/home/tabs/WalletAdminTabs.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getHashFromTabId, getTabIdFromHash, WalletAdminTabsConfig, WalletAdminT
88
import { PaymentsTab } from './payments'
99
import { HomeTab } from './home'
1010
import { TaxFormsTab } from './tax-forms'
11+
import { PaymentMethodsTab } from './payment-methods'
1112
import styles from './WalletAdminTabs.module.scss'
1213

1314
interface WalletHomeProps {
@@ -47,6 +48,8 @@ const WalletAdminTabs: FC<WalletHomeProps> = (props: WalletHomeProps) => {
4748
{activeTab === WalletAdminTabViews.payments && <PaymentsTab profile={props.profile} />}
4849

4950
{activeTab === WalletAdminTabViews.taxforms && <TaxFormsTab profile={props.profile} />}
51+
52+
{activeTab === WalletAdminTabViews.withdrawalmethods && <PaymentMethodsTab profile={props.profile} />}
5053
</div>
5154
)
5255
}

src/apps/wallet-admin/src/home/tabs/config/wallet-tabs-config.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export enum WalletAdminTabViews {
44
home = '0',
55
payments = '1',
66
taxforms = '2',
7-
// withdrawalmethods = '3',
7+
withdrawalmethods = '3',
88
}
99

1010
export const WalletAdminTabsConfig: TabsNavItem[] = [
@@ -16,10 +16,10 @@ export const WalletAdminTabsConfig: TabsNavItem[] = [
1616
id: WalletAdminTabViews.payments,
1717
title: 'Payments',
1818
},
19-
// {
20-
// id: WalletAdminTabViews.withdrawalmethods,
21-
// title: 'Withdrawal Methods',
22-
// },
19+
{
20+
id: WalletAdminTabViews.withdrawalmethods,
21+
title: 'Payment Providers',
22+
},
2323
{
2424
id: WalletAdminTabViews.taxforms,
2525
title: 'Tax Forms',
@@ -34,8 +34,8 @@ export function getHashFromTabId(tabId: string): string {
3434
return '#payments'
3535
case WalletAdminTabViews.taxforms:
3636
return '#tax-forms'
37-
// case WalletAdminTabViews.withdrawalmethods:
38-
// return '#withdrawal-methods'
37+
case WalletAdminTabViews.withdrawalmethods:
38+
return '#payment-providers'
3939
default:
4040
return '#home'
4141
}
@@ -47,8 +47,8 @@ export function getTabIdFromHash(hash: string): string {
4747
return WalletAdminTabViews.payments
4848
case '#tax-forms':
4949
return WalletAdminTabViews.taxforms
50-
// case '#withdrawal-methods':
51-
// return WalletAdminTabViews.withdrawalmethods
50+
case '#payment-providers':
51+
return WalletAdminTabViews.withdrawalmethods
5252
default:
5353
return WalletAdminTabViews.home
5454
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.container {
4+
background-color: $black-5;
5+
padding: $sp-6;
6+
margin: $sp-8 0;
7+
border-radius: 6px;
8+
9+
@include ltelg {
10+
padding: $sp-4;
11+
}
12+
13+
.header {
14+
display: flex;
15+
justify-content: flex-start;
16+
gap: 5px;
17+
align-items: center;
18+
19+
@include ltelg {
20+
flex-direction: column;
21+
}
22+
}
23+
24+
.content {
25+
background-color: $tc-white;
26+
border-radius: 4px;
27+
margin-top: $sp-4;
28+
.centered {
29+
height: 200px;
30+
display: flex;
31+
justify-content: space-around;
32+
align-items: center;
33+
}
34+
}
35+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/* eslint-disable max-len */
2+
/* eslint-disable react/jsx-no-bind */
3+
import { toast } from 'react-toastify'
4+
import React, { FC, useCallback, useEffect } from 'react'
5+
6+
import { Collapsible, ConfirmModal, LoadingCircles } from '~/libs/ui'
7+
import { UserProfile } from '~/libs/core'
8+
9+
import { PaymentProvider } from '../../../lib/models/PaymentProvider'
10+
import { deletePaymentProvider, getMemberHandle, getPaymentMethods } from '../../../lib/services/wallet'
11+
import { FilterBar, PaymentMethodTable } from '../../../lib'
12+
import { PaginationInfo } from '../../../lib/models/PaginationInfo'
13+
14+
import styles from './PaymentMethodsTab.module.scss'
15+
16+
interface ListViewProps {
17+
// eslint-disable-next-line react/no-unused-prop-types
18+
profile: UserProfile
19+
}
20+
21+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
22+
const ListView: FC<ListViewProps> = (props: ListViewProps) => {
23+
const [confirmFlow, setConfirmFlow] = React.useState<{
24+
provider: PaymentProvider
25+
} | undefined>(undefined)
26+
const [isLoading, setIsLoading] = React.useState<boolean>(false)
27+
const [filters, setFilters] = React.useState<Record<string, string[]>>({})
28+
const [paymentMethods, setPaymentMethods] = React.useState<PaymentProvider[]>([])
29+
const [userIds, setUserIds] = React.useState<string[]>([])
30+
const [pagination, setPagination] = React.useState<PaginationInfo>({
31+
currentPage: 1,
32+
pageSize: 10,
33+
totalItems: 0,
34+
totalPages: 0,
35+
})
36+
37+
const fetchPaymentProviders = useCallback(async () => {
38+
if (isLoading) {
39+
return
40+
}
41+
42+
setIsLoading(true)
43+
try {
44+
45+
const paymentMethodsResponse = await getPaymentMethods(pagination.pageSize, (pagination.currentPage - 1) * pagination.pageSize, userIds)
46+
const tmpUserIds = paymentMethodsResponse.paymentMethods.map(provider => provider.userId)
47+
const handleMap = await getMemberHandle(tmpUserIds)
48+
49+
const userPaymentMethods = paymentMethodsResponse.paymentMethods.map((provider: PaymentProvider) => ({ ...provider, handle: handleMap.get(parseInt(provider.userId, 10)) ?? provider.userId }))
50+
51+
setPaymentMethods(userPaymentMethods)
52+
setPagination(paymentMethodsResponse.pagination)
53+
} catch (apiError) {
54+
console.error('Failed to fetch winnings:', apiError)
55+
} finally {
56+
setIsLoading(false)
57+
}
58+
// eslint-disable-next-line react-hooks/exhaustive-deps
59+
}, [pagination.pageSize, pagination.currentPage, userIds])
60+
61+
useEffect(() => {
62+
fetchPaymentProviders()
63+
}, [fetchPaymentProviders])
64+
65+
return (
66+
<>
67+
<div className={styles.container}>
68+
<div className={styles.header}>
69+
<h3>Member Payment Providers</h3>
70+
</div>
71+
<div className={styles.content}>
72+
<Collapsible header={<h3>Member Payment Providers Listing</h3>}>
73+
<FilterBar
74+
filters={[
75+
{
76+
key: 'userIds',
77+
label: 'Username/Handle',
78+
type: 'member_autocomplete',
79+
},
80+
{
81+
key: 'pageSize',
82+
label: 'Members per page',
83+
options: [
84+
{
85+
label: '10',
86+
value: '10',
87+
},
88+
{
89+
label: '50',
90+
value: '50',
91+
},
92+
{
93+
label: '100',
94+
value: '100',
95+
},
96+
],
97+
type: 'dropdown',
98+
},
99+
]}
100+
onFilterChange={(key: string, value: string[]) => {
101+
const newPagination = {
102+
...pagination,
103+
currentPage: 1,
104+
}
105+
if (key === 'pageSize') {
106+
newPagination.pageSize = parseInt(value[0], 10)
107+
}
108+
109+
if (key === 'userIds') {
110+
setUserIds(value)
111+
}
112+
113+
setPagination(newPagination)
114+
setFilters({
115+
...filters,
116+
[key]: value,
117+
})
118+
}}
119+
onResetFilters={() => {
120+
setPagination({
121+
...pagination,
122+
currentPage: 1,
123+
pageSize: 10,
124+
})
125+
setFilters({})
126+
}}
127+
/>
128+
{isLoading && <LoadingCircles className={styles.centered} />}
129+
{!isLoading && paymentMethods.length > 0 && (
130+
<PaymentMethodTable
131+
paymentMethods={paymentMethods}
132+
numPages={pagination.totalPages}
133+
currentPage={pagination.currentPage}
134+
onPreviousPageClick={() => {
135+
setPagination({
136+
...pagination,
137+
currentPage: pagination.currentPage - 1,
138+
})
139+
}}
140+
onNextPageClick={() => {
141+
setPagination({
142+
...pagination,
143+
currentPage: pagination.currentPage + 1,
144+
})
145+
}}
146+
onPageClick={(pageNumber: number) => {
147+
setPagination({
148+
...pagination,
149+
currentPage: pageNumber,
150+
})
151+
}}
152+
onDeleteClick={async (provider: PaymentProvider) => {
153+
setConfirmFlow({ provider })
154+
}}
155+
/>
156+
)}
157+
{!isLoading && paymentMethods.length === 0 && (
158+
<div className={styles.centered}>
159+
<p className='body-main'>
160+
{Object.keys(filters).length === 0
161+
? 'Member payment-providers will appear here.'
162+
: 'No payment-provider found for the selected member(s).'}
163+
</p>
164+
</div>
165+
)}
166+
</Collapsible>
167+
</div>
168+
</div>
169+
{confirmFlow && (
170+
<ConfirmModal
171+
title='Delete Confirmation'
172+
action='delete'
173+
onClose={() => {
174+
setConfirmFlow(undefined)
175+
}}
176+
onConfirm={async () => {
177+
const userId = confirmFlow.provider.userId
178+
const providerId = confirmFlow.provider.id!
179+
setConfirmFlow(undefined)
180+
181+
toast.success('Deleting payment provider. Please wait...', { position: 'bottom-right' })
182+
try {
183+
await deletePaymentProvider(userId, providerId)
184+
toast.success('Successfully deleted payment provider.', { position: 'bottom-right' })
185+
} catch (err) {
186+
toast.error('Failed to delete users payment provider. Please try again later', { position: 'bottom-right' })
187+
}
188+
189+
fetchPaymentProviders()
190+
}}
191+
open={confirmFlow !== undefined}
192+
>
193+
<div>
194+
<p>
195+
Are you sure you want to reset the payment provider of the member
196+
{' '}
197+
{confirmFlow.provider.handle}
198+
?
199+
</p>
200+
<br />
201+
<p>This action cannot be undone.</p>
202+
</div>
203+
</ConfirmModal>
204+
)}
205+
</>
206+
)
207+
}
208+
209+
export default ListView
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as PaymentMethodsTab } from './PaymentMethodsTab'

src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable max-len */
22
/* eslint-disable react/jsx-no-bind */
33
import { toast } from 'react-toastify'
4+
import { AxiosError } from 'axios'
45
import React, { FC, useCallback, useEffect } from 'react'
56

67
import { Collapsible, ConfirmModal, LoadingCircles } from '~/libs/ui'
@@ -75,6 +76,7 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
7576
paymentStatus?: string;
7677
auditNote?: string;
7778
}>({})
79+
const [apiErrorMsg, setApiErrorMsg] = React.useState<string>('Member earnings will appear here.')
7880

7981
const editStateRef = React.useRef(editState)
8082

@@ -156,7 +158,11 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
156158
setWinnings(winningsData)
157159
setPagination(payments.pagination)
158160
} catch (apiError) {
159-
console.error('Failed to fetch winnings:', apiError)
161+
if (apiError instanceof AxiosError && apiError?.response?.status === 403) {
162+
setApiErrorMsg(apiError.response.data.message)
163+
} else {
164+
setApiErrorMsg('Failed to fetch winnings. Please try again later.')
165+
}
160166
} finally {
161167
setIsLoading(false)
162168
}
@@ -436,7 +442,7 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
436442
<div className={styles.centered}>
437443
<p className='body-main'>
438444
{Object.keys(filters).length === 0
439-
? 'Member earnings will appear here.'
445+
? apiErrorMsg
440446
: 'No payments match your filters.'}
441447
</p>
442448
</div>

src/apps/wallet-admin/src/home/tabs/tax-forms/TaxFormsTab.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable max-len */
22
/* eslint-disable react/jsx-no-bind */
33
import { toast } from 'react-toastify'
4+
import { AxiosError } from 'axios'
45
import React, { FC, useCallback, useEffect } from 'react'
56

67
import { Collapsible, ConfirmModal, LoadingCircles } from '~/libs/ui'
@@ -34,6 +35,7 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
3435
totalItems: 0,
3536
totalPages: 0,
3637
})
38+
const [apiErrorMsg, setApiErrorMsg] = React.useState<string>('Member earnings will appear here.')
3739

3840
const fetchTaxForms = useCallback(async () => {
3941
if (isLoading) {
@@ -52,7 +54,11 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
5254
setForms(taxForms)
5355
setPagination(taxFormsResponse.pagination)
5456
} catch (apiError) {
55-
console.error('Failed to fetch winnings:', apiError)
57+
if (apiError instanceof AxiosError && apiError?.response?.status === 403) {
58+
setApiErrorMsg(apiError.response.data.message)
59+
} else {
60+
setApiErrorMsg('Failed to fetch winnings. Please try again later.')
61+
}
5662
} finally {
5763
setIsLoading(false)
5864
}
@@ -80,7 +86,7 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
8086
},
8187
{
8288
key: 'pageSize',
83-
label: 'Tax Forms per page',
89+
label: 'Members per page',
8490
options: [
8591
{
8692
label: '10',
@@ -171,8 +177,8 @@ const ListView: FC<ListViewProps> = (props: ListViewProps) => {
171177
<div className={styles.centered}>
172178
<p className='body-main'>
173179
{Object.keys(filters).length === 0
174-
? 'Member tax-forms will appear here.'
175-
: 'No tax-forms match your filters.'}
180+
? apiErrorMsg
181+
: 'No tax-forms found for the selected member(s).'}
176182
</p>
177183
</div>
178184
)}

0 commit comments

Comments
 (0)