Skip to content

Commit

Permalink
Merge pull request #176 from boostcampwm-2024/front/main
Browse files Browse the repository at this point in the history
[FE] 브랜치 병합
  • Loading branch information
dannysir authored Nov 21, 2024
2 parents 19173b5 + f04c674 commit e05d39c
Show file tree
Hide file tree
Showing 67 changed files with 2,214 additions and 448 deletions.
44 changes: 44 additions & 0 deletions FE/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions FE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
"lottie-react": "^2.4.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.1.2",
"react-router-dom": "^6.27.0",
"socket.io-client": "^4.8.1",
"vite-tsconfig-paths": "^5.0.1",
"zustand": "^5.0.1"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@types/node": "^22.9.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
Expand Down
4 changes: 4 additions & 0 deletions FE/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import StocksDetail from 'page/StocksDetail';
import Header from 'components/Header';
import Login from 'components/Login';
import SearchModal from './components/Search';
import MyPage from 'page/MyPage';
import Rank from 'page/Rank.tsx';

function App() {
return (
Expand All @@ -18,6 +20,8 @@ function App() {
<Route path='/' element={<Layout />}>
<Route index element={<Home />} />
<Route path='stocks/:id' element={<StocksDetail />} />
<Route path='mypage' element={<MyPage />} />
<Route path='rank' element={<Rank />} />
</Route>
</Routes>
</Router>
Expand Down
2 changes: 1 addition & 1 deletion FE/src/assets/emptyAnimation.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion FE/src/assets/noResultAnimation.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion FE/src/assets/searchAnimation.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions FE/src/components/FallbackUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { FallbackProps } from 'react-error-boundary';

export default function FallbackUI({
error,
resetErrorBoundary,
}: FallbackProps) {
return (
<div>
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
28 changes: 22 additions & 6 deletions FE/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,28 @@ import useLoginModalStore from 'store/useLoginModalStore';
import useSearchModalStore from '../store/useSearchModalStore.ts';
import useSearchInputStore from '../store/useSearchInputStore.ts';
import logo from 'assets/Logo.png';
import { deleteCookie } from 'utils/common.ts';
import { checkAuth } from 'service/auth.ts';
import { useEffect } from 'react';

export default function Header() {
const { toggleModal } = useLoginModalStore();
const { isLogin, resetToken } = useAuthStore();
const { isLogin, setIsLogin } = useAuthStore();
const { toggleSearchModal } = useSearchModalStore();
const { searchInput } = useSearchInputStore();

useEffect(() => {
const check = async () => {
const res = await checkAuth();
if (res.ok) setIsLogin(true);
else setIsLogin(false);
};

check();
}, [setIsLogin]);

return (
<header className='fixed left-0 top-0 h-[60px] w-full'>
<header className='fixed left-0 top-0 h-[60px] w-full bg-white'>
<div className='mx-auto flex h-full max-w-[1280px] items-center justify-between px-8'>
<Link to={'/'} className='flex items-center gap-2'>
<img src={logo} className={'h-[32px]'} />
Expand All @@ -21,9 +34,9 @@ export default function Header() {

<div className='flex items-center gap-8'>
<nav className='flex items-center gap-6 text-sm font-bold text-juga-grayscale-500'>
<button className='px-0.5 py-2'></button>
<button className='px-0.5 py-2'>랭킹</button>
<button className='px-0.5 py-2'>마이페이지</button>
<Link to={'/'}></Link>
<Link to={'/rank'}>랭킹</Link>
<Link to={'/mypage'}>마이페이지</Link>
</nav>
<div className='relative'>
<input
Expand All @@ -39,7 +52,10 @@ export default function Header() {
{isLogin ? (
<button
className='px-4 py-2 text-sm text-juga-grayscale-500'
onClick={resetToken}
onClick={() => {
setIsLogin(false);
deleteCookie('accessToken');
}}
>
로그아웃
</button>
Expand Down
34 changes: 28 additions & 6 deletions FE/src/components/Login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function Login() {
const { isOpen, toggleModal } = useLoginModalStore();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { setAccessToken } = useAuthStore();
const { setIsLogin } = useAuthStore();
const [errorCode, setErrorCode] = useState<number>(200);

useEffect(() => {
Expand All @@ -29,10 +29,31 @@ export default function Login() {
return;
}

setAccessToken(res.accessToken);
setIsLogin(true);
toggleModal();
};

const handleKakaoBtnClick = async () => {
if (import.meta.env.DEV) {
const res = await login(
import.meta.env.VITE_TEST_ID,
import.meta.env.VITE_TEST_PW,
);

if ('error' in res) {
setErrorCode(res.statusCode);
return;
}

document.cookie = `accessToken=${res.accessToken}; path=/;`;
setIsLogin(true);
toggleModal();
return;
}

window.location.href = `${import.meta.env.VITE_API_URL}/auth/kakao`;
};

return (
<>
<Overay onClick={() => toggleModal()} />
Expand Down Expand Up @@ -67,11 +88,12 @@ export default function Login() {
로그인
</button>
</form>
<button className='flex items-center justify-center gap-2 rounded-3xl bg-yellow-300 px-3.5 py-2 transition hover:bg-yellow-400'>
<button
className='flex items-center justify-center gap-2 rounded-3xl bg-yellow-300 px-3.5 py-2 transition hover:bg-yellow-400'
onClick={handleKakaoBtnClick}
>
<ChatBubbleOvalLeftIcon className='size-5' />
<a href={`${import.meta.env.VITE_API_URL}/auth/kakao`}>
카카오 계정으로 로그인
</a>
카카오 계정으로 로그인
</button>
</section>
</>
Expand Down
23 changes: 23 additions & 0 deletions FE/src/components/Mypage/Account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useQuery } from '@tanstack/react-query';
import AccountCondition from './AccountCondition';
import MyStocksList from './MyStocksList';
import { getAssets } from 'service/assets';

export default function Account() {
const { data, isLoading, isError } = useQuery(['account', 'assets'], () =>
getAssets(),
);

if (isLoading) return <div>loading</div>;
if (!data) return <div>No data</div>;
if (isError) return <div>error</div>;

const { asset, stocks } = data;

return (
<div className='flex flex-col gap-3'>
<AccountCondition asset={asset} />
<MyStocksList stocks={stocks} />
</div>
);
}
61 changes: 61 additions & 0 deletions FE/src/components/Mypage/AccountCondition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Asset } from 'types';
import { stringToLocaleString } from 'utils/common';

type AccountConditionProps = {
asset: Asset;
};

export default function AccountCondition({ asset }: AccountConditionProps) {
const {
cash_balance,
stock_balance,
total_asset,
total_profit,
total_profit_rate,
is_positive,
} = asset;

return (
<div className='flex flex-wrap gap-4 p-3 text-base font-semibold shadow-sm rounded-xl bg-gray-50'>
<div className='flex min-w-[250px] flex-1 flex-col rounded-lg bg-white p-6 shadow-sm'>
<h3 className='mb-4 text-xl text-center text-gray-700'>자산 현황</h3>
<div className='flex justify-between mb-6'>
<p className='text-juga-grayscale-500'>총 자산</p>
<p className='text-gray-900'>{stringToLocaleString(total_asset)}</p>
</div>
<div className='flex justify-between mb-2'>
<p className='text-juga-grayscale-500'>가용 자산</p>
<p className='text-gray-900'>
{stringToLocaleString(cash_balance)}
</p>
</div>
<div className='flex justify-between'>
<p className='text-juga-grayscale-500'>주식 자산</p>
<p className='text-gray-900'>
{stringToLocaleString(stock_balance)}
</p>
</div>
</div>

<div className='flex min-w-[250px] flex-1 flex-col rounded-lg bg-white p-6 shadow-sm'>
<h3 className='mb-4 text-xl text-center text-gray-700'>투자 성과</h3>
<div className='flex justify-between mb-6'>
<p className='text-juga-grayscale-500'>투자 손익</p>
<p
className={`${is_positive ? 'text-juga-blue-50' : 'text-juga-red-60'}`}
>
{stringToLocaleString(total_profit)}
</p>
</div>
<div className='flex justify-between'>
<p className='text-juga-grayscale-500'>수익률</p>
<p
className={`${is_positive ? 'text-juga-blue-50' : 'text-juga-red-60'}`}
>
{total_profit_rate}%
</p>
</div>
</div>
</div>
);
}
35 changes: 35 additions & 0 deletions FE/src/components/Mypage/MyStocksList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MyStockListUnit } from 'types';

type MyStocksListProps = {
stocks: MyStockListUnit[];
};

export default function MyStocksList({ stocks }: MyStocksListProps) {
return (
<div className='flex flex-col w-full p-4 mx-auto bg-white rounded-md shadow-md'>
<div className='flex pb-2 text-sm font-bold border-b'>
<p className='w-1/2 text-left truncate'>종목</p>
<p className='w-1/4 text-center'>보유 수량</p>
<p className='w-1/4 text-right'>평균 가격</p>
</div>

<ul className='flex flex-col text-sm divide-y min-h-48'>
{stocks.map((stock) => {
const { code, name, avg_price, quantity } = stock;
return (
<li className='flex py-2' key={code}>
<div className='flex w-1/2 gap-2 text-left truncate'>
<p className='font-semibold'>{name}</p>
<p className='text-gray-500'>{code}</p>
</div>
<p className='w-1/4 text-center'>{quantity}</p>
<p className='w-1/4 text-right truncate'>
{Math.floor(avg_price).toLocaleString()}
</p>
</li>
);
})}
</ul>
</div>
);
}
Loading

0 comments on commit e05d39c

Please sign in to comment.