Skip to content

Commit 7b518e7

Browse files
authored
Merge pull request #502 from WatchItDev/app/fix/ui
refactor: quick transfer UI, profile tabs rendering, profile transfer modal
2 parents e5c89a0 + cb571fd commit 7b518e7

9 files changed

+262
-76
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import Box from "@mui/material/Box";
2+
import Typography from "@mui/material/Typography";
3+
import {FC} from "react";
4+
import {Profile} from "@lens-protocol/api-bindings";
5+
import {truncateAddress} from "@src/utils/wallet.ts";
6+
7+
interface FinanceDisplayNameProps {
8+
mode: 'profile' | 'wallet';
9+
initialList?: Profile[];
10+
carousel: any;
11+
}
12+
13+
/**
14+
* FinanceDisplayName is a functional component responsible for rendering the display name
15+
* of a selected profile from a provided list. If no list is provided or the list is empty,
16+
* the component returns null. Otherwise, it displays the name of the currently selected
17+
* profile in a styled container.
18+
*
19+
* Props:
20+
* @param {Object} initialList - Array of profiles containing details such as the local name.
21+
* @param {Object} carousel - Object containing the current index used to determine the selected profile.
22+
* @param {string} mode - Determines whether to display the profile name or wallet address.
23+
*
24+
* Behavior:
25+
* - If initialList is empty or null, the component does not render anything.
26+
* - It selects a profile based on the carousel's currentIndex and renders the localName of that profile.
27+
* - If no profile is selected, it falls back to a default message ('No profile selected').
28+
*/
29+
const FinanceDisplayProfileInfo: FC<FinanceDisplayNameProps> = ({initialList, carousel, mode}) => {
30+
// If the initial list is empty, return
31+
if (!initialList?.length) {
32+
return null;
33+
}
34+
35+
const selectedProfile = initialList?.[carousel.currentIndex];
36+
return (
37+
<Box sx={{ textAlign: 'center',mt:-2, mb: 1 }}>
38+
{
39+
mode === 'profile' ?
40+
(<Box component="span" sx={{ flexGrow: 1, typography: 'subtitle1' }}>
41+
{selectedProfile?.metadata?.displayName ?? 'No profile selected'}
42+
</Box>) : null
43+
}
44+
{
45+
mode === 'wallet' ?
46+
<Typography variant="body2" color="text.secondary">
47+
{truncateAddress(selectedProfile?.ownedBy?.address)}
48+
</Typography> : null
49+
}
50+
</Box>
51+
);
52+
}
53+
54+
export default FinanceDisplayProfileInfo;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import Box from "@mui/material/Box";
2+
import Stack from "@mui/material/Stack";
3+
import Typography from "@mui/material/Typography";
4+
5+
/**
6+
* FinanceNoFollowingsQuickTransfer is a React functional component that serves as a user interface element
7+
* to guide users when there are no followings available for quick transfers.
8+
*
9+
* Features:
10+
* - Displays a message prompting the user to follow someone in order to make transfers.
11+
* - Includes a visual divider with an "OR" indicator for alternate actions.
12+
* - Provides instructions to perform a search to initiate a transfer.
13+
*
14+
* Styling:
15+
* - The component utilizes a centrally aligned design with spacing between elements.
16+
* - A dashed line and a circular indicator are used as visual cues.
17+
* - Background color and opacity settings enhance readability and focus.
18+
*/
19+
const FinanceNoFollowingsQuickTransfer = () => {
20+
return (
21+
<Box sx={{ textAlign: 'center', mb: 3, backgroundColor: 'rgba(0,0,0,0.1)', p: 1, borderRadius: 1 }}>
22+
<Stack direction="column" alignItems="center" spacing={1}>
23+
<Typography variant="body2" color="text.primary">
24+
Here appear your followings. Follow one to transfer.
25+
</Typography>
26+
<Box sx={{ opacity:0.7, position: 'relative', display: 'flex', alignItems: 'center', my: 2 }}>
27+
<Box
28+
sx={{
29+
position: 'absolute',
30+
top: '50%',
31+
transform: 'translateX(-50%)',
32+
width: 200,
33+
height: 25,
34+
display: 'flex',
35+
alignItems: 'center',
36+
justifyContent: 'center',
37+
borderTop: '1px dashed',
38+
borderColor: 'text.secondary',
39+
}} />
40+
<Box
41+
sx={{
42+
position: 'absolute',
43+
left: '50%',
44+
transform: 'translateX(-50%)',
45+
backgroundColor: 'rgb(44,46,51)',
46+
borderRadius: '50%',
47+
width: 30,
48+
height: 30,
49+
display: 'flex',
50+
alignItems: 'center',
51+
justifyContent: 'center',
52+
border: '1px dashed',
53+
borderColor: 'text.secondary',
54+
}}
55+
>
56+
<Typography color="text.secondary" sx={{ fontSize: 12 }}>
57+
OR
58+
</Typography>
59+
</Box>
60+
</Box>
61+
<Typography variant="body2" color="text.primary">
62+
Perform a search to start a transfer.
63+
</Typography>
64+
</Stack>
65+
</Box>
66+
);
67+
}
68+
69+
export default FinanceNoFollowingsQuickTransfer;

