Skip to content

Commit 041b602

Browse files
authored
feat(referral): add hardware records page with new UI components (#9595)
- Add getHardwareRecords API to fetch hardware order records - Create HardwareSalesRewardHeader component with stats cards - Add HardwareRecordCard for mobile view - Add HardwareRecordTable for desktop view - Add HardwareRecordTimeline and StatusBadge components - Remove old Sales tab, keep only Records view - Add type definitions for hardware records
1 parent 76e299d commit 041b602

File tree

12 files changed

+1038
-292
lines changed

12 files changed

+1038
-292
lines changed

packages/kit-bg/src/services/ServiceReferralCode.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
IEarnWalletHistory,
1313
IExportInviteDataParams,
1414
IHardwareCumulativeRewards,
15+
IHardwareRecordsResponse,
1516
IHardwareSalesRecord,
1617
IInviteCodeItem,
1718
IInviteCodeListResponse,
@@ -550,6 +551,24 @@ class ServiceReferralCode extends ServiceBase {
550551
return result;
551552
}
552553

554+
@backgroundMethod()
555+
async getHardwareRecords(cursor?: string): Promise<IHardwareRecordsResponse> {
556+
const client = await this.getOneKeyIdClient(EServiceEndpointEnum.Rebate);
557+
const params: {
558+
limit: number;
559+
cursor?: string;
560+
} = {
561+
limit: 10,
562+
};
563+
if (cursor) {
564+
params.cursor = cursor;
565+
}
566+
const response = await client.get<{
567+
data: IHardwareRecordsResponse;
568+
}>('/rebate/v1/invite/hardware-records', { params });
569+
return response.data.data;
570+
}
571+
553572
@backgroundMethod()
554573
async bindPerpsWallet({
555574
action,

packages/kit/src/views/ReferFriends/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export {
88
REFER_FRIENDS_PAGE_MAX_WIDTH,
99
} from './ReferFriendsPageContainer';
1010
export { ReferralBenefitsList } from './ReferralBenefitsList';
11+
export { SimpleTabs } from './SimpleTabs';
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { useCallback, useState } from 'react';
2+
3+
import { StyleSheet } from 'react-native';
4+
5+
import type { IColorTokens } from '@onekeyhq/components';
6+
import {
7+
Badge,
8+
Icon,
9+
SizableText,
10+
Stack,
11+
XStack,
12+
YStack,
13+
} from '@onekeyhq/components';
14+
import { Currency } from '@onekeyhq/kit/src/components/Currency';
15+
import type { IHardwareRecordItem } from '@onekeyhq/shared/src/referralCode/type';
16+
17+
import { HardwareRecordStatusBadge } from './HardwareRecordStatusBadge';
18+
import {
19+
HardwareRecordTimeline,
20+
formatTimestamp,
21+
} from './HardwareRecordTimeline';
22+
23+
type IHardwareRecordStatus =
24+
| 'Completed'
25+
| 'Pending'
26+
| 'Undistributed'
27+
| 'Refunded';
28+
29+
const statusToRewardColor: Record<IHardwareRecordStatus, IColorTokens> = {
30+
Completed: '$textSuccess',
31+
Undistributed: '$textInfo',
32+
Refunded: '$textSubdued',
33+
Pending: '$textCaution',
34+
};
35+
36+
interface IHardwareRecordCardProps {
37+
item: IHardwareRecordItem;
38+
}
39+
40+
export function HardwareRecordCard({ item }: IHardwareRecordCardProps) {
41+
const [isExpanded, setIsExpanded] = useState(false);
42+
43+
const handleToggle = useCallback(() => {
44+
setIsExpanded((prev) => !prev);
45+
}, []);
46+
47+
const formattedDate = formatTimestamp(item.orderPlacedAt);
48+
49+
const rewardColor =
50+
statusToRewardColor[item.status as IHardwareRecordStatus] || '$textSuccess';
51+
52+
return (
53+
<YStack
54+
bg="$bgApp"
55+
borderWidth={StyleSheet.hairlineWidth}
56+
borderColor="$borderSubdued"
57+
borderRadius="$3"
58+
overflow="hidden"
59+
>
60+
<YStack
61+
p="$4"
62+
gap="$3"
63+
cursor="pointer"
64+
hoverStyle={{ bg: '$bgHover' }}
65+
pressStyle={{ bg: '$bgActive' }}
66+
onPress={handleToggle}
67+
>
68+
{/* Header - Status Badge and Amount */}
69+
<XStack jc="space-between" ai="center">
70+
<HardwareRecordStatusBadge
71+
status={item.status}
72+
statusLabel={item.statusLabel}
73+
/>
74+
<Currency
75+
color={rewardColor}
76+
formatter="value"
77+
size="$bodyMdMedium"
78+
formatterOptions={{
79+
showPlusMinusSigns: true,
80+
}}
81+
>
82+
{item.rebateAmountFiatValue}
83+
</Currency>
84+
</XStack>
85+
86+
{/* Order Number */}
87+
<SizableText size="$bodyLgMedium" color="$text">
88+
{item.orderNumber}
89+
</SizableText>
90+
91+
{/* Date, Invite Code, and Expand Icon */}
92+
<XStack jc="space-between" ai="center">
93+
<XStack gap="$3" ai="center" flex={1}>
94+
{/* Date */}
95+
{formattedDate ? (
96+
<SizableText size="$bodyMd" color="$textSubdued">
97+
{formattedDate}
98+
</SizableText>
99+
) : null}
100+
101+
{/* Invite Code */}
102+
<Badge badgeType="default" badgeSize="sm">
103+
{item.inviteCode}
104+
</Badge>
105+
</XStack>
106+
<Stack animation="quick" rotate={isExpanded ? '-180deg' : '-90deg'}>
107+
<Icon
108+
name="ChevronDownSmallOutline"
109+
size="$5"
110+
color="$iconSubdued"
111+
/>
112+
</Stack>
113+
</XStack>
114+
</YStack>
115+
116+
{/* Expanded Content - Order History Timeline */}
117+
{isExpanded && item.history && item.history.length > 0 ? (
118+
<HardwareRecordTimeline history={item.history} />
119+
) : null}
120+
</YStack>
121+
);
122+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { IBadgeType } from '@onekeyhq/components';
2+
import { Badge, Stack } from '@onekeyhq/components';
3+
4+
type IHardwareRecordStatus =
5+
| 'Completed'
6+
| 'Pending'
7+
| 'Undistributed'
8+
| 'Refunded';
9+
10+
interface IHardwareRecordStatusBadgeProps {
11+
status: string;
12+
statusLabel?: string;
13+
}
14+
15+
const statusToBadgeType: Record<IHardwareRecordStatus, IBadgeType> = {
16+
Completed: 'success',
17+
Pending: 'warning',
18+
Undistributed: 'info',
19+
Refunded: 'default',
20+
};
21+
22+
const badgeTypeToIconColor: Record<IBadgeType, string> = {
23+
success: '$iconSuccess',
24+
warning: '$iconCaution',
25+
info: '$iconInfo',
26+
critical: '$iconCritical',
27+
default: '$iconSubdued',
28+
};
29+
30+
export function HardwareRecordStatusBadge({
31+
status,
32+
statusLabel,
33+
}: IHardwareRecordStatusBadgeProps) {
34+
const badgeType =
35+
statusToBadgeType[status as IHardwareRecordStatus] || 'default';
36+
const displayLabel = statusLabel || status;
37+
const iconColor = badgeTypeToIconColor[badgeType] || '$iconSubdued';
38+
39+
return (
40+
<Badge badgeType={badgeType} badgeSize="sm">
41+
<Stack w={6} h={6} borderRadius="$full" bg={iconColor} mr="$1.5" />
42+
<Badge.Text>{displayLabel}</Badge.Text>
43+
</Badge>
44+
);
45+
}

0 commit comments

Comments
 (0)