Skip to content

Commit

Permalink
Feature/#182 - 메인페이지 데이터 패칭 수정, 마이페이지 구현 (#289)
Browse files Browse the repository at this point in the history
  • Loading branch information
baegyeong authored Nov 28, 2024
2 parents ef8789f + 78f33ae commit 301a108
Show file tree
Hide file tree
Showing 22 changed files with 399 additions and 135 deletions.
40 changes: 40 additions & 0 deletions packages/frontend/src/apis/queries/auth/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,43 @@ export const GetTestLoginSchema = z.object({
});

export type GetTestLogin = z.infer<typeof GetTestLoginSchema>;

export const GetUserInfoSchema = z.object({
nickname: z.string(),
subName: z.string(),
createdAt: z.string().datetime(),
email: z.string(),
type: z.string(),
});

export type GetUserInfo = z.infer<typeof GetUserInfoSchema>;

export const PostUserNicknameSchema = z.object({
message: z.string(),
date: z.string().datetime(),
});

export type PostUserNickname = z.infer<typeof PostUserNicknameSchema>;

export const PostLogoutSchema = z.object({
message: z.string(),
});

export type PostLogout = z.infer<typeof PostLogoutSchema>;

export const GetUserStockSchema = z.object({
id: z.number(),
stockId: z.string(),
name: z.string(),
isTrading: z.boolean(),
groupCode: z.string(),
createdAt: z.string().datetime(),
});

export type GetUserStock = z.infer<typeof GetUserStockSchema>;

export const GetUserStockResponseSchema = z.object({
userStocks: z.array(GetUserStockSchema),
});

export type GetUserStockResponse = z.infer<typeof GetUserStockResponseSchema>;
16 changes: 16 additions & 0 deletions packages/frontend/src/apis/queries/auth/useGetUserInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useQuery } from '@tanstack/react-query';
import { GetUserInfoSchema, type GetUserInfo } from './schema';
import { get } from '@/apis/utils/get';

const getUserInfo = () =>
get<GetUserInfo>({
schema: GetUserInfoSchema,
url: '/api/user/info',
});

export const useGetUserInfo = () => {
return useQuery({
queryKey: ['userInfo'],
queryFn: getUserInfo,
});
};
19 changes: 19 additions & 0 deletions packages/frontend/src/apis/queries/auth/useGetUserStock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useQuery } from '@tanstack/react-query';
import {
GetUserStockResponseSchema,
type GetUserStockResponse,
} from './schema';
import { get } from '@/apis/utils/get';

const getUserStock = () =>
get<GetUserStockResponse>({
schema: GetUserStockResponseSchema,
url: '/api/stock/user',
});

export const useGetUserStock = () => {
return useQuery({
queryKey: ['userStock'],
queryFn: getUserStock,
});
};
16 changes: 16 additions & 0 deletions packages/frontend/src/apis/queries/auth/usePostLogout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useMutation } from '@tanstack/react-query';
import { PostLogout, PostLogoutSchema } from './schema';
import { post } from '@/apis/utils/post';

const postLogout = () =>
post<PostLogout>({
schema: PostLogoutSchema,
url: '/api/auth/logout',
});

export const usePostLogout = () => {
return useMutation({
mutationKey: ['logout'],
mutationFn: postLogout,
});
};
22 changes: 22 additions & 0 deletions packages/frontend/src/apis/queries/auth/usePostUserNickname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { PostUserNickname, PostUserNicknameSchema } from './schema';
import { post } from '@/apis/utils/post';

const postUserNickname = ({ nickname }: { nickname: string }) =>
post<PostUserNickname>({
params: { nickname },
schema: PostUserNicknameSchema,
url: '/api/user/info',
});

export const usePostUserNickname = ({ nickname }: { nickname: string }) => {
const queryClient = useQueryClient();

return useMutation({
mutationKey: ['userNickname'],
mutationFn: () => postUserNickname({ nickname }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['userInfo'] });
},
});
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMutation } from '@tanstack/react-query';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import {
DeleteStockUserSchema,
type DeleteStockUserRequest,
Expand All @@ -13,7 +13,9 @@ const deleteStockUser = ({ stockId }: DeleteStockUserRequest) =>
data: { stockId },
});

