diff --git a/public/images/hanaMain/bankbook.png b/public/images/hanaMain/bankbook.png new file mode 100644 index 0000000..9c35d4a Binary files /dev/null and b/public/images/hanaMain/bankbook.png differ diff --git a/src/App.tsx b/src/App.tsx index 171208f..1738bd4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,7 @@ import { Outlet } from 'react-router-dom'; import './App.css'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ScrollToTop } from './utils/scrollToTop'; const queryClient = new QueryClient({ defaultOptions: { @@ -13,6 +14,7 @@ const queryClient = new QueryClient({ function App() { return ( <> + diff --git a/src/components/common/Navbar.tsx b/src/components/common/Navbar.tsx index e97d144..5d34b23 100644 --- a/src/components/common/Navbar.tsx +++ b/src/components/common/Navbar.tsx @@ -34,7 +34,6 @@ const navbarItems = [ export const Navbar = () => { const location = useLocation(); - console.log(location); return ( <> diff --git a/src/components/molecules/Alarm.tsx b/src/components/molecules/Alarm.tsx index 1b36c0e..c39a636 100644 --- a/src/components/molecules/Alarm.tsx +++ b/src/components/molecules/Alarm.tsx @@ -18,16 +18,16 @@ export const Alarm: FC = ({ message, showAlarm, onClickShowAlarm }) => { } else { timer = setTimeout(() => { onClickShowAlarm(false); - }, 2000); + }, 1000); } return () => { clearTimeout(timer); }; - }, [showAlarm]); + }, [activeAnimation]); return (
{message}
diff --git a/src/components/molecules/Ex.tsx b/src/components/molecules/Ex.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/molecules/HanaMainCard.tsx b/src/components/molecules/HanaMainCard.tsx index 9c51441..947db1e 100644 --- a/src/components/molecules/HanaMainCard.tsx +++ b/src/components/molecules/HanaMainCard.tsx @@ -17,7 +17,7 @@ export const HanaMainCard: FC = ({ id, Icon, message, color }) => {
{ - id === 2 ? navigate('/') : undefined; + id === 2 ? navigate('/login') : undefined; }} > = ({ dateList, price }) => {

일정 선택 -

- {dateList.map((date, index) => ( -
-
- clickedChoiceDate( - date.lessondate_id, - formatDate(date.date, date.start_time, date.end_time), - date.quantityLeft - ) - } - > -

- {formatDate(date.date, date.start_time, date.end_time)} -

-

- {price.toLocaleString()}원 - - {date.quantityLeft}개 남음 - -

+ {' '} +
+ {dateList.map((date, index) => ( +
+
+ clickedChoiceDate( + date.lessondate_id, + formatDate(date.date, date.start_time, date.end_time), + date.quantityLeft + ) + } + > +

+ {formatDate(date.date, date.start_time, date.end_time)} +

+

+ {price.toLocaleString()}원 + + {date.quantityLeft}개 남음 + +

+
+ {index !== dateList.length && ( +
+ )}
- {index !== dateList.length &&
} -
- ))} + ))} +
{choiceDate && (
diff --git a/src/components/molecules/QR.tsx b/src/components/molecules/QR.tsx index d9bfc53..e3d74be 100644 --- a/src/components/molecules/QR.tsx +++ b/src/components/molecules/QR.tsx @@ -1,7 +1,8 @@ import QRCode from 'qrcode.react'; -import { FC, useState } from 'react'; -import { QRScanner } from './QRScanner'; +import { FC, useEffect, useState } from 'react'; import { ModalBottomContainer } from '../organisms/ModalBottomContainer'; +import { useTimer } from '../../hooks/useTimer'; +import { VscDebugRestart } from 'react-icons/vsc'; interface IProps { userId: number; @@ -18,41 +19,67 @@ export const QR: FC = ({ balance, onClose, }) => { - console.log(userId, accountId, accountNumber, balance); - const [isScan, setIsScan] = useState(false); + const [isTimeout, setIsTimeout] = useState(false); + const [qrValue, setQrValue] = useState(''); + const [minute, second, resetTimer] = useTimer(); + + useEffect(() => { + setQrValue( + JSON.stringify({ + userId, + accountId, + accountNumber, + balance, + }) + ); + }, [userId, accountId, accountNumber, balance]); + + const handleReset = () => { + setQrValue( + JSON.stringify({ + userId, + accountId, + accountNumber, + balance, + }) + ); + resetTimer(); + setIsTimeout(false); + }; + + useEffect(() => { + if (minute === '00' && second === '00') setIsTimeout(true); + }, [minute, second]); return ( -
setIsScan(!isScan)} - > -

- QR코드 -

-

- QR스캔 -

-
- {isScan ? ( - setIsScan(false)} /> - ) : ( -
+
+
- )} + {isTimeout && ( + + )} +
+ {minute}:{second} +

+ +

+
+
); }; diff --git a/src/components/organisms/Ex.tsx b/src/components/organisms/Ex.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/hooks/ex.ts b/src/hooks/ex.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/hooks/useTimer.tsx b/src/hooks/useTimer.tsx new file mode 100644 index 0000000..8b6b7b7 --- /dev/null +++ b/src/hooks/useTimer.tsx @@ -0,0 +1,24 @@ +import { useEffect, useState } from 'react'; + +export const useTimer = (initialTime: number = 60 * 1000) => { + const [time, setTime] = useState(initialTime); + const minute = String(Math.floor((time / (1000 * 60)) % 60)).padStart(2, '0'); + const second = String(Math.floor((time / 1000) % 60)).padStart(2, '0'); + + const resetTimer = () => { + setTime(initialTime); + }; + + useEffect(() => { + const timer = setInterval(() => { + setTime((prevTime) => prevTime - 1000); + }, 1000); + + if (time <= 0) { + clearInterval(timer); + } + return () => clearInterval(timer); + }, [time]); + + return [minute, second, resetTimer] as const; +}; diff --git a/src/main.tsx b/src/main.tsx index 23692db..7b30d93 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -29,7 +29,7 @@ const router = createBrowserRouter([ path: '/', element: , children: [ - { path: '/main', element: }, + { path: '/hana', element: }, { path: '/login', element: }, { path: '/qr-pay', element: }, { path: '/open-lesson/host', element: }, diff --git a/src/pages/main/HanaFunMain.tsx b/src/pages/main/HanaFunMain.tsx index 6029216..fe04394 100644 --- a/src/pages/main/HanaFunMain.tsx +++ b/src/pages/main/HanaFunMain.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { GoKebabHorizontal } from 'react-icons/go'; import DropdownSingle from '../../components/common/DropdownSingle'; import { InfoCard } from '../../components/molecules/InfoCard'; @@ -14,6 +14,9 @@ import { QR } from '../../components/molecules/QR'; import { AccountType } from '../../components/organisms/ChoiceAccount'; import { RiQrScan2Line } from 'react-icons/ri'; import { Lessondata } from '../search/LessonSearch'; +import { getCookie } from '../../utils/cookie'; +import { useNavigate } from 'react-router-dom'; +import { QRScanner } from '../../components/molecules/QRScanner'; export const userDummyData = { userId: 1, @@ -61,9 +64,11 @@ const infoCardSliderSettings = { }; export const HanaFunMain = () => { - const [showDropdown, setShowDropdown] = useState(false); + const navigate = useNavigate(); const [showKeypad, setShowKeypad] = useState(false); const [showQr, setShowQr] = useState(false); + const [isScan, setIsScan] = useState(false); + const [active, setActive] = useState(null); const [selectedAccount, setSelectedAccount] = useState({ accountId: -1, @@ -76,12 +81,12 @@ export const HanaFunMain = () => { console.log('비밀번호>>', password); console.log('로그인'); setShowKeypad(false); - setShowDropdown(false); + setActive(null); setShowQr(true); }; - const clickedAccount = (account: AccountType) => { - setShowDropdown((showDropdown) => !showDropdown); + const clickedAccount = (account: AccountType, index: number) => { + handleModalOpen(index); setSelectedAccount({ accountId: account.accountId, accountNumber: account.accountNumber, @@ -90,14 +95,38 @@ export const HanaFunMain = () => { }); }; + const handleModalOpen = (index: number) => { + setActive(active === index ? null : index); + }; + + // useEffect(() => { + // const token = getCookie('token'); + // if (!token) navigate('/hana'); + // }, []); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const target = event.target as HTMLElement; + if (!target.closest('.lesson-card') && active !== null) { + setActive(null); + } + }; + + document.addEventListener('click', handleClickOutside); + return () => { + document.removeEventListener('click', handleClickOutside); + }; + }, [active]); + return ( <> + {isScan && setIsScan(false)} />} {showKeypad && ( sendAccountPassword(pw)} onClose={() => { setShowKeypad(false); - setShowDropdown(false); + setActive(null); }} /> )} @@ -110,31 +139,28 @@ export const HanaFunMain = () => { onClose={() => setShowQr(false)} /> )} -
-
-

- logo - 하나 - F - u - n! -

+
+

{userDummyData.name}

+
- {/* */} +
- {userDummyData.accounts.map((account) => ( + {userDummyData.accounts.map((account, index) => (
-
+
{account.accountName} @@ -142,9 +168,9 @@ export const HanaFunMain = () => { color='#B5B5B5' size={16} className='rotate-90 cursor-pointer' - onClick={() => clickedAccount(account)} + onClick={() => clickedAccount(account, index)} /> - {showDropdown && ( + {active === index && (
{ {/* 하나원큐 계좌 */}
-

- {data.account_name} - 한도계좌 -

-

- 입출금 {data.account_number} - 복사 -

-

- {data.balance.toLocaleString()} - -

-
- - -
diff --git a/src/pages/openLesson/RegisterLesson.tsx b/src/pages/openLesson/RegisterLesson.tsx index af16a64..dd6a1c8 100644 --- a/src/pages/openLesson/RegisterLesson.tsx +++ b/src/pages/openLesson/RegisterLesson.tsx @@ -1,7 +1,7 @@ import { useNavigate } from 'react-router-dom'; import { Topbar } from '../../components/common/Topbar'; import { Button } from '../../components/common/Button'; -import { ChangeEvent, useRef, useState } from 'react'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; import { CompleteSend } from '../../components/organisms/CompleteSend'; import { FaCamera } from 'react-icons/fa'; import { SelectAddress } from '../../components/molecules/SelectAddress'; @@ -50,7 +50,6 @@ export const RegisterLesson = () => { const file = e.target.files[0]; const imageUrl = URL.createObjectURL(file); setUploadImageFile(imageUrl); - checkValid(); // const formData = new FormData(); // if (file) { // formData.append('file', file); @@ -62,23 +61,19 @@ export const RegisterLesson = () => { const onChangeCategory = (category: string) => { setCategory(category); setShowModal(false); - checkValid(); }; const onChangeLessonTime = (lessontime: LessonTime[]) => { setLessonTime(lessontime); - checkValid(); }; const onChangeMaterials = (materials: string) => { setMaterials(materials); - checkValid(); }; const onChangeAddress = (address: string) => { if (address && address.length !== 1) { setAddress(address); - checkValid(); } }; @@ -96,6 +91,14 @@ export const RegisterLesson = () => { setIsBtnActive(false); return; } + if (inputPrice.current && +inputPrice.current.value <= 0) { + setIsBtnActive(false); + return; + } + if (inputCapacity.current && +inputCapacity.current.value <= 0) { + setIsBtnActive(false); + return; + } setIsBtnActive(true); }; @@ -113,6 +116,10 @@ export const RegisterLesson = () => { setIsSend(true); }; + useEffect(() => { + checkValid(); + }, [uploadImageFile, category, lessonTime, materials, address]); + return ( <> {showModal && ( @@ -169,7 +176,7 @@ export const RegisterLesson = () => { type='text' title='강좌명' placeholder='강좌명을 입력해주세요.(최대 50자)' - onChange={checkValid} + onChange={() => checkValid()} ref={inputTitle} />
@@ -186,14 +193,14 @@ export const RegisterLesson = () => { type='number' title='모집인원' placeholder='모집인원을 입력해주세요.' - onChange={checkValid} + onChange={() => checkValid()} ref={inputCapacity} /> checkValid()} ref={inputPrice} /> @@ -204,7 +211,7 @@ export const RegisterLesson = () => { ref={inputDetailInfo} placeholder='상세 설명을 입력해주세요. (200자 이내)' maxLength={200} - onChange={checkValid} + onChange={() => checkValid()} className='w-full h-36 rounded-md border-[0.7px] border-hanaSilver text-xs placeholder:text-hanaSilver p-3 focus:outline-none' > diff --git a/src/pages/search/PayLesson.tsx b/src/pages/search/PayLesson.tsx index 0d01a19..abdb02c 100644 --- a/src/pages/search/PayLesson.tsx +++ b/src/pages/search/PayLesson.tsx @@ -88,8 +88,10 @@ export const PayLesson = () => { onClose={() => setShowModal(false)} >

- {state.payment}원 - 결제합니다. + + {state.payment.toLocaleString()} + + 원 결제합니다.


diff --git a/src/utils/scrollToTop.ts b/src/utils/scrollToTop.ts new file mode 100644 index 0000000..4b224ef --- /dev/null +++ b/src/utils/scrollToTop.ts @@ -0,0 +1,12 @@ +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +export const ScrollToTop = () => { + const { pathname } = useLocation(); + + useEffect(() => { + window.scrollTo(0, 0); + }, [pathname]); + + return null; +};