Skip to content

Commit

Permalink
Merge pull request #167 from boostcampwm-2024/feature/mypage
Browse files Browse the repository at this point in the history
[FE] mypage 페이지 구현
  • Loading branch information
dannysir authored Nov 20, 2024
2 parents 1d76404 + ebf5107 commit 725566e
Show file tree
Hide file tree
Showing 15 changed files with 271 additions and 13 deletions.
28 changes: 28 additions & 0 deletions FE/package-lock.json

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

1 change: 1 addition & 0 deletions FE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"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",
Expand Down
4 changes: 3 additions & 1 deletion FE/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import StocksDetail from 'page/StocksDetail';
import Header from 'components/Header';
import Login from 'components/Login';
import SearchModal from './components/Search';
import Rank from './page/Rank.tsx';
import MyPage from 'page/MyPage';
import Rank from 'page/Rank.tsx';

function App() {
return (
Expand All @@ -19,6 +20,7 @@ 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>
Expand Down
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>
);
}
13 changes: 5 additions & 8 deletions FE/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function Header() {
const { searchInput } = useSearchInputStore();

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,13 +21,10 @@ 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'>
<Link to={'/'}>
<button className='px-0.5 py-2'></button>
</Link>
<Link to={'/rank'}>
<button className='px-0.5 py-2'>랭킹</button>
</Link>
<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 Down
1 change: 1 addition & 0 deletions FE/src/components/Login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default function Login() {
}

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

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>
);
}
52 changes: 52 additions & 0 deletions FE/src/components/Mypage/AccountCondition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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,
} = 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-gray-600'>총 자산</p>
<p className='text-gray-900'>{stringToLocaleString(total_asset)}</p>
</div>
<div className='flex justify-between mb-2'>
<p className='text-gray-600'>가용 자산</p>
<p className='text-gray-900'>
{stringToLocaleString(cash_balance)}
</p>
</div>
<div className='flex justify-between'>
<p className='text-gray-600'>주식 자산</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-gray-600'>투자 손익</p>
<p className='text-red-600'>{stringToLocaleString(total_profit)}</p>
</div>
<div className='flex justify-between'>
<p className='text-gray-600'>수익률</p>
<p className='text-red-600'>{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>
);
}
31 changes: 31 additions & 0 deletions FE/src/components/Mypage/Nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useSearchParams } from 'react-router-dom';
import { MypageSectionType } from 'types';

const mapping = {
account: '보유 자산 현황',
info: '내 정보',
};
const sections: MypageSectionType[] = ['account', 'info'];

export default function Nav() {
const [searchParams, setSearchParams] = useSearchParams();
const currentSection = searchParams.get('section') || 'account';

const handleClick = (section: MypageSectionType) => {
setSearchParams({ section });
};

return (
<div className='flex flex-col w-48 rounded-lg h-fit'>
{sections.map((e, idx) => (
<button
key={`assetNav${idx}`}
onClick={() => handleClick(e)}
className={`h-20 rounded-xl font-semibold ${currentSection === e ? 'bg-gray-100' : 'transition hover:bg-gray-50'}`}
>
{mapping[e]}
</button>
))}
</div>
);
}
24 changes: 21 additions & 3 deletions FE/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,32 @@ import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.tsx';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
QueryClient,
QueryClientProvider,
QueryErrorResetBoundary,
} from '@tanstack/react-query';
import { ErrorBoundary } from 'react-error-boundary';
import FallbackUI from 'components/FallbackUI.tsx';

const queryClient = new QueryClient();
const queryClient = new QueryClient({
defaultOptions: {
queries: {
useErrorBoundary: true,
},
},
});

createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary onReset={reset} FallbackComponent={FallbackUI}>
<App />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
</QueryClientProvider>
</StrictMode>,
);
17 changes: 17 additions & 0 deletions FE/src/page/MyPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Account from 'components/Mypage/Account';
import Nav from 'components/Mypage/Nav';
import { useSearchParams } from 'react-router-dom';

export default function MyPage() {
const [searchParams] = useSearchParams();
const currentPage = searchParams.get('section') || 'account';

return (
<div className='flex gap-5'>
<Nav />
<div className='flex-1'>
{{ account: <Account />, info: <div>info</div> }[currentPage]}
</div>
</div>
);
}
14 changes: 14 additions & 0 deletions FE/src/service/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AssetsResponse } from 'types';

export async function getAssets(): Promise<AssetsResponse> {
const url = import.meta.env.PROD
? `${import.meta.env.VITE_API_URL}/assets`
: '/api/assets';

return fetch(url, {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res.json());
}
24 changes: 23 additions & 1 deletion FE/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,32 @@ export type StockChartUnit = {
prdy_vrss_sign: string;
};


export type MypageSectionType = 'account' | 'info';

export type Asset = {
cash_balance: string;
stock_balance: string;
total_asset: string;
total_profit: string;
total_profit_rate: string;
};

export type MyStockListUnit = {
avg_price: number;
code: string;
name: string;
quantity: number;
};

export type AssetsResponse = {
asset: Asset;
stocks: MyStockListUnit[];
};
export type ChartSizeConfigType = {
upperHeight: number;
lowerHeight: number;
chartWidth: number;
yAxisWidth: number;
xAxisHeight: number;
};
};
3 changes: 3 additions & 0 deletions FE/src/utils/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function stringToLocaleString(s: string) {
return (+s).toLocaleString();
}

0 comments on commit 725566e

Please sign in to comment.