diff --git a/packages/frontend/src/apis/queries/alarm/useGetAlarm.ts b/packages/frontend/src/apis/queries/alarm/useGetAlarm.ts index 9ddc79e0..0d45652d 100644 --- a/packages/frontend/src/apis/queries/alarm/useGetAlarm.ts +++ b/packages/frontend/src/apis/queries/alarm/useGetAlarm.ts @@ -13,5 +13,6 @@ export const useGetAlarm = ({ isLoggedIn }: { isLoggedIn: boolean }) => { queryKey: ['getAlarm'], queryFn: getAlarm, enabled: isLoggedIn, + staleTime: 1000 * 60 * 5, }); }; diff --git a/packages/frontend/src/apis/queries/alarm/usePostCreateAlarm.ts b/packages/frontend/src/apis/queries/alarm/usePostCreateAlarm.ts index 7e1fa8e3..260cd5f2 100644 --- a/packages/frontend/src/apis/queries/alarm/usePostCreateAlarm.ts +++ b/packages/frontend/src/apis/queries/alarm/usePostCreateAlarm.ts @@ -30,6 +30,8 @@ export const usePostCreateAlarm = () => { }: PostCreateAlarmRequest) => postCreateAlarm({ stockId, targetPrice, targetVolume, alarmExpiredDate }), onSuccess: () => - queryClient.invalidateQueries({ queryKey: ['getStockAlarm'] }), + queryClient.invalidateQueries({ + queryKey: ['getStockAlarm', 'getAlarm'], + }), }); }; diff --git a/packages/frontend/src/apis/queries/auth/usePostLogout.ts b/packages/frontend/src/apis/queries/auth/usePostLogout.ts index 3278bf64..2bbf6f55 100644 --- a/packages/frontend/src/apis/queries/auth/usePostLogout.ts +++ b/packages/frontend/src/apis/queries/auth/usePostLogout.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { PostLogout, PostLogoutSchema } from './schema'; import { post } from '@/apis/utils/post'; @@ -9,8 +9,12 @@ const postLogout = () => }); export const usePostLogout = () => { + const queryClient = useQueryClient(); + return useMutation({ mutationKey: ['logout'], mutationFn: postLogout, + onSuccess: () => + queryClient.invalidateQueries({ queryKey: ['loginStatus'] }), }); }; diff --git a/packages/frontend/src/apis/queries/chat/useGetChatList.ts b/packages/frontend/src/apis/queries/chat/useGetChatList.ts index 5c0817b4..355c02cc 100644 --- a/packages/frontend/src/apis/queries/chat/useGetChatList.ts +++ b/packages/frontend/src/apis/queries/chat/useGetChatList.ts @@ -46,6 +46,6 @@ export const useGetChatList = ({ pages: [...data.pages].flatMap((page) => page.chats), pageParams: [...data.pageParams], }), - staleTime: 1000 * 60 * 5, + staleTime: 1000 * 60 * 3, }); }; diff --git a/packages/frontend/src/apis/queries/stock-detail/useGetStockDetail.ts b/packages/frontend/src/apis/queries/stock-detail/useGetStockDetail.ts index fb3ed3b9..e8ed24c5 100644 --- a/packages/frontend/src/apis/queries/stock-detail/useGetStockDetail.ts +++ b/packages/frontend/src/apis/queries/stock-detail/useGetStockDetail.ts @@ -16,5 +16,6 @@ export const useGetStockDetail = ({ stockId }: GetStockRequest) => { return useSuspenseQuery({ queryKey: ['stockDetail', stockId], queryFn: () => getStockDetail({ stockId }), + staleTime: 1000 * 60 * 5, }); }; diff --git a/packages/frontend/src/apis/queries/stock-detail/useGetStockOwnership.ts b/packages/frontend/src/apis/queries/stock-detail/useGetStockOwnership.ts index 6322290a..3846e2ab 100644 --- a/packages/frontend/src/apis/queries/stock-detail/useGetStockOwnership.ts +++ b/packages/frontend/src/apis/queries/stock-detail/useGetStockOwnership.ts @@ -18,5 +18,6 @@ export const useGetOwnership = ({ stockId }: GetStockRequest) => { queryKey: ['stockOwnership', stockId], queryFn: () => getOwnership({ stockId }), enabled: !!stockId, + staleTime: 1000 * 60 * 5, }); }; diff --git a/packages/frontend/src/apis/queries/stock-detail/usePostStockView.ts b/packages/frontend/src/apis/queries/stock-detail/usePostStockView.ts index 27dd2312..44fc03d7 100644 --- a/packages/frontend/src/apis/queries/stock-detail/usePostStockView.ts +++ b/packages/frontend/src/apis/queries/stock-detail/usePostStockView.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { PostStockResponseSchema, type PostStockRequest, @@ -14,8 +14,11 @@ const postStockView = ({ stockId }: PostStockRequest) => }); export const usePostStockView = () => { + const queryClient = useQueryClient(); + return useMutation({ mutationKey: ['stockView'], mutationFn: ({ stockId }: PostStockRequest) => postStockView({ stockId }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['topViews'] }), }); }; diff --git a/packages/frontend/src/apis/queries/stocks/useGetStocksByPrice.ts b/packages/frontend/src/apis/queries/stocks/useGetStocksByPrice.ts index 428c54fc..7fadb44d 100644 --- a/packages/frontend/src/apis/queries/stocks/useGetStocksByPrice.ts +++ b/packages/frontend/src/apis/queries/stocks/useGetStocksByPrice.ts @@ -1,4 +1,4 @@ -import { useSuspenseQuery } from '@tanstack/react-query'; +import { keepPreviousData, useQuery } from '@tanstack/react-query'; import { GetStockListResponseSchema, type GetStockListRequest, @@ -14,8 +14,9 @@ const getStockByPrice = ({ limit, type }: GetStockListRequest) => }); export const useGetStocksByPrice = ({ limit, type }: GetStockListRequest) => { - return useSuspenseQuery({ + return useQuery({ queryKey: ['stocks', limit, type], queryFn: () => getStockByPrice({ limit, type }), + placeholderData: keepPreviousData, }); }; diff --git a/packages/frontend/src/apis/queries/stocks/useGetStocksPriceSeries.ts b/packages/frontend/src/apis/queries/stocks/useGetStocksPriceSeries.ts index 3d9c85b4..eb6cef05 100644 --- a/packages/frontend/src/apis/queries/stocks/useGetStocksPriceSeries.ts +++ b/packages/frontend/src/apis/queries/stocks/useGetStocksPriceSeries.ts @@ -1,4 +1,4 @@ -import { useInfiniteQuery } from '@tanstack/react-query'; +import { keepPreviousData, useInfiniteQuery } from '@tanstack/react-query'; import { StockTimeSeriesResponseSchema, type StockTimeSeriesRequest, @@ -49,6 +49,7 @@ export const useGetStocksPriceSeries = ({ .flatMap((page) => page.volumeDtoList), }), refetchOnWindowFocus: false, - staleTime: 5 * 60 * 1000, + staleTime: 10 * 1000, + placeholderData: keepPreviousData, }); }; diff --git a/packages/frontend/src/apis/queries/user/useGetUserInfo.ts b/packages/frontend/src/apis/queries/user/useGetUserInfo.ts index efc97f89..be12533e 100644 --- a/packages/frontend/src/apis/queries/user/useGetUserInfo.ts +++ b/packages/frontend/src/apis/queries/user/useGetUserInfo.ts @@ -12,5 +12,6 @@ export const useGetUserInfo = () => { return useSuspenseQuery({ queryKey: ['userInfo'], queryFn: getUserInfo, + staleTime: 1000 * 60 * 5, }); }; diff --git a/packages/frontend/src/apis/queries/user/useGetUserStock.ts b/packages/frontend/src/apis/queries/user/useGetUserStock.ts index f81fce97..bfb1e241 100644 --- a/packages/frontend/src/apis/queries/user/useGetUserStock.ts +++ b/packages/frontend/src/apis/queries/user/useGetUserStock.ts @@ -15,5 +15,6 @@ export const useGetUserStock = () => { return useSuspenseQuery({ queryKey: ['userStock'], queryFn: getUserStock, + staleTime: 1000 * 60 * 3, }); }; diff --git a/packages/frontend/src/components/lottie/skeleton.json b/packages/frontend/src/components/lottie/skeleton.json new file mode 100644 index 00000000..e7021b1f --- /dev/null +++ b/packages/frontend/src/components/lottie/skeleton.json @@ -0,0 +1 @@ +{"v":"5.6.5","fr":25,"ip":0,"op":38,"w":828,"h":460,"nm":"Card Cuenta Skeleton","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Icon/icIsotipo-Santander","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[90,90,0],"ix":2},"a":{"a":0,"k":[54,54,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":108,"h":108,"ip":0,"op":5750,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"bg","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,186,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":25,"nm":"Sombra paralela","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Color de la sombra","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106],"ix":1}},{"ty":0,"nm":"Opacidad","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2,"ix":2}},{"ty":0,"nm":"Dirección","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":108.435,"ix":3}},{"ty":0,"nm":"Distancia","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":9.487,"ix":4}},{"ty":0,"nm":"Suavizado","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":18,"ix":5}},{"ty":7,"nm":"Sólo sombra","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0,"ix":6}}]}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[250,124],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":22,"ix":4},"nm":"Trazado de rectángulo 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,0.019999999553],"ix":3},"o":{"a":0,"k":2,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Trazo 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.972549021244,0.972549021244,0.972549021244,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"bg","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":5750,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Card-HOME/Cuenta/Enable","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,186,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[250,124],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Trazado de rectángulo 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Card-HOME/Cuenta/Enable","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":5750,"st":0,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Icon/Isotipo-Santander","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[54,54,0],"ix":2},"a":{"a":0,"k":[39,39,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":78,"h":78,"ip":0,"op":5750,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Rectangle 854","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[54,54,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[36,36],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Trazado de rectángulo 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.768627464771,0.768627464771,0.768627464771,0],"ix":4},"o":{"a":0,"k":0,"ix":5},"r":1,"bm":0,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Rectangle 854","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":5750,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Icon/icIsotipo-Santander","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[54,54,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[36,36],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Trazado de rectángulo 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Icon/icIsotipo-Santander","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":5750,"st":0,"bm":0}]},{"id":"comp_2","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Icon/Isotipo-Santander","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39,39,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,26],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Trazado de rectángulo 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Icon/Isotipo-Santander","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":5750,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Rectangle 873","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":5,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[72,243.5,0],"ix":2},"a":{"a":0,"k":[-318,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":6,"s":[0,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":14,"s":[105,105,100]},{"t":16.5263671875,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[212,33],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Trazado de rectángulo 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929411768913,0.929411768913,0.929411768913,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Rectangle 873","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":2,"op":50,"st":2,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Rectangle 881","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":9,"s":[0]},{"t":19,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[81,338,0],"ix":2},"a":{"a":0,"k":[-153,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[0,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":18,"s":[105,105,100]},{"t":20.5263671875,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[102,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Trazado de rectángulo 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929411768913,0.929411768913,0.929411768913,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Rectangle 881","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":6,"op":50,"st":6,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Rectangle 880","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":3,"s":[0]},{"t":13,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,125,0],"ix":2},"a":{"a":0,"k":[-153,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":4,"s":[0,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":12,"s":[105,105,100]},{"t":14.5263671875,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[102,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Trazado de rectángulo 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929411768913,0.929411768913,0.929411768913,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Rectangle 880","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":50,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Card-HOME/Cuenta/Enable","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[411,233,0],"ix":2},"a":{"a":0,"k":[375,186,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":750,"h":372,"ip":0,"op":5750,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/packages/frontend/src/components/ui/alarm/Alarm.tsx b/packages/frontend/src/components/ui/alarm/Alarm.tsx index 339c009e..090c4d9e 100644 --- a/packages/frontend/src/components/ui/alarm/Alarm.tsx +++ b/packages/frontend/src/components/ui/alarm/Alarm.tsx @@ -3,7 +3,7 @@ import Flag from '@/assets/flag.svg?react'; export interface AlarmProps { option: string; - goalPrice: number | string; + goalPrice: number; alarmDate: string | null; } diff --git a/packages/frontend/src/contexts/theme/themeProvider.tsx b/packages/frontend/src/contexts/theme/themeProvider.tsx index 699f178d..90485878 100644 --- a/packages/frontend/src/contexts/theme/themeProvider.tsx +++ b/packages/frontend/src/contexts/theme/themeProvider.tsx @@ -10,13 +10,16 @@ import { export const ThemeProvider = () => { const { isLoggedIn } = useContext(LoginContext); - const { data: userTheme } = useGetUserTheme(); + const { data: userTheme, isLoading } = useGetUserTheme(); const { mutate: updateTheme } = usePatchUserTheme(); - const initialTheme = isLoggedIn ? userTheme : localStorage.getItem('theme'); - const [theme, setTheme] = useState<GetUserTheme['theme']>( - initialTheme as GetUserTheme['theme'], - ); + const [theme, setTheme] = useState<GetUserTheme['theme']>(() => { + if (!isLoading && isLoggedIn && userTheme) { + return userTheme; + } + const localTheme = localStorage.getItem('theme'); + return (localTheme as GetUserTheme['theme']) || 'light'; + }); document.body.classList.toggle('dark', theme === 'dark'); diff --git a/packages/frontend/src/pages/login/Login.tsx b/packages/frontend/src/pages/login/Login.tsx index a2e515ea..61ca861f 100644 --- a/packages/frontend/src/pages/login/Login.tsx +++ b/packages/frontend/src/pages/login/Login.tsx @@ -1,10 +1,12 @@ +import { useQueryClient } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import { useGetTestLogin } from '@/apis/queries/auth/useGetTestLogin'; import Google from '@/assets/google.svg?react'; import { Button } from '@/components/ui/button'; +const GOOGLE_LOGIN = '/api/auth/google/login'; export const Login = () => { - const googleLoginUrl = '/api/auth/google/login'; + const queryClient = useQueryClient(); const { refetch } = useGetTestLogin({ password: 'test', username: 'test' }); return ( @@ -16,7 +18,7 @@ export const Login = () => { <p className="display-medium20">주춤주춤과 함께해요!</p> </section> <section className="relative z-10 flex flex-col gap-4"> - <Link to={googleLoginUrl} className="w-72" reloadDocument> + <Link to={GOOGLE_LOGIN} className="w-72" reloadDocument> <Button className="flex h-10 w-full items-center justify-center gap-4 px-10 dark:bg-black"> <Google /> <span>구글 로그인</span> @@ -24,7 +26,10 @@ export const Login = () => { </Link> <Link to="/"> <Button - onClick={() => refetch()} + onClick={() => { + refetch(); + queryClient.invalidateQueries({ queryKey: ['loginStatus'] }); + }} className="h-10 w-full dark:bg-black" > 게스트로 로그인 diff --git a/packages/frontend/src/pages/my-page/StockInfo.tsx b/packages/frontend/src/pages/my-page/StockInfo.tsx index 8303fed6..cd3db406 100644 --- a/packages/frontend/src/pages/my-page/StockInfo.tsx +++ b/packages/frontend/src/pages/my-page/StockInfo.tsx @@ -8,7 +8,7 @@ import { LoginContext } from '@/contexts/login'; export const StockInfo = () => { return ( - <section className="display-bold20 flex max-h-[43.3rem] flex-col gap-5 rounded-md bg-white p-7"> + <section className="display-bold20 flex h-full max-h-[43.4rem] flex-col gap-5 rounded-md bg-white p-7"> <h2>주식 정보</h2> <StockInfoContents /> </section> @@ -21,7 +21,14 @@ const StockInfoContents = () => { const { data } = useGetUserStock(); const { mutate } = useDeleteStockUser({ - onSuccess: () => queryClient.invalidateQueries({ queryKey: ['userStock'] }), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ['userStock'], + }); + queryClient.invalidateQueries({ + queryKey: ['stockOwnership'], + }); + }, }); const { isLoggedIn } = useContext(LoginContext); @@ -47,7 +54,7 @@ const StockInfoContents = () => { <article className="grid gap-5 overflow-auto pr-5 xl:grid-cols-2"> {data?.userStocks.map((stock) => ( <section - className="display-bold14 text-dark-gray bg-extra-light-gray flex cursor-pointer items-center justify-between rounded px-4 py-2 transition-all duration-300 hover:scale-105 xl:p-8" + className="display-bold14 text-dark-gray bg-extra-light-gray flex cursor-pointer items-center justify-between rounded px-4 py-2 xl:p-8" onClick={() => navigate(`/stocks/${stock.stockId}`)} > <p>{stock.name}</p> diff --git a/packages/frontend/src/pages/stock-detail/NotificationPanel.tsx b/packages/frontend/src/pages/stock-detail/NotificationPanel.tsx index 4a374142..841116fc 100644 --- a/packages/frontend/src/pages/stock-detail/NotificationPanel.tsx +++ b/packages/frontend/src/pages/stock-detail/NotificationPanel.tsx @@ -36,6 +36,10 @@ const NotificationContents = () => { } if (!data) { + return <p className="text-center">알림 정보를 불러오는 데 실패했어요.</p>; + } + + if (data.length === 0) { return <p className="text-center">현재 설정된 알림이 없어요.</p>; } @@ -43,7 +47,7 @@ const NotificationContents = () => { <Alarm key={alarm.alarmId} option={alarm.targetPrice ? '목표가' : '거래가'} - goalPrice={alarm.targetPrice ?? alarm.targetVolume!} + goalPrice={alarm.targetPrice ?? alarm.targetVolume ?? 0} alarmDate={alarm.alarmExpiredDate} /> )); diff --git a/packages/frontend/src/pages/stock-detail/StockMetricsPanel.tsx b/packages/frontend/src/pages/stock-detail/StockMetricsPanel.tsx index 6c1a6ca3..c2c238d5 100644 --- a/packages/frontend/src/pages/stock-detail/StockMetricsPanel.tsx +++ b/packages/frontend/src/pages/stock-detail/StockMetricsPanel.tsx @@ -1,6 +1,8 @@ import { useEffect, useState } from 'react'; +import Lottie from 'react-lottie-player'; import { useParams } from 'react-router-dom'; import { MetricItem, Title } from './components'; +import skeleton from '@/components/lottie/skeleton.json'; import { METRICS_DATA } from '@/constants/metricDetail'; import { socketStock } from '@/sockets/config'; import { useWebsocket } from '@/sockets/useWebsocket'; @@ -57,21 +59,30 @@ const StockMetricsPanel = ({ return ( <article className="flex flex-1 flex-col gap-10 rounded-md bg-white p-6 shadow"> - {Object.values(metricsData).map((section) => ( - <section className="flex flex-col gap-5" key={section.id}> - <Title>{section.title}</Title> - <section className="grid items-center gap-5 lg:grid-cols-2 lg:grid-rows-2 2xl:w-9/12"> - {section.metrics.map((metric) => ( - <MetricItem - key={metric.name} - label={metric.name} - value={metric.value} - tooltip={metric.message} - /> - ))} - </section> + {!price || !change || !volume ? ( + <section className="grid w-9/12 grid-cols-2 grid-rows-2"> + <Lottie animationData={skeleton} play className="w-64" /> + <Lottie animationData={skeleton} play className="w-64" /> + <Lottie animationData={skeleton} play className="w-64" /> + <Lottie animationData={skeleton} play className="w-64" /> </section> - ))} + ) : ( + Object.values(metricsData).map((section) => ( + <section className="flex flex-col gap-5" key={section.id}> + <Title>{section.title}</Title> + <section className="grid items-center gap-5 lg:grid-cols-2 lg:grid-rows-2 2xl:w-9/12"> + {section.metrics.map((metric) => ( + <MetricItem + key={metric.name} + label={metric.name} + value={metric.value} + tooltip={metric.message} + /> + ))} + </section> + </section> + )) + )} </article> ); }; diff --git a/packages/frontend/src/pages/stocks/StockRankingTable.tsx b/packages/frontend/src/pages/stocks/StockRankingTable.tsx index 92a8cb36..f917399b 100644 --- a/packages/frontend/src/pages/stocks/StockRankingTable.tsx +++ b/packages/frontend/src/pages/stocks/StockRankingTable.tsx @@ -1,10 +1,8 @@ -import { Suspense, useState } from 'react'; -import { ErrorBoundary } from 'react-error-boundary'; +import { useState } from 'react'; import { Link } from 'react-router-dom'; import { usePostStockView } from '@/apis/queries/stock-detail'; import { useGetStocksByPrice } from '@/apis/queries/stocks'; import DownArrow from '@/assets/down-arrow.svg?react'; -import { Loader } from '@/components/ui/loader'; import { cn } from '@/utils/cn'; const LIMIT = 10; @@ -51,48 +49,44 @@ const StockRankingTable = () => { </tr> </thead> <tbody> - <ErrorBoundary - fallback={ - <p className="py-3">종목 정보를 불러오는데 실패했어요.</p> - } - > - <Suspense fallback={<Loader />}> - {data.result.map((stock, index) => ( - <tr - key={stock.id} - className="display-medium14 text-dark-gray text-right [&>*]:p-4" - > - <td className="flex gap-6 text-left"> - <span className="text-gray w-3 flex-shrink-0"> - {index + 1} - </span> - <Link - to={`/stocks/${stock.id}`} - onClick={() => mutate({ stockId: stock.id })} - className="display-bold14 hover:text-orange cursor-pointer text-ellipsis hover:underline" - aria-label={stock.name} - > - {stock.name} - </Link> - </td> - <td>{stock.currentPrice?.toLocaleString()}원</td> - <td - className={cn( - +stock.changeRate >= 0 ? 'text-red' : 'text-blue', - )} + {data ? ( + data.result.map((stock, index) => ( + <tr + key={stock.id} + className="display-medium14 text-dark-gray text-right [&>*]:p-4" + > + <td className="flex gap-6 text-left"> + <span className="text-gray w-3 flex-shrink-0"> + {index + 1} + </span> + <Link + to={`/stocks/${stock.id}`} + onClick={() => mutate({ stockId: stock.id })} + className="display-bold14 hover:text-orange cursor-pointer text-ellipsis hover:underline" + aria-label={stock.name} > - {stock.changeRate}% - </td> - <td className="hidden lg:table-cell"> - {stock.volume?.toLocaleString()}원 - </td> - <td className="hidden lg:table-cell"> - {stock.marketCap?.toLocaleString()}주 - </td> - </tr> - ))} - </Suspense> - </ErrorBoundary> + {stock.name} + </Link> + </td> + <td>{stock.currentPrice?.toLocaleString()}원</td> + <td + className={cn( + +stock.changeRate >= 0 ? 'text-red' : 'text-blue', + )} + > + {stock.changeRate}% + </td> + <td className="hidden lg:table-cell"> + {stock.volume?.toLocaleString()}원 + </td> + <td className="hidden lg:table-cell"> + {stock.marketCap?.toLocaleString()}주 + </td> + </tr> + )) + ) : ( + <p className="py-3">종목 정보를 불러오는데 실패했어요.</p> + )} </tbody> </table> </div>