src/sections/finance/components/finance-quick-transfer-modal.tsx

+36-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from 'react';
1+
import React, {useCallback, useEffect, useState} from 'react';
22
import { useSelector } from 'react-redux';
33

44
// MUI components
@@ -21,7 +21,7 @@ import LoadingButton from '@mui/lab/LoadingButton';
2121
import { supabase } from '@src/utils/supabase';
2222
import { useNotificationPayload } from '@src/hooks/use-notification-payload.ts';
2323
import { useNotifications } from '@src/hooks/use-notifications.ts';
24-
import { InputAmountProps } from '@src/components/input-amount.tsx';
24+
import {InputAmount, InputAmountProps} from '@src/components/input-amount.tsx';
2525
import { useTransfer } from '@src/hooks/use-transfer.ts';
2626

2727
// Notifications
@@ -30,6 +30,8 @@ import { SUCCESS } from '@notifications/success.ts';
3030
import { ERRORS } from '@notifications/errors.ts';
3131
import {dicebear} from "@src/utils/dicebear.ts";
3232
import AvatarProfile from "@src/components/avatar/avatar.tsx";
33+
import {MAX_POOL} from "@src/sections/finance/components/finance-quick-transfer.tsx";
34+
import {handleAmountConstraints} from "@src/utils/format-number.ts";
3335

3436
type TConfirmTransferDialogProps = InputAmountProps & DialogProps;
3537

