diff --git a/src/sections/finance/components/finance-display-profile-info.tsx b/src/sections/finance/components/finance-display-profile-info.tsx new file mode 100644 index 00000000..2f802c68 --- /dev/null +++ b/src/sections/finance/components/finance-display-profile-info.tsx @@ -0,0 +1,54 @@ +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import {FC} from "react"; +import {Profile} from "@lens-protocol/api-bindings"; +import {truncateAddress} from "@src/utils/wallet.ts"; + +interface FinanceDisplayNameProps { + mode: 'profile' | 'wallet'; + initialList?: Profile[]; + carousel: any; +} + +/** + * FinanceDisplayName is a functional component responsible for rendering the display name + * of a selected profile from a provided list. If no list is provided or the list is empty, + * the component returns null. Otherwise, it displays the name of the currently selected + * profile in a styled container. + * + * Props: + * @param {Object} initialList - Array of profiles containing details such as the local name. + * @param {Object} carousel - Object containing the current index used to determine the selected profile. + * @param {string} mode - Determines whether to display the profile name or wallet address. + * + * Behavior: + * - If initialList is empty or null, the component does not render anything. + * - It selects a profile based on the carousel's currentIndex and renders the localName of that profile. + * - If no profile is selected, it falls back to a default message ('No profile selected'). + */ +const FinanceDisplayProfileInfo: FC = ({initialList, carousel, mode}) => { + // If the initial list is empty, return + if (!initialList?.length) { + return null; + } + + const selectedProfile = initialList?.[carousel.currentIndex]; + return ( + + { + mode === 'profile' ? + ( + {selectedProfile?.metadata?.displayName ?? 'No profile selected'} + ) : null + } + { + mode === 'wallet' ? + + {truncateAddress(selectedProfile?.ownedBy?.address)} + : null + } + + ); +} + +export default FinanceDisplayProfileInfo; diff --git a/src/sections/finance/components/finance-no-followings-quick-transfer.tsx b/src/sections/finance/components/finance-no-followings-quick-transfer.tsx new file mode 100644 index 00000000..bf53ce7c --- /dev/null +++ b/src/sections/finance/components/finance-no-followings-quick-transfer.tsx @@ -0,0 +1,69 @@ +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; + +/** + * FinanceNoFollowingsQuickTransfer is a React functional component that serves as a user interface element + * to guide users when there are no followings available for quick transfers. + * + * Features: + * - Displays a message prompting the user to follow someone in order to make transfers. + * - Includes a visual divider with an "OR" indicator for alternate actions. + * - Provides instructions to perform a search to initiate a transfer. + * + * Styling: + * - The component utilizes a centrally aligned design with spacing between elements. + * - A dashed line and a circular indicator are used as visual cues. + * - Background color and opacity settings enhance readability and focus. + */ +const FinanceNoFollowingsQuickTransfer = () => { + return ( + + + + Here appear your followings. Follow one to transfer. + + + + + + OR + + + + + Perform a search to start a transfer. + + + + ); +} + +export default FinanceNoFollowingsQuickTransfer; diff --git a/src/sections/finance/components/finance-quick-transfer-modal.tsx b/src/sections/finance/components/finance-quick-transfer-modal.tsx index ecdc1ad0..db1dacd1 100644 --- a/src/sections/finance/components/finance-quick-transfer-modal.tsx +++ b/src/sections/finance/components/finance-quick-transfer-modal.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import { useSelector } from 'react-redux'; // MUI components @@ -21,7 +21,7 @@ import LoadingButton from '@mui/lab/LoadingButton'; import { supabase } from '@src/utils/supabase'; import { useNotificationPayload } from '@src/hooks/use-notification-payload.ts'; import { useNotifications } from '@src/hooks/use-notifications.ts'; -import { InputAmountProps } from '@src/components/input-amount.tsx'; +import {InputAmount, InputAmountProps} from '@src/components/input-amount.tsx'; import { useTransfer } from '@src/hooks/use-transfer.ts'; // Notifications @@ -30,6 +30,8 @@ import { SUCCESS } from '@notifications/success.ts'; import { ERRORS } from '@notifications/errors.ts'; import {dicebear} from "@src/utils/dicebear.ts"; import AvatarProfile from "@src/components/avatar/avatar.tsx"; +import {MAX_POOL} from "@src/sections/finance/components/finance-quick-transfer.tsx"; +import {handleAmountConstraints} from "@src/utils/format-number.ts"; type TConfirmTransferDialogProps = InputAmountProps & DialogProps; @@ -55,6 +57,12 @@ function FinanceQuickTransferModal({ const { sendNotification } = useNotifications(); const [message, setMessage] = useState(''); + // For transfer button is clicked in some profile + const balance = useSelector((state: any) => state.auth.balance); + const MAX_AMOUNT = balance; + const [value, setValue] = useState(0); + const [canContinue, setCanContinue] = useState(true); + // Check if we have a valid profile or not const hasProfile = !!contactInfo; @@ -100,7 +108,7 @@ function FinanceQuickTransferModal({ const handleConfirmTransfer = async () => { try { - await transfer({ amount, recipient: address ?? '' }); + await transfer({ amount: amount > 0 ? amount: value, recipient: address ?? '' }); onFinish(); @@ -123,7 +131,7 @@ function FinanceQuickTransferModal({ // Store transaction in supabase await storeTransactionInSupabase(contactInfo?.id ?? address, senderId, { address: contactInfo?.ownedBy?.address ?? address, - amount, + amount: amount > 0 ? amount : value, message, ...notificationPayload, }); @@ -143,6 +151,16 @@ function FinanceQuickTransferModal({ } }; + const handleChangeInput = useCallback((event: React.ChangeEvent) => { + const value = Number(event.target.value); + handleAmountConstraints({value, MAX_AMOUNT, MAX_POOL, setAmount: setValue, setCanContinue}); + }, [MAX_AMOUNT]); + + + const handleBlur = useCallback(() => { + handleAmountConstraints({value, MAX_AMOUNT, MAX_POOL, setAmount: setValue, setCanContinue}); + }, [value, MAX_AMOUNT]); + const RainbowEffect = transferLoading ? NeonPaper : Box; return ( @@ -165,11 +183,19 @@ function FinanceQuickTransferModal({ - + {amount > 0 ? ( + + + ) : } @@ -190,7 +216,7 @@ function FinanceQuickTransferModal({ variant="contained" sx={{ backgroundColor: '#fff' }} onClick={handleConfirmTransfer} - disabled={transferLoading} + disabled={transferLoading || !canContinue} loading={transferLoading} > Confirm diff --git a/src/sections/finance/components/finance-quick-transfer.tsx b/src/sections/finance/components/finance-quick-transfer.tsx index f23ba4ef..efca0d98 100644 --- a/src/sections/finance/components/finance-quick-transfer.tsx +++ b/src/sections/finance/components/finance-quick-transfer.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useState } from 'react'; // REDUX IMPORTS import { useDispatch, useSelector } from 'react-redux'; -import { storeAddress, toggleRainbow } from '@redux/address'; +import { storeAddress } from '@redux/address'; // LENS IMPORTS import { Profile } from '@lens-protocol/api-bindings'; @@ -17,7 +17,6 @@ import Stack from '@mui/material/Stack'; import Button from '@mui/material/Button'; import Slider from '@mui/material/Slider'; import Tooltip from '@mui/material/Tooltip'; -import TextField from '@mui/material/TextField'; import Box from '@mui/material/Box'; import CardHeader from '@mui/material/CardHeader'; import { CardProps } from '@mui/material/Card'; @@ -30,13 +29,17 @@ import { InputAmount } from '@src/components/input-amount.tsx'; import FinanceQuickTransferModal from '@src/sections/finance/components/finance-quick-transfer-modal.tsx'; import FinanceSearchProfileModal from '@src/sections/finance/components/finance-search-profile-modal.tsx'; import AvatarProfile from "@src/components/avatar/avatar.tsx"; +import FinanceNoFollowingsQuickTransfer + from "@src/sections/finance/components/finance-no-followings-quick-transfer"; +import FinanceDisplayProfileInfo from "@src/sections/finance/components/finance-display-profile-info"; +import {handleAmountConstraints} from "@src/utils/format-number.ts"; // ---------------------------------------------------------------------- const STEP = 50; const MIN_AMOUNT = 0; // A thousand millions allowed in the pool -const MAX_POOL: number = 1000000000; +export const MAX_POOL: number = 1000000000; interface Props extends CardProps { title?: string; @@ -67,7 +70,6 @@ export default function FinanceQuickTransfer({ // Local states const [walletAddress, setWalletAddress] = useState(storedAddress.address ?? ''); - const [addressError, setAddressError] = useState(false); const [currentIndex, setCurrentIndex] = useState(0); const [addressFiltered, setAddressFiltered] = useState(false); const [initialized, setInitialized] = useState(false); @@ -182,27 +184,6 @@ export default function FinanceQuickTransfer({ } }, [initialList]); - // Handle changes in the input field for the wallet address - const handleInputChange = (event: React.ChangeEvent) => { - const value = event.target.value; - setWalletAddress(value); - dispatch(storeAddress({ address: value, profileId: getContactInfo?.id ?? '' })); - - // If it's a valid address, let the next effect handle searching in the list - if (isValidAddress(value)) { - setAddressFiltered(true); // We set a flag that we typed a valid address - dispatch(toggleRainbow()); - setAddressError(false); - } else { - setAddressError(true); - } - - // Rainbow effect trigger - setTimeout(() => { - dispatch(toggleRainbow()); - }, 1400); - }; - // Handle changes in the slider const handleChangeSlider = useCallback((_event: Event, newValue: number | number[]) => { setAmount(newValue as number); @@ -211,31 +192,14 @@ export default function FinanceQuickTransfer({ } }, [MAX_AMOUNT]); - // Helper function to handle amount constraints - const handleAmountConstraints = (value: number, MAX_AMOUNT: number) => { - if (value > MAX_POOL) { - value = MAX_POOL; // Truncate to a thousand millions - } - if (value < 0) { - value = 0; // Set amount to 0 if lower than 0 - } - setAmount(value); - setCanContinue(value <= MAX_AMOUNT); - - // If amount is greater than balance, allow input but setCanContinue to false - if (value > MAX_AMOUNT) { - setCanContinue(false); - } - }; - const handleChangeInput = useCallback((event: React.ChangeEvent) => { const value = Number(event.target.value); - handleAmountConstraints(value, MAX_AMOUNT); + handleAmountConstraints({value, MAX_AMOUNT, MAX_POOL, setAmount, setCanContinue}); }, [MAX_AMOUNT]); const handleBlur = useCallback(() => { - handleAmountConstraints(amount, MAX_AMOUNT); + handleAmountConstraints({value: amount, MAX_AMOUNT, MAX_POOL, setAmount, setCanContinue}); }, [amount, MAX_AMOUNT]); @@ -293,21 +257,6 @@ export default function FinanceQuickTransfer({ // We pick the contactInfo to pass to the modal. If currentIndex is -1, there's no matched profile const contactInfoToPass = currentIndex === -1 ? undefined : getContactInfo; - // Render the wallet address input - const renderWalletInput = ( - - - - ); - // Render the carousel of profiles const renderCarousel = ( @@ -418,13 +367,14 @@ export default function FinanceQuickTransfer({ disabled={amount === 0 || !isValidAddress(walletAddress) || !canContinue} onClick={confirm.onTrue} > - Transfer Now + Quick transfer ); const Wrapper = showRainbow ? NeonPaper : Box; + console.log(list); return ( <> } @@ -449,8 +400,9 @@ export default function FinanceQuickTransfer({ {/* Content */} - {renderWalletInput} - {!!list?.length && renderCarousel} + + {list?.length > 0 ? renderCarousel : } + {renderInput} @@ -471,3 +423,4 @@ export default function FinanceQuickTransfer({ ); } + diff --git a/src/sections/finance/index.tsx b/src/sections/finance/index.tsx index eaca528e..86aca3f6 100644 --- a/src/sections/finance/index.tsx +++ b/src/sections/finance/index.tsx @@ -76,7 +76,7 @@ export default function OverviewBankingView() { }} /> - {!mdUp ? : null} + {!mdUp ? : null} {lgUp ? : null} @@ -110,7 +110,6 @@ export default function OverviewBankingView() { )} + { + sessionData?.authenticated && profile?.id !== sessionData?.profile?.id && ( + + ) + } diff --git a/src/sections/user/profile-transfer.tsx b/src/sections/user/profile-transfer.tsx new file mode 100644 index 00000000..795a4bbc --- /dev/null +++ b/src/sections/user/profile-transfer.tsx @@ -0,0 +1,44 @@ +import {Profile} from "@lens-protocol/api-bindings"; +import {FC} from "react"; +import FinanceQuickTransferModal from "@src/sections/finance/components/finance-quick-transfer-modal.tsx"; +import LoadingButton from "@mui/lab/LoadingButton"; +import {useBoolean} from "@src/hooks/use-boolean.ts"; + +interface ProfileTransferProps { + profile: Profile; +} + +const ProfileTransfer: FC = ({profile}) => { + const confirm = useBoolean(); + + const handleOpen = () => { + confirm.onTrue(); + } + + const handleTransferFinish = () => { + confirm.onFalse(); + } + + return ( + <> + + Transfer + + + + + ) +} + +export default ProfileTransfer; diff --git a/src/sections/user/view/user-profile-view.tsx b/src/sections/user/view/user-profile-view.tsx index dda889b7..ac857b72 100644 --- a/src/sections/user/view/user-profile-view.tsx +++ b/src/sections/user/view/user-profile-view.tsx @@ -126,8 +126,12 @@ const UserProfileView = ({ id }: any) => { width: 1, zIndex: 9, borderBottom: '1px solid rgba(255, 255, 255, 0.08)', - [`& .${tabsClasses.flexContainer}`]: { justifyContent: 'center', px: 1, pl: { xs: 10, md: 0 } }, - [`& .${tabsClasses.scroller}`]: { display: 'flex', justifyContent: 'center' }, + [`& .${tabsClasses.flexContainer}`]: { justifyContent: 'flex-start', px: 0 }, + [`& .${tabsClasses.scroller}`]: { display: 'flex', justifyContent: {xs: 'flex-start', sm: 'center'} }, + [`& .${tabsClasses.scrollButtons}`]: { + '&.MuiTabs-scrollButtons.Mui-disabled': { display: 'none' }, + '&:first-of-type': { display: currentTab === 'publications' ? 'none' : 'flex' }, + }, }} > {tabsWithCounts.map((tab) => ( diff --git a/src/utils/format-number.ts b/src/utils/format-number.ts index b8242a75..357b25cd 100644 --- a/src/utils/format-number.ts +++ b/src/utils/format-number.ts @@ -42,3 +42,34 @@ export function formatBalanceNumber(balance: number) { const balanceOptions = { minimumFractionDigits: 1, maximumFractionDigits: 3 }; return new Intl.NumberFormat('en-US', balanceOptions).format(balance as any); } + +interface AmountConstraintsProps { + value: number; + MAX_AMOUNT: number; + MAX_POOL: number; + setAmount: (value: number) => void; + setCanContinue: (canContinue: boolean) => void; +} + +export const handleAmountConstraints = ({ + value, + MAX_AMOUNT, + MAX_POOL, + setAmount, + setCanContinue, + }: AmountConstraintsProps) => { + if (value > MAX_POOL) { + value = MAX_POOL; // Truncate to a thousand millions + } + if (value < 0) { + value = 0; // Set amount to 0 if lower than 0 + } + setAmount(value); + setCanContinue(value <= MAX_AMOUNT); + + // If amount is greater than balance, allow input but setCanContinue to false + if (value > MAX_AMOUNT || value <= 0) { + setCanContinue(false); + } + console.log('value', value); +};