Skip to content

Commit 022bd96

Browse files
authored
update wallet_checkout interface to new design (#860)
* updated the wallet_checkout schema and types * updated findFeasibleContractPayments to consider solana payments also * chore:remove un-used methods from WalletCheckoutUtils.ts * chore: asset namespace change from spl to token * chore: display solana checkout request * chore: fix chainId for solana * add solana chain support for direct and contract interaction payment option * chore: run prettier * chore:redesign checkout screen * chore: added icon wrapper css * chore: use declared const value * chore: refactored prepareFeasiblePayments * chore: refactor paymentHandler * chore: remove unused method * chore: run prettier * chore: removed comment and fix formatting * chore: add estimated gas fee * chore: run prettier * chore:some UI Improvements
1 parent 146e95c commit 022bd96

32 files changed

+2526
-960
lines changed

advanced/wallets/react-wallet-v2/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@reown/appkit-experimental": "1.6.8",
3333
"@reown/walletkit": "1.2.3",
3434
"@rhinestone/module-sdk": "0.1.25",
35+
"@solana/spl-token": "^0.4.13",
3536
"@solana/web3.js": "1.89.2",
3637
"@taquito/signer": "^15.1.0",
3738
"@taquito/taquito": "^15.1.0",
Loading

advanced/wallets/react-wallet-v2/public/chain-logos/chain-placeholder.svg

Lines changed: 0 additions & 14 deletions
This file was deleted.
Loading
Loading

advanced/wallets/react-wallet-v2/public/token-logos/token-placeholder.svg

Lines changed: 0 additions & 9 deletions
This file was deleted.

advanced/wallets/react-wallet-v2/src/components/OrderInfoCard.tsx

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
1-
import { Col, Row, Text } from '@nextui-org/react'
1+
import { DetailedPaymentOption } from '@/types/wallet_checkout'
2+
import { Avatar, Col, Link, Row, styled, StyledLink, Text } from '@nextui-org/react'
23
import { useEffect, useState } from 'react'
4+
import { CoreTypes } from '@walletconnect/types'
5+
import OrderDetailIcon from './PaymentCheckout/visual/OrderDetailIcon'
6+
import WalletCheckoutUtil from '@/utils/WalletCheckoutUtil'
7+
import { formatUnits } from 'viem'
8+
import { useSnapshot } from 'valtio'
9+
import SettingsStore from '@/store/SettingsStore'
10+
import BadgeCheckIcon from './PaymentCheckout/visual/BadgeCheckIcon'
11+
import BadgeAlertIcon from './PaymentCheckout/visual/BadgeAlertIcon'
312

413
/**
514
* Types
615
*/
716
interface IProps {
17+
selectedPayment: DetailedPaymentOption | null
818
orderId: string
19+
metadata: CoreTypes.Metadata
920
expiry?: number
1021
}
1122

@@ -33,12 +44,30 @@ const calculateTimeRemaining = (expiryTimestamp?: number) => {
3344
return { hours, minutes, seconds, isExpired: false }
3445
}
3546

36-
/**
37-
* Component
38-
*/
39-
export default function OrderInfoCard({ orderId, expiry }: IProps) {
47+
export default function OrderInfoCard({ orderId, expiry, selectedPayment, metadata }: IProps) {
48+
const styles = {
49+
iconWrapper: {
50+
width: '40px',
51+
height: '40px',
52+
display: 'flex',
53+
justifyContent: 'center',
54+
alignItems: 'center'
55+
},
56+
circleIconBg: {
57+
display: 'flex',
58+
backgroundColor: '#333',
59+
height: '32px',
60+
width: '32px',
61+
borderRadius: '50%',
62+
alignItems: 'center',
63+
justifyContent: 'center'
64+
}
65+
}
4066
const [timeRemaining, setTimeRemaining] = useState(calculateTimeRemaining(expiry))
41-
67+
const { currentRequestVerifyContext } = useSnapshot(SettingsStore.state)
68+
const validation = currentRequestVerifyContext?.verified.validation
69+
const { icons, name, url } = metadata
70+
const isVerified = validation == 'VALID'
4271
// Update countdown every second
4372
useEffect(() => {
4473
if (!expiry) return
@@ -89,15 +118,50 @@ export default function OrderInfoCard({ orderId, expiry }: IProps) {
89118
}
90119

91120
return (
92-
<Row>
93-
<Col>
94-
<Text h5>Order Details</Text>
95-
<Text color="$gray400" size={'small'}>
96-
Order ID: {orderId}
121+
<Col css={{ display: 'flex', flexDirection: 'column', gap: '12px', marginTop: '12px' }}>
122+
<div style={styles.iconWrapper}>
123+
<div style={styles.circleIconBg}>
124+
<OrderDetailIcon />
125+
</div>
126+
</div>
127+
<Col css={{ display: 'flex', flexDirection: 'column', gap: '12px', paddingLeft: '4px' }}>
128+
<Row align="center" justify="space-between">
129+
<Text color="$gray400">Merchant</Text>
130+
<Text css={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
131+
{isVerified ? (
132+
<BadgeCheckIcon color="green" size={16} />
133+
) : (
134+
<BadgeAlertIcon color="#F5A623" size={16} />
135+
)}
136+
{name || 'Unknown'}
97137
</Text>
138+
</Row>
139+
140+
{selectedPayment && selectedPayment.recipient && (
141+
<Row align="center" justify="space-between">
142+
<Text color="$gray400">Address</Text>
143+
<Text>{WalletCheckoutUtil.formatRecipient(selectedPayment.recipient)}</Text>
144+
</Row>
145+
)}
146+
{selectedPayment && selectedPayment.fee && (
147+
<Row align="center" justify="space-between">
148+
<Text color="$gray400">Estimated Gas Fee</Text>
149+
<Text>
150+
~
151+
{Number(
152+
formatUnits(BigInt(selectedPayment.fee.gasFee), selectedPayment.fee.decimals)
153+
).toFixed(6)}{' '}
154+
{selectedPayment.fee.feeSymbol}
155+
</Text>
156+
</Row>
157+
)}
158+
<Row align="center" justify="space-between">
159+
<Text color="$gray400">Order ID</Text>
160+
<Text size={'small'}>{orderId}</Text>
161+
</Row>
98162
{timeRemaining ? (
99-
<Row align="center" css={{ gap: '8px' }}>
100-
<Text color={timeRemaining.isExpired ? '$error' : '$gray400'} size={'small'}>
163+
<Row align="center" justify="space-between">
164+
<Text color={timeRemaining.isExpired ? '$error' : '$gray400'}>
101165
{timeRemaining.isExpired ? 'Expired' : 'Expires in: '}
102166
</Text>
103167
{!timeRemaining.isExpired && (
@@ -119,6 +183,6 @@ export default function OrderInfoCard({ orderId, expiry }: IProps) {
119183
</Text>
120184
)}
121185
</Col>
122-
</Row>
186+
</Col>
123187
)
124188
}
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import { useEffect, useRef, useState } from 'react'
2+
import { Text, Card } from '@nextui-org/react'
3+
4+
// Simple hook to detect clicks outside an element
5+
const useClickAway = (onClickAway: () => void) => {
6+
const ref = useRef<HTMLDivElement>(null)
7+
8+
useEffect(() => {
9+
const handleClickOutside = (event: MouseEvent) => {
10+
if (ref.current && !ref.current.contains(event.target as Node)) {
11+
onClickAway()
12+
}
13+
}
14+
15+
document.addEventListener('mousedown', handleClickOutside)
16+
return () => {
17+
document.removeEventListener('mousedown', handleClickOutside)
18+
}
19+
}, [onClickAway])
20+
21+
return ref
22+
}
23+
// Style variables
24+
const styles = {
25+
dropdownContainer: {
26+
position: 'relative' as const
27+
},
28+
dropdownButton: {
29+
padding: '8px 16px',
30+
background: '$accents0',
31+
cursor: 'pointer'
32+
},
33+
cardBody: {
34+
overflow: 'visible' as const,
35+
display: 'flex' as const,
36+
justifyContent: 'space-between' as const,
37+
alignItems: 'center' as const,
38+
padding: '4px 0'
39+
},
40+
contentWrapper: {
41+
display: 'flex' as const,
42+
alignItems: 'center' as const,
43+
width: '100%'
44+
},
45+
chevronContainer: {
46+
marginLeft: 'auto'
47+
},
48+
dropdownMenu: {
49+
position: 'absolute' as const,
50+
top: 'calc(100% + 8px)',
51+
left: 0,
52+
right: 0,
53+
zIndex: 1000,
54+
padding: '4px 8px',
55+
boxShadow: '0 4px 14px 0 rgba(0, 0, 0, 0.3)',
56+
maxHeight: '300px',
57+
overflow: 'auto' as const,
58+
backgroundColor: '#2a2a2a'
59+
},
60+
checkmark: {
61+
position: 'absolute' as const,
62+
right: '10px',
63+
top: '50%',
64+
transform: 'translateY(-50%)',
65+
width: '20px',
66+
height: '20px',
67+
borderRadius: '50%',
68+
backgroundColor: '#17c964',
69+
display: 'flex' as const,
70+
alignItems: 'center' as const,
71+
justifyContent: 'center' as const,
72+
color: 'white',
73+
boxShadow: '0 2px 8px rgba(23, 201, 100, 0.5)'
74+
}
75+
}
76+
77+
// Generic dropdown component
78+
export default function GenericDropdown({
79+
items,
80+
selectedIndex,
81+
setSelectedIndex,
82+
renderItem,
83+
placeholder = 'Select an option',
84+
emptyMessage = 'No options available'
85+
}: {
86+
items: any[]
87+
selectedIndex: number
88+
setSelectedIndex: (index: number) => void
89+
renderItem: (item: any) => React.ReactNode
90+
placeholder?: string
91+
emptyMessage?: string
92+
}) {
93+
const [isOpen, setIsOpen] = useState(false)
94+
95+
// Reference to detect clicks outside dropdown
96+
const dropdownRef = useClickAway(() => {
97+
setIsOpen(false)
98+
})
99+
100+
const handleSelect = (index: number) => {
101+
setSelectedIndex(index)
102+
setIsOpen(false)
103+
}
104+
105+
// Get style for dropdown item
106+
const getDropdownItemStyle = (isSelected: boolean, isLast: boolean) => ({
107+
cursor: 'pointer' as const,
108+
padding: '8px 4px',
109+
borderRadius: '8px',
110+
backgroundColor: isSelected ? 'rgba(23, 201, 100, 0.2)' : 'transparent',
111+
position: 'relative' as const,
112+
marginBottom: 0,
113+
borderBottom: isLast ? 'none' : '1px solid rgba(255, 255, 255, 0.1)',
114+
paddingBottom: '8px',
115+
marginTop: '8px',
116+
transition: 'all 0.2s ease'
117+
})
118+
119+
// Handle hover effects
120+
const handleMouseOver = (e: React.MouseEvent<HTMLDivElement>, isSelected: boolean) => {
121+
if (!isSelected) {
122+
e.currentTarget.style.backgroundColor = 'rgba(255, 255, 255, 0.1)'
123+
}
124+
}
125+
126+
const handleMouseOut = (e: React.MouseEvent<HTMLDivElement>, isSelected: boolean) => {
127+
if (!isSelected) {
128+
e.currentTarget.style.backgroundColor = 'transparent'
129+
}
130+
}
131+
132+
// Render chevron icon
133+
const renderChevron = () => (
134+
<svg
135+
width="16"
136+
height="16"
137+
viewBox="0 0 24 24"
138+
fill="none"
139+
stroke="currentColor"
140+
strokeWidth="2"
141+
strokeLinecap="round"
142+
strokeLinejoin="round"
143+
style={{
144+
transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
145+
transition: 'transform 0.2s ease'
146+
}}
147+
>
148+
<polyline points="6 9 12 15 18 9"></polyline>
149+
</svg>
150+
)
151+
152+
// Render checkmark icon
153+
const renderCheckmark = () => (
154+
<div style={styles.checkmark}>
155+
<svg
156+
xmlns="http://www.w3.org/2000/svg"
157+
width="14"
158+
height="14"
159+
viewBox="0 0 24 24"
160+
fill="none"
161+
stroke="currentColor"
162+
strokeWidth="3"
163+
strokeLinecap="round"
164+
strokeLinejoin="round"
165+
>
166+
<polyline points="20 6 9 17 4 12"></polyline>
167+
</svg>
168+
</div>
169+
)
170+
171+
return (
172+
<div ref={dropdownRef} style={styles.dropdownContainer}>
173+
{/* Dropdown Button */}
174+
<Card onClick={() => setIsOpen(!isOpen)} css={styles.dropdownButton}>
175+
<Card.Body css={styles.cardBody}>
176+
<div style={styles.contentWrapper}>
177+
{items.length > 0 && selectedIndex >= 0 ? (
178+
renderItem(items[selectedIndex])
179+
) : (
180+
<Text size={14} css={{ color: '#aaaaaa' }}>
181+
{placeholder}
182+
</Text>
183+
)}
184+
<div style={styles.chevronContainer}>{renderChevron()}</div>
185+
</div>
186+
</Card.Body>
187+
</Card>
188+
189+
{/* Dropdown Menu */}
190+
{isOpen && (
191+
<Card css={styles.dropdownMenu}>
192+
{items.length > 0 ? (
193+
items.map((item, idx) => {
194+
const isSelected = selectedIndex === idx
195+
const isLastItem = idx === items.length - 1
196+
197+
return (
198+
<div
199+
key={idx}
200+
onClick={() => handleSelect(idx)}
201+
style={getDropdownItemStyle(isSelected, isLastItem)}
202+
onMouseOver={e => handleMouseOver(e, isSelected)}
203+
onMouseOut={e => handleMouseOut(e, isSelected)}
204+
>
205+
{renderItem(item)}
206+
{isSelected && renderCheckmark()}
207+
</div>
208+
)
209+
})
210+
) : (
211+
<Text css={{ color: '$accents7', padding: '8px 4px' }}>{emptyMessage}</Text>
212+
)}
213+
</Card>
214+
)}
215+
</div>
216+
)
217+
}

0 commit comments

Comments
 (0)