Skip to content

Commit

Permalink
feat(top-up-wallet): create ui for TopupWallet version mobile
Browse files Browse the repository at this point in the history
  • Loading branch information
ngyngcphu committed Dec 4, 2023
1 parent a0a7ce1 commit eef5227
Show file tree
Hide file tree
Showing 19 changed files with 505 additions and 256 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# This file is not allowed to be deleted, because it represents which environment variables to use when the project is open-source.
VITE_BACKEND_URL=
VITE_GOOGLE_OAUTH_REDIRECT_URL=
VITE_GOOGLE_CLIENT_ID=
VITE_GOOGLE_CLIENT_ID=
VITE_PAYPAL_CLIENT_ID=
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@heroicons/react": "^2.0.18",
"@hookform/resolvers": "^3.2.0",
"@material-tailwind/react": "^2.1.0",
"@paypal/react-paypal-js": "^8.1.3",
"@react-oauth/google": "^0.12.1",
"@tanstack/react-query": "^5.8.4",
"@tanstack/react-table": "^8.10.7",
Expand Down
Binary file added src/assets/paypal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 0 additions & 70 deletions src/components/order/mobile/ExchangeRateInfo.tsx

This file was deleted.

40 changes: 0 additions & 40 deletions src/components/order/mobile/PaymentMethod.tsx

This file was deleted.

245 changes: 238 additions & 7 deletions src/components/order/mobile/TopupWalletForm.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,241 @@
import { ChevronLeftIcon } from '@heroicons/react/24/solid';
import { Button, Card, CardBody, Typography } from '@material-tailwind/react';
import { ExchangeRateInfo } from './ExchangeRateInfo';
import { PaymentMethod } from './PaymentMethod';
import { TopupWalletInput } from './TopupWalletInput';
// Tue's first-task in here.
import { ReactElement, useState, useRef } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { Alert, Button, Card, CardBody, Input, Typography } from '@material-tailwind/react';
import {
BanknotesIcon,
CurrencyDollarIcon,
DocumentTextIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
WalletIcon
} from '@heroicons/react/24/outline';
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronUpIcon,
XMarkIcon
} from '@heroicons/react/24/solid';
import coinImage from '@assets/coin.png';
import paypal from '@assets/paypal.png';
import { SUGGEST_AMOUNT } from '@constants';
import { useExchangeRateQuery } from '@hooks';