@@ -55,6 +57,12 @@ function FinanceQuickTransferModal({
5557
const { sendNotification } = useNotifications();
5658
const [message, setMessage] = useState('');
5759

60+
// For transfer button is clicked in some profile
61+
const balance = useSelector((state: any) => state.auth.balance);
62+
const MAX_AMOUNT = balance;
63+
const [value, setValue] = useState(0);
64+
const [canContinue, setCanContinue] = useState(true);
65+
5866
// Check if we have a valid profile or not
5967
const hasProfile = !!contactInfo;
6068

@@ -100,7 +108,7 @@ function FinanceQuickTransferModal({
100108

101109
const handleConfirmTransfer = async () => {
102110
try {
103-
await transfer({ amount, recipient: address ?? '' });
111+
await transfer({ amount: amount > 0 ? amount: value, recipient: address ?? '' });
104112

105113
onFinish();
106114

@@ -123,7 +131,7 @@ function FinanceQuickTransferModal({
123131
// Store transaction in supabase
124132
await storeTransactionInSupabase(contactInfo?.id ?? address, senderId, {
125133
address: contactInfo?.ownedBy?.address ?? address,
126-
amount,
134+
amount: amount > 0 ? amount : value,
127135
message,
128136
...notificationPayload,
129137
});
@@ -143,6 +151,16 @@ function FinanceQuickTransferModal({
143151
}
144152
};
145153

154+
const handleChangeInput = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
155+
const value = Number(event.target.value);
156+
handleAmountConstraints({value, MAX_AMOUNT, MAX_POOL, setAmount: setValue, setCanContinue});
157+
}, [MAX_AMOUNT]);
158+
159+
160+
const handleBlur = useCallback(() => {
161+
handleAmountConstraints({value, MAX_AMOUNT, MAX_POOL, setAmount: setValue, setCanContinue});
162+
}, [value, MAX_AMOUNT]);
163+
146164
const RainbowEffect = transferLoading ? NeonPaper : Box;
147165

148166
return (
@@ -165,11 +183,19 @@ function FinanceQuickTransferModal({
165183
</Stack>
166184

167185
<Stack direction="column" spacing={0} sx={{ py: 2, flexGrow: 1 }}>
168-
<ListItemText
169-
primary="Amount:"
170-
secondary={`${amount} MMC`}
171-
secondaryTypographyProps={{ component: 'span', mt: 0.5 }}
172-
/>
186+
{amount > 0 ? (
187+
188+
<ListItemText
189+
primary="Amount:"
190+
secondary={`${amount} MMC`}
191+
secondaryTypographyProps={{ component: 'span', mt: 0.5 }}
192+
/>
193+
) : <InputAmount
194+
max={MAX_POOL}
195+
amount={value}
196+
onBlur={handleBlur}
197+
onChange={handleChangeInput}
198+
/>}
173199
</Stack>
174200
</Stack>
175201

@@ -190,7 +216,7 @@ function FinanceQuickTransferModal({
190216
variant="contained"
191217
sx={{ backgroundColor: '#fff' }}
192218
onClick={handleConfirmTransfer}
193-
disabled={transferLoading}
219+
disabled={transferLoading || !canContinue}
194220
loading={transferLoading}
195221
>
196222
Confirm

src/sections/finance/components/finance-quick-transfer.tsx

+15-62
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useState } from 'react';
33

44
// REDUX IMPORTS
55
import { useDispatch, useSelector } from 'react-redux';
6-
import { storeAddress, toggleRainbow } from '@redux/address';
6+
import { storeAddress } from '@redux/address';
77

88
// LENS IMPORTS
99
import { Profile } from '@lens-protocol/api-bindings';
@@ -17,7 +17,6 @@ import Stack from '@mui/material/Stack';
1717
import Button from '@mui/material/Button';
1818
import Slider from '@mui/material/Slider';
1919
import Tooltip from '@mui/material/Tooltip';
20-
import TextField from '@mui/material/TextField';
2120
import Box from '@mui/material/Box';
2221
import CardHeader from '@mui/material/CardHeader';
2322
import { CardProps } from '@mui/material/Card';
@@ -30,13 +29,17 @@ import { InputAmount } from '@src/components/input-amount.tsx';
3029
import FinanceQuickTransferModal from '@src/sections/finance/components/finance-quick-transfer-modal.tsx';
3130
import FinanceSearchProfileModal from '@src/sections/finance/components/finance-search-profile-modal.tsx';
3231
import AvatarProfile from "@src/components/avatar/avatar.tsx";
32+
import FinanceNoFollowingsQuickTransfer
33+
from "@src/sections/finance/components/finance-no-followings-quick-transfer";
34+
import FinanceDisplayProfileInfo from "@src/sections/finance/components/finance-display-profile-info";
35+
import {handleAmountConstraints} from "@src/utils/format-number.ts";
3336

3437
// ----------------------------------------------------------------------
3538

3639
const STEP = 50;
3740
const MIN_AMOUNT = 0;
3841
// A thousand millions allowed in the pool
39-
const MAX_POOL: number = 1000000000;
42+
export const MAX_POOL: number = 1000000000;
4043

4144
interface Props extends CardProps {
4245
title?: string;
@@ -67,7 +70,6 @@ export default function FinanceQuickTransfer({
6770

6871
// Local states
6972
const [walletAddress, setWalletAddress] = useState(storedAddress.address ?? '');
70-
const [addressError, setAddressError] = useState(false);
7173
const [currentIndex, setCurrentIndex] = useState(0);
7274
const [addressFiltered, setAddressFiltered] = useState<boolean>(false);
7375
const [initialized, setInitialized] = useState(false);
@@ -182,27 +184,6 @@ export default function FinanceQuickTransfer({
182184
}
183185
}, [initialList]);
184186

185-
// Handle changes in the input field for the wallet address
186-
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
187-
const value = event.target.value;
188-
setWalletAddress(value);
189-
dispatch(storeAddress({ address: value, profileId: getContactInfo?.id ?? '' }));
190-
191-
// If it's a valid address, let the next effect handle searching in the list
192-
if (isValidAddress(value)) {
193-
setAddressFiltered(true); // We set a flag that we typed a valid address
194-
dispatch(toggleRainbow());
195-
setAddressError(false);
196-
} else {
197-
setAddressError(true);
198-
}
199-
200-
// Rainbow effect trigger
201-
setTimeout(() => {
202-
dispatch(toggleRainbow());
203-
}, 1400);
204-
};
205-
206187
// Handle changes in the slider
207188
const handleChangeSlider = useCallback((_event: Event, newValue: number | number[]) => {
208189
setAmount(newValue as number);
@@ -211,31 +192,14 @@ export default function FinanceQuickTransfer({
211192
}
212193
}, [MAX_AMOUNT]);
213194

214-
// Helper function to handle amount constraints
215-
const handleAmountConstraints = (value: number, MAX_AMOUNT: number) => {
216-
if (value > MAX_POOL) {
217-
value = MAX_POOL; // Truncate to a thousand millions
218-
}
219-
if (value < 0) {
220-
value = 0; // Set amount to 0 if lower than 0
221-
}
222-
setAmount(value);
223-
setCanContinue(value <= MAX_AMOUNT);
224-
225-
// If amount is greater than balance, allow input but setCanContinue to false
226-
if (value > MAX_AMOUNT) {
227-
setCanContinue(false);
228-
}
229-
};
230-
231195
const handleChangeInput = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
232196
const value = Number(event.target.value);
233-
handleAmountConstraints(value, MAX_AMOUNT);
197+
handleAmountConstraints({value, MAX_AMOUNT, MAX_POOL, setAmount, setCanContinue});
234198
}, [MAX_AMOUNT]);
235199

236200

237201
const handleBlur = useCallback(() => {
238-
handleAmountConstraints(amount, MAX_AMOUNT);
202+
handleAmountConstraints({value: amount, MAX_AMOUNT, MAX_POOL, setAmount, setCanContinue});
239203
}, [amount, MAX_AMOUNT]);
240204

241205

@@ -293,21 +257,6 @@ export default function FinanceQuickTransfer({
293257
// We pick the contactInfo to pass to the modal. If currentIndex is -1, there's no matched profile
294258
const contactInfoToPass = currentIndex === -1 ? undefined : getContactInfo;
295259

296-
// Render the wallet address input
297-
const renderWalletInput = (
298-
<Box sx={{ mb: 3 }}>
299-
<TextField
300-
fullWidth
301-
label="Wallet Address"
302-
value={walletAddress}
303-
onChange={handleInputChange}
304-
placeholder="Enter wallet address"
305-
error={addressError}
306-
helperText={addressError ? 'Invalid wallet address' : ''}
307-
/>
308-
</Box>
309-
);
310-
311260
// Render the carousel of profiles
312261
const renderCarousel = (
313262
<Box sx={{ position: 'relative', mb: 3 }}>
@@ -418,13 +367,14 @@ export default function FinanceQuickTransfer({
418367
disabled={amount === 0 || !isValidAddress(walletAddress) || !canContinue}
419368
onClick={confirm.onTrue}
420369
>
421-
Transfer Now
370+
Quick transfer
422371
</Button>
423372
</Stack>
424373
);
425374

426375
const Wrapper = showRainbow ? NeonPaper : Box;
427376

377+
console.log(list);
428378
return (
429379
<>
430380
<Wrapper
@@ -442,15 +392,17 @@ export default function FinanceQuickTransfer({
442392
{...other}
443393
>
444394
<CardHeader
395+
sx={{ p: '12px 16px 0 0' }}
445396
title={title}
446397
subheader={subheader}
447398
action={<FinanceSearchProfileModal onSelectProfile={handleSelectProfile} />}
448399
/>
449400

450401
{/* Content */}
451402
<Stack sx={{ p: 3 }}>
452-
{renderWalletInput}
453-
{!!list?.length && renderCarousel}
403+
<FinanceDisplayProfileInfo mode={'profile'} initialList={initialList} carousel={carousel} />
404+
{list?.length > 0 ? renderCarousel : <FinanceNoFollowingsQuickTransfer />}
405+
<FinanceDisplayProfileInfo mode={'wallet'} initialList={initialList} carousel={carousel} />
454406
{renderInput}
455407
</Stack>
456408
</Stack>
@@ -471,3 +423,4 @@ export default function FinanceQuickTransfer({
471423
</>
472424
);
473425
}
426+

0 commit comments

Comments
 (0)