export const useDeleteStockUser = ({ ...options }) => {
export const useDeleteStockUser = (
options?: UseMutationOptions<DeleteStockUser, Error, DeleteStockUserRequest>,
) => {
return useMutation({
mutationKey: ['deleteStockUser'],
mutationFn: ({ stockId }: DeleteStockUserRequest) =>
Expand Down
13 changes: 13 additions & 0 deletions packages/frontend/src/apis/queries/stocks/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,16 @@ export const SearchResultsResponseSchema = z.object({
});

export type SearchResultsResponse = z.infer<typeof SearchResultsResponseSchema>;

export const StockIndexSchema = z.object({
name: z.string(),
currentPrice: z.string(),
changeRate: z.string(),
volume: z.number(),
high: z.string(),
low: z.string(),
open: z.string(),
updatedAt: z.string().datetime(),
});

export type StockIndexResponse = z.infer<typeof StockIndexSchema>;
17 changes: 17 additions & 0 deletions packages/frontend/src/apis/queries/stocks/useGetStockIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useQuery } from '@tanstack/react-query';
import { z } from 'zod';
import { StockIndexSchema, type StockIndexResponse } from './schema';
import { get } from '@/apis/utils/get';

const getStockIndex = () =>
get<StockIndexResponse[]>({
schema: z.array(StockIndexSchema),
url: `/api/stock/index`,
});

export const useGetStockIndex = () => {
return useQuery({
queryKey: ['stockIndex'],
queryFn: getStockIndex,
});
};
5 changes: 3 additions & 2 deletions packages/frontend/src/apis/queries/stocks/useGetTopViews.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { z } from 'zod';
import {
GetStockListResponseSchema,
GetStockTopViewsResponse,
Expand All @@ -7,8 +8,8 @@ import {
import { get } from '@/apis/utils/get';

const getTopViews = ({ limit }: Partial<GetStockListRequest>) =>
get<GetStockTopViewsResponse[]>({
schema: GetStockListResponseSchema,
get<Partial<GetStockTopViewsResponse>[]>({
schema: z.array(GetStockListResponseSchema.partial()),
url: `/api/stock/topViews`,
params: { limit },
});
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/apis/utils/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { instance } from '../config';
import { formatZodError } from './formatZodError';

interface PostParams {
params: AxiosRequestConfig['params'];
params?: AxiosRequestConfig['params'];
schema: z.ZodType;
url: string;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/layouts/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const Layout = () => {
<div className="flex min-h-screen">
<Sidebar />
<main className="ml-20 flex-1">
<div className="h-full overflow-auto px-16 py-16 md:px-32 lg:px-48">
<div className="h-full overflow-auto px-16 py-16 md:px-24 lg:px-28 xl:px-44">
<Outlet />
</div>
</main>
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/components/layouts/search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const Search = ({ className }: SearchProps) => {
<Input
placeholder="검색어"
onChange={(e) => setStockName(e.target.value)}
autoFocus
/>
<Button type="submit" size="sm">
검색
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/pages/login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const Login = () => {

export const LoginButton = ({ to, src, alt, onClick }: LoginButtonProps) => {
return (
<Link to={to} className="w-72" onClick={onClick}>
<Link to={to} className="w-72" onClick={onClick} reloadDocument>
<img src={src} alt={alt} />
</Link>
);
Expand Down
5 changes: 3 additions & 2 deletions packages/frontend/src/pages/my-page/MyPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Link } from 'react-router-dom';
import { AlarmInfo } from './AlarmInfo';
import { StockInfo } from './StockInfo';
import { UserInfo } from './UserInfo';
import { useGetLoginStatus } from '@/apis/queries/auth';

export const MyPage = () => {
Expand All @@ -13,9 +14,9 @@ export const MyPage = () => {
<h1 className="display-bold24 mb-16">마이페이지</h1>
<article className="grid h-[40rem] grid-cols-[1.5fr_2.5fr] gap-5">
<section className="grid grid-rows-[1fr_2fr] gap-5">
<section className="display-bold20 rounded-md bg-white p-7">
<section className="rounded-md bg-white p-7">
{loginStatus?.message === 'Authenticated' ? (
'내정보'
<UserInfo />
) : (
<Link
to="/login"
Expand Down
38 changes: 37 additions & 1 deletion packages/frontend/src/pages/my-page/StockInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,51 @@
import { useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import { GetLoginStatus } from '@/apis/queries/auth/schema';
import { useGetUserStock } from '@/apis/queries/auth/useGetUserStock';
import { useDeleteStockUser } from '@/apis/queries/stock-detail';
import { Button } from '@/components/ui/button';

interface StockInfoProps {
loginStatus: GetLoginStatus;
}

export const StockInfo = ({ loginStatus }: StockInfoProps) => {
const queryClient = useQueryClient();
const navigate = useNavigate();

const { data } = useGetUserStock();
const { mutate } = useDeleteStockUser({
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['userStock'] }),
});

const { userStocks } = data || {};

return (
<section className="display-bold20 flex flex-col gap-5 rounded-md bg-white p-7">
<h2>주식 정보</h2>
{loginStatus?.message === 'Authenticated' ? (
<></>
userStocks?.length === 0 ? (
<p className="display-medium14 text-dark-gray">
소유한 주식이 없습니다.
</p>
) : (
<article className="grid grid-cols-2 gap-5">
{data?.userStocks.map((stock) => (
<section
className="display-bold14 text-dark-gray flex cursor-pointer justify-between rounded bg-[#FAFAFA] p-10 transition-all duration-300 hover:scale-105"
onClick={() => navigate(`/stocks/${stock.stockId}`)}
>
<p>{stock.name}</p>
<Button
size="sm"
onClick={() => mutate({ stockId: stock.stockId })}
>
삭제
</Button>
</section>
))}
</article>
)
) : (
<p className="display-medium14 text-dark-gray">
로그인 후 이용 가능합니다.
Expand Down
70 changes: 70 additions & 0 deletions packages/frontend/src/pages/my-page/UserInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useState } from 'react';
import { useGetUserInfo } from '@/apis/queries/auth/useGetUserInfo';
import { usePostLogout } from '@/apis/queries/auth/usePostLogout';
import { usePostUserNickname } from '@/apis/queries/auth/usePostUserNickname';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';

export const UserInfo = () => {
const [isEdit, setIsEdit] = useState(false);
const [newNickname, setNewNickname] = useState('');

const { data } = useGetUserInfo();
const { email, nickname } = data || {};

const { mutate: editNickname } = usePostUserNickname({
nickname: newNickname,
});
const { mutate: logout } = usePostLogout();

return (
<div className="flex flex-col gap-5">
<section className="flex justify-between">
<div className="display-medium14 text-gray flex gap-3">
<h2 className="display-bold20 text-black">내정보</h2>
{isEdit ? (
<>
<button
onClick={() => {
editNickname();
setIsEdit(false);
}}
>
완료
</button>
<button onClick={() => setIsEdit(false)}>취소</button>
</>
) : (
<button onClick={() => setIsEdit(true)}>수정</button>
)}
</div>
<Button
onClick={() => {
logout();
location.reload();
}}
>
로그아웃
</Button>
</section>
<section className="flex flex-col gap-3 [&>div]:flex [&>div]:gap-4">
<div>
<span className="text-gray">이메일</span>
<span className="text-dark-gray">{email}</span>
</div>
<div>
<span className="text-gray">닉네임</span>
{isEdit ? (
<Input
defaultValue={nickname}
autoFocus
onChange={(e) => setNewNickname(e.target.value)}
/>
) : (
<span className="text-dark-gray">{nickname}</span>
)}
</div>
</section>
</div>
);
};
Loading

0 comments on commit 301a108

Please sign in to comment.