export function TopupWalletForm() {
const queryClient = useQueryClient();
const {
exchangeRate: [coinPerPage, coinPerDollar]
} = useExchangeRateQuery();

const ExchangeRateInfo = () => {
const CHEVRON_CLASSNAME =
'w-5 h-5 opacity-40 hover:text-[#0F172A] hover:opacity-100 focus:opacity-100 active:opacity-100';
const [chevronIcon, setChevronIcon] = useState<boolean>(false);

const ExchangeRateRow: Component<{ title: string; coins?: number; children: ReactElement }> = ({
title,
coins,
children
}) => (
<div className='flex items-center justify-between'>
<p className='text-gray/3 text-xs font-normal'>{title}</p>
<div className='flex items-center'>
{coins !== undefined && <p className='text-gray/4 text-xs font-medium'>{coins}</p>}
<img className='w-4 h-4 mix-blend-luminosity' src={coinImage} alt='coinImage'></img>
<p className='text-gray/4 text-xs font-medium mx-1'>=</p>
<div className='min-w-[48px] flex justify-end'>
<p className='text-gray/4 text-xs font-medium'>1</p>
{children}
</div>
</div>
</div>
);

return (
<Card className='rounded-none shadow-sm'>
<CardBody className='p-0'>
<div
className={`px-6 py-4 flex items-center justify-between cursor-pointer ${
chevronIcon ? 'bg-[#EFF6FF]' : ''
}`}
onClick={() => setChevronIcon(!chevronIcon)}
>
<div className='flex items-center'>
<InformationCircleIcon strokeWidth={2} className='w-5 h-5 text-blue/1 mr-2' />
<p className='text-gray/4 text-base font-medium '>Exchange rate</p>
</div>
{!chevronIcon ? (
<ChevronDownIcon className={CHEVRON_CLASSNAME} />
) : (
<ChevronUpIcon className={CHEVRON_CLASSNAME} />
)}
</div>
<div className={`px-6 pt-4 pb-6 flex flex-col ${!chevronIcon ? 'hidden' : ''}`}>
<ExchangeRateRow
title='To Dollar:'
coins={coinPerDollar.data}
children={<CurrencyDollarIcon className='w-4 h-4 text-gray/4' />}
/>
<ExchangeRateRow
title='To A4 paper:'
coins={coinPerPage.data}
children={
<>
<p className='text-gray/4 text-xs font-medium'>A4</p>
<DocumentTextIcon className='w-4 h-4 text-gray/4' />
</>
}
/>
</div>
</CardBody>
</Card>
);
};

const TopupAmountInput = () => {
const remainCoins = queryClient.getQueryData<number>(['/api/user/remain-coins']);
const [showInfo, setShowInfo] = useState<boolean>(false);
const [inputValue, setInputValue] = useState<string>('');
const inputRef = useRef<HTMLInputElement>(null);

return (
<Card className='rounded-none shadow-sm my-4'>
<CardBody className='p-0'>
<div className='px-6 py-4 flex items-center'>
<BanknotesIcon strokeWidth={2} className='w-5 h-5 text-blue/1 mr-2' />
<p className='text-gray/4 text-base font-medium '>Top up wallet</p>
</div>
</CardBody>
<div className='px-6 pt-0 pb-6'>
<div className='flex items-center'>
<p className='text-gray/4 text-xs font-medium'>Current balance:</p>
<img
className='w-4 h-4 mix-blend-luminosity ml-1'
src={coinImage}
alt='coinImage'
></img>
<p className='text-gray/4 text-xs font-medium'>{remainCoins}</p>
</div>
<div className='relative'>
<Input
color='blue'
label='Top up Amount'
value={inputValue}
size='lg'
className='px-4 pb-11 text-gray/4 !text-2xl !font-bold border rounded-lg '
labelProps={{
className:
'text-gray/4 text-xs font-normal peer-placeholder-shown:text-base peer-placeholder-shown:text-[#9CA3AF] peer-disabled:peer-placeholder-shown::text-xs '
}}
containerProps={{
className: 'min-h-[88px] mt-2 '
}}
crossOrigin={undefined}
inputRef={inputRef}
onBlur={() => !inputRef.current?.value && setShowInfo(false)}
onChange={(e) => {
setInputValue(e.target.value);
setShowInfo(true);
}}
maxLength={10}
/>
<div className={showInfo ? '' : 'hidden'}>
<XMarkIcon
className='w-7 h-7 text-gray/4 absolute right-4 top-4 cursor-pointer'
onClick={() => {
setInputValue('');
setShowInfo(false);
}}
/>
<div className='px-4 absolute flex justify-between w-full left-0 bottom-3'>
<div className='flex items-center flex-wrap'>
<p className='text-gray/4 text-xs'>Exchange to:</p>
<img className='w-4 h-4 ml-1' src={coinImage} alt='coinImage'></img>
<p className='text-[#D97706] text-xs font-bold'>
{Math.floor(
parseFloat(inputValue.replace(/[^0-9.]/g, '') || '0') *
(coinPerDollar.data ?? 1)
)}
</p>
</div>
<div className='flex items-center flex-wrap'>
<p className='text-gray/4 text-xs'>(Bonus:</p>
<img
className='w-4 h-4 mix-blend-luminosity'
src={coinImage}
alt='coinImage'
></img>
<Typography className='text-gray/4 text-xs'>
{Math.floor(parseInt(inputValue.replace(/[^0-9.]/g, '') || '0') / 10) *
(coinPerDollar.data ?? 1)}
)
</Typography>
</div>
</div>
{parseFloat(inputValue.replace(/[^0-9.]/g, '')) < 1 && (
<Alert className='bg-red-50 text-red-600 p-2'>
<div className='flex items-center'>
<ExclamationTriangleIcon className='w-5 h-5 mr-2' />
<Typography variant='small'>Minimum amount is {SUGGEST_AMOUNT[0]}</Typography>
</div>
</Alert>
)}
</div>
</div>
<div
className={
showInfo && Number(inputValue.replace(/[^0-9.]/g, '')) < 10000
? 'mt-6 mb-4 flex items-start content-start flex-wrap gap-2'
: 'my-4 flex items-start content-start flex-wrap gap-2'
}
>
{SUGGEST_AMOUNT.map((item, index) => (
<Button
key={index}
className='flex items-center p-2 rounded-lg bg-[#DBEAFE] text-base font-semibold text-blue/1 shadow-none normal-case'
onClick={() => {
setInputValue(item);
setShowInfo(true);
}}
>
{item}
</Button>
))}
</div>
<div className='flex'>
<Typography className='text-gray/4 text-xs font-medium flex'>Bonus:</Typography>
<img className='w-4 h-4' src={coinImage} alt='coinImage'></img>
<Typography className='text-[#D97706] text-xs font-bold'>
{coinPerDollar.data}
</Typography>
<Typography className='text-gray/3 text-xs font-normal ml-1'>
(for every 10$)
</Typography>
</div>
</div>
</Card>
);
};

const PaymentMethod = () => (
<Card className='rounded-none shadow-sm my-4'>
<CardBody className='p-0'>
<div className='px-6 py-4 flex items-center'>
<WalletIcon strokeWidth={2} className='w-5 h-5 text-blue/1 mr-2' />
<p className='text-gray/4 text-base font-medium '>Payment Method</p>
</div>
<div className='px-6 flex items-center'>
<div className='flex items-center gap-2'>
<img className='w-16 object-cover' src={paypal} alt='paypal'></img>
<div className='flex flex-col justify-between items-start'>
<p className='text-gray/4 text-sm font-normal '>Pay with PayPal wallet</p>
<p className='text-gray/3 text-sm font-normal'>Redirect to PayPal</p>
</div>
</div>
</div>
</CardBody>
</Card>
);

return (
<>
<Card className='rounded-none shadow-md'>
Expand All @@ -20,7 +251,7 @@ export function TopupWalletForm() {
</Card>
<div className='w-screen py-6'>
<ExchangeRateInfo />
<TopupWalletInput />
<TopupAmountInput />
<PaymentMethod />
</div>
<footer className='relative w-full'>
Expand Down
Loading

0 comments on commit eef5227

Please sign in to comment.