Skip to content

Commit d446db7

Browse files
authored
Merge pull request #1049 from topcoder-platform/PM-1098_payout-tab
PM-1098 payout tab
2 parents 5d4aace + 1d6a775 commit d446db7

File tree

10 files changed

+138
-32
lines changed

10 files changed

+138
-32
lines changed

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { PageTitle, TabsNavbar, TabsNavItem } from '~/libs/ui'
77
import { getHashFromTabId, getTabIdFromHash, WalletTabsConfig, WalletTabViews } from './config'
88
import { WinningsTab } from './winnings'
99
import { HomeTab } from './home'
10+
import { PayoutTab } from './payout'
1011
import styles from './WalletTabs.module.scss'
1112

1213
interface WalletHomeProps {
@@ -16,15 +17,16 @@ interface WalletHomeProps {
1617
const WalletTabs: FC<WalletHomeProps> = (props: WalletHomeProps) => {
1718
const { hash }: { hash: string } = useLocation()
1819

19-
const activeTabHash: string = useMemo<string>(() => getTabIdFromHash(hash), [hash])
20+
const activeTabHash: WalletTabViews = useMemo<WalletTabViews>(() => getTabIdFromHash(hash), [hash])
2021

21-
const [activeTab, setActiveTab]: [string, Dispatch<SetStateAction<string>>] = useState<string>(activeTabHash)
22+
const [activeTab, setActiveTab]: [WalletTabViews, Dispatch<SetStateAction<WalletTabViews>>]
23+
= useState<WalletTabViews>(activeTabHash)
2224

2325
useEffect(() => {
2426
setActiveTab(activeTabHash)
2527
}, [activeTabHash])
2628

27-
function handleTabChange(tabId: string): void {
29+
function handleTabChange(tabId: WalletTabViews): void {
2830
setActiveTab(tabId)
2931
window.location.hash = getHashFromTabId(tabId)
3032
}
@@ -34,14 +36,18 @@ const WalletTabs: FC<WalletHomeProps> = (props: WalletHomeProps) => {
3436
<TabsNavbar defaultActive={activeTab} onChange={handleTabChange} tabs={WalletTabsConfig} />
3537

3638
<PageTitle>
37-
{[WalletTabsConfig.find((tab: TabsNavItem) => tab.id === activeTab)?.title, 'Wallet', 'Topcoder'].join(
38-
' | ',
39-
)}
39+
{[
40+
WalletTabsConfig.find((tab: TabsNavItem<WalletTabViews>) => tab.id === activeTab)?.title,
41+
'Wallet',
42+
'Topcoder',
43+
].join(' | ')}
4044
</PageTitle>
4145

4246
{activeTab === WalletTabViews.winnings && <WinningsTab profile={props.profile} />}
4347

4448
{activeTab === WalletTabViews.home && <HomeTab profile={props.profile} />}
49+
50+
{activeTab === WalletTabViews.payout && <PayoutTab profile={props.profile} />}
4551
</div>
4652
)
4753
}

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { TabsNavItem } from '~/libs/ui'
22

33
export enum WalletTabViews {
4-
home = '0',
5-
winnings = '1',
4+
home,
5+
winnings,
6+
payout,
67
}
78

8-
export const WalletTabsConfig: TabsNavItem[] = [
9+
export const WalletTabsConfig: TabsNavItem<WalletTabViews>[] = [
910
{
1011
id: WalletTabViews.home,
1112
title: 'Wallet',
@@ -14,23 +15,29 @@ export const WalletTabsConfig: TabsNavItem[] = [
1415
id: WalletTabViews.winnings,
1516
title: 'Winnings',
1617
},
18+
{
19+
id: WalletTabViews.payout,
20+
title: 'Payout',
21+
},
1722
]
1823

19-
export function getHashFromTabId(tabId: string): string {
24+
export function getHashFromTabId(tabId: WalletTabViews): string {
2025
switch (tabId) {
21-
case WalletTabViews.home:
22-
return '#home'
2326
case WalletTabViews.winnings:
2427
return '#winnings'
28+
case WalletTabViews.payout:
29+
return '#payout'
2530
default:
2631
return '#home'
2732
}
2833
}
2934

30-
export function getTabIdFromHash(hash: string): string {
35+
export function getTabIdFromHash(hash: string): WalletTabViews {
3136
switch (hash) {
3237
case '#winnings':
3338
return WalletTabViews.winnings
39+
case '#payout':
40+
return WalletTabViews.payout
3441
default:
3542
return WalletTabViews.home
3643
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.iframe {
4+
width: 100%;
5+
height: 100%;
6+
border: none;
7+
height: 90vh;
8+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { FC, MutableRefObject, useEffect, useRef } from 'react'
2+
3+
import { UserProfile } from '~/libs/core'
4+
import { EnvironmentConfig } from '~/config'
5+
6+
import { getTrolleyPortalLink } from '../../../lib/services/wallet'
7+
8+
import styles from './PayoutTab.module.scss'
9+
10+
interface PayoutTabProps {
11+
profile: UserProfile
12+
}
13+
14+
const PayoutTab: FC<PayoutTabProps> = props => {
15+
const loading = useRef<number>()
16+
const frameRef: MutableRefObject<HTMLElement | any> = useRef()
17+
18+
useEffect(() => {
19+
if (!props.profile.userId || props.profile.userId === loading.current) {
20+
return
21+
}
22+
23+
loading.current = props.profile.userId
24+
getTrolleyPortalLink()
25+
.then((link: string) => {
26+
frameRef.current.src = link
27+
})
28+
}, [props.profile.userId])
29+
30+
useEffect(() => {
31+
if (!frameRef.current) {
32+
return undefined
33+
}
34+
35+
const handleEvent: (event: any) => void = (event: any) => {
36+
const { data: widgetEvent, origin }: { data: { event: string, data: number }, origin: string } = event
37+
38+
if (origin.indexOf(EnvironmentConfig.TROLLEY_WIDGET_ORIGIN) === -1) {
39+
return
40+
}
41+
42+
// resize iframe based on the reported content height
43+
if (widgetEvent.event === 'document.height') {
44+
Object.assign(frameRef.current.style, { height: `${widgetEvent.data}px` })
45+
}
46+
}
47+
48+
window.addEventListener('message', handleEvent, false)
49+
return (): void => {
50+
window.removeEventListener('message', handleEvent, false)
51+
}
52+
}, [frameRef.current?.src])
53+
54+
return (
55+
<div className={styles.wrap}>
56+
<iframe
57+
className={styles.iframe}
58+
ref={frameRef}
59+
title='Trolley'
60+
/>
61+
</div>
62+
)
63+
}
64+
65+
export default PayoutTab
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as PayoutTab } from './PayoutTab'

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,20 @@ export async function resendOtp(transactionId: string): Promise<TransactionRespo
124124
throw new Error('Failed to resend OTP.')
125125
}
126126
}
127+
128+
/**
129+
* Fetches the Trolley portal link from the server.
130+
*
131+
* @returns {Promise<string>} A promise that resolves to the Trolley portal link.
132+
* @throws {Error} If the response does not contain a valid link.
133+
*/
134+
export async function getTrolleyPortalLink(): Promise<string> {
135+
const url = `${baseUrl}/trolley/portal-link`
136+
const response = await xhrGetAsync<{ link: string }>(url)
137+
138+
if (!response.link) {
139+
throw new Error('Error fetching Trolley portal link')
140+
}
141+
142+
return response.link
143+
}

src/config/environments/default.env.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,5 @@ export const USERFLOW_SURVEYS = {
7575
PROFILES: getReactEnv<string>('USERFLOW_SURVEY_PROFILES', '5cfae36f-0700-41c4-8938-0add4037acb2'),
7676
TALENTSEARCH: getReactEnv<string>('USERFLOW_SURVEY_TALENTSEARCH', 'd1030c93-dd36-4ae0-b5d0-95004b8e9d32'),
7777
}
78+
79+
export const TROLLEY_WIDGET_ORIGIN = getReactEnv<string>('TROLLEY_WIDGET_ORIGIN', 'https://widget.trolley.com')

src/config/environments/global-config.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ export interface GlobalConfig {
4343
PROFILES: string
4444
TALENTSEARCH: string
4545
}
46+
TROLLEY_WIDGET_ORIGIN: string
4647
}

src/libs/ui/lib/components/tabs-navbar/TabsNavbar.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
Dispatch,
3-
FC,
43
MutableRefObject,
54
ReactNode,
65
SetStateAction,
@@ -19,28 +18,28 @@ import { ActiveTabTipIcon, IconOutline } from '../svgs'
1918
import { TabsNavItem } from './tabs-nav-item.model'
2019
import styles from './TabsNavbar.module.scss'
2120

22-
export interface TabsNavbarProps {
23-
defaultActive: string
24-
onChange: (active: string) => void
25-
tabs: ReadonlyArray<TabsNavItem>
21+
export interface TabsNavbarProps<T> {
22+
defaultActive: T
23+
onChange: (active: T) => void
24+
tabs: ReadonlyArray<TabsNavItem<T>>
2625
}
2726

28-
const TabsNavbar: FC<TabsNavbarProps> = (props: TabsNavbarProps) => {
27+
const TabsNavbar = <T, >(props: TabsNavbarProps<T>): JSX.Element => {
2928
const query: URLSearchParams = new URLSearchParams(window.location.search)
30-
const initialTab: MutableRefObject<string | null> = useRef<string|null>(query.get('tab'))
29+
const initialTab: MutableRefObject<T | undefined> = useRef<T|undefined>(query.get('tab') as T)
3130

32-
const [tabOpened, setTabOpened]: [string | undefined, Dispatch<SetStateAction<string | undefined>>]
33-
= useState<string | undefined>(props.defaultActive)
31+
const [tabOpened, setTabOpened]: [T | undefined, Dispatch<SetStateAction<T | undefined>>]
32+
= useState<T | undefined>(props.defaultActive)
3433
const tabRefs: MutableRefObject<Array<HTMLElement>> = useRef([] as Array<HTMLElement>)
3534
const [offset, setOffset]: [number, Dispatch<SetStateAction<number>>] = useState<number>(0)
3635
const [menuIsVisible, setMenuIsVisible]: [boolean, Dispatch<SetStateAction<boolean>>] = useState(false)
3736
const triggerRef: MutableRefObject<any> = useRef(undefined)
3837

39-
const activeTab: TabsNavItem = useMemo(() => (
40-
props.tabs.find(tab => tab.id === tabOpened) as TabsNavItem
38+
const activeTab: TabsNavItem<T> = useMemo(() => (
39+
props.tabs.find(tab => tab.id === tabOpened) as TabsNavItem<T>
4140
), [tabOpened, props.tabs])
4241

43-
const updateOffset: (tabId: string) => void = useCallback((tabId: string) => {
42+
const updateOffset: (tabId: T) => void = useCallback((tabId: T) => {
4443

4544
const index: number = props.tabs.findIndex(tab => tab.id === tabId)
4645
if (index === -1) {
@@ -54,7 +53,7 @@ const TabsNavbar: FC<TabsNavbarProps> = (props: TabsNavbarProps) => {
5453
props.tabs,
5554
])
5655

57-
const handleActivateTab: (tabId: string) => () => void = useCallback((tabId: string) => () => {
56+
const handleActivateTab: (tabId: T) => () => void = useCallback((tabId: T) => () => {
5857
setTabOpened(tabId)
5958
props.onChange.call(undefined, tabId)
6059
updateOffset(tabId)
@@ -74,7 +73,7 @@ const TabsNavbar: FC<TabsNavbarProps> = (props: TabsNavbarProps) => {
7473
&& props.tabs.find(tab => tab.id === initialTab.current)
7574
) {
7675
handleActivateTab(initialTab.current)()
77-
initialTab.current = ''
76+
initialTab.current = undefined
7877
} else if (props.defaultActive) {
7978
setTabOpened(props.defaultActive)
8079
updateOffset(props.defaultActive)
@@ -87,7 +86,7 @@ const TabsNavbar: FC<TabsNavbarProps> = (props: TabsNavbarProps) => {
8786
])
8887

8988
const renderTabItem: (
90-
tab: TabsNavItem,
89+
tab: TabsNavItem<T>,
9190
activeTabId?: string,
9291
ref?: (el: HTMLElement | null) => void
9392
) => ReactNode = (
@@ -122,7 +121,7 @@ const TabsNavbar: FC<TabsNavbarProps> = (props: TabsNavbarProps) => {
122121
<div
123122
ref={ref}
124123
className={classNames(styles['tab-item'], activeTabId === tab.id && 'active')}
125-
key={tab.id}
124+
key={tab.id as string}
126125
onClick={handleActivateTab(tab.id)}
127126
>
128127
{tabContent}
@@ -151,7 +150,7 @@ const TabsNavbar: FC<TabsNavbarProps> = (props: TabsNavbarProps) => {
151150

152151
<div className={classNames(styles['menu-wrapper'])}>
153152
{props.tabs.map((tab, i) => (
154-
renderTabItem(tab, tabOpened, el => { tabRefs.current[i] = el as HTMLElement })
153+
renderTabItem(tab, tabOpened as string, el => { tabRefs.current[i] = el as HTMLElement })
155154
))}
156155
</div>
157156
<div
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { TabsNavItemBadge } from './tab-nav-item-badge.model'
22

3-
export interface TabsNavItem {
3+
export interface TabsNavItem<T = string> {
44
badges?: Array<TabsNavItemBadge>
5-
id: string
5+
id: T
66
title: string
77
url?: string
88
}

0 commit comments

Comments
 (0)