Skip to content

Commit

Permalink
Merge pull request #184 from TEAM-BEAT/feat/#155/ReactQuery
Browse files Browse the repository at this point in the history
[Feat/#155] react query 추가
  • Loading branch information
pepperdad authored Jul 16, 2024
2 parents 597955a + f7ffeb3 commit df970bf
Show file tree
Hide file tree
Showing 24 changed files with 1,428 additions and 57 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"@mui/icons-material": "^5.16.0",
"@mui/material": "^5.16.0",
"@mui/x-date-pickers": "^7.9.0",
"@tanstack/react-query": "^5.51.1",
"@tanstack/react-query-devtools": "^5.51.1",
"axios": "^1.7.2",
"dayjs": "^1.11.11",
"jotai": "^2.8.4",
Expand Down
31 changes: 20 additions & 11 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { ThemeProvider } from "styled-components";

import Modal from "@components/commons/modal/Modal";
import { ThemeProvider as MuiThemeProvider, createTheme } from "@mui/material/styles";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const darkTheme = createTheme({
palette: {
Expand All @@ -18,18 +20,25 @@ const darkTheme = createTheme({
});

function App() {
const queryClient = new QueryClient();

return (
<MuiThemeProvider theme={darkTheme}>
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<GlobalStyle />
<RouterProvider router={router} />
<Modal />
<Alert />
<Confirm />
</LocalizationProvider>
</ThemeProvider>
</MuiThemeProvider>
<QueryClientProvider client={queryClient}>
<MuiThemeProvider theme={darkTheme}>
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<GlobalStyle />
<RouterProvider router={router} />
<Modal />
<Alert />
<Confirm />
</LocalizationProvider>
</ThemeProvider>
</MuiThemeProvider>
<div style={{ fontSize: "16px" }}>
<ReactQueryDevtools initialIsOpen={false} />
</div>
</QueryClientProvider>
);
}

Expand Down
Empty file removed src/apis/.gitkeep
Empty file.
47 changes: 47 additions & 0 deletions src/apis/domains/bookings/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { get, post } from "@apis/index";
import { components } from "@typings/api/schema";
import { ApiResponseType } from "@typings/commonType";
import { AxiosResponse } from "axios";

export interface postGuestReq {
scheduleId: number;
purchaseTicketCount: number;
scheduleNumber: string;
bookerName: string;
bookerPhoneNumber: string;
birthDate: string;
password: string;
totalPaymentAmount: number;
isPaymentCompleted: boolean;
}

type GuestBookingResponse = components["schemas"]["GuestBookingResponse"];

// 1. API 요청 함수 작성 및 타입 추가
export const postGuestBook = async (
formData: postGuestReq
): Promise<GuestBookingResponse | null> => {
try {
const response: AxiosResponse<ApiResponseType<GuestBookingResponse>> = await post(
"/bookings/guest",
formData
);

return response.data.data;
} catch (error) {
console.error("error", error);

return null;
}
};

export const getGuestBookingList = async () => {
try {
const response = await get("/bookings/guest/retrieve");

console.log(response.data);
return response;
} catch (error) {
console.error("error", error);
}
};
28 changes: 28 additions & 0 deletions src/apis/domains/bookings/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { QueryClient, useMutation, useQuery } from "@tanstack/react-query";
import { getGuestBookingList, postGuestBook, postGuestReq } from "./api";

export const QUERY_KEY = {
LIST: "list",
};

// 2. 쿼리 작성
export const useGuestBook = () => {
const queryClient = new QueryClient();

return useMutation({
mutationFn: (formData: postGuestReq) => postGuestBook(formData), // API 요청 함수
onSuccess: (res) => {
// 성공 시, 호출
queryClient.invalidateQueries({ queryKey: [QUERY_KEY.LIST] });
},
});
};

export const useGetGuestBookingList = () => {
return useQuery({
queryKey: [QUERY_KEY.LIST],
queryFn: () => getGuestBookingList(),
staleTime: 1000 * 60 * 60,
gcTime: 1000 * 60 * 60 * 24,
});
};
18 changes: 18 additions & 0 deletions src/apis/domains/home/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { get } from "@apis/index";
import { components } from "@typings/api/schema";
import { ApiResponseType } from "@typings/commonType";
import { AxiosResponse } from "axios";

type HomeResponse = components["schemas"]["HomeResponse"];

// 1. API 요청 함수 작성 및 타입 추가
export const getAllScheduleList = async (): Promise<HomeResponse | null> => {
try {
const response: AxiosResponse<ApiResponseType<HomeResponse>> = await get("/main");

return response.data.data;
} catch (error) {
console.error("error", error);
return null;
}
};
16 changes: 16 additions & 0 deletions src/apis/domains/home/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useQuery } from "@tanstack/react-query";
import { getAllScheduleList } from "./api";

const QUERY_KEY = {
LIST: "list",
};

// 2. 쿼리 작성
export const useGetAllScheduleList = () => {
return useQuery({
queryKey: [QUERY_KEY.LIST],
queryFn: () => getAllScheduleList(), // API 요청 함수
staleTime: 1000 * 60 * 60,
gcTime: 1000 * 60 * 60 * 24,
});
};
32 changes: 32 additions & 0 deletions src/apis/domains/register/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { get } from "@apis/index";
import { useQuery } from "@tanstack/react-query";

export const QUERY_KEY_POST = {
getPresignedUrl: "getPresignedUrl",
};

interface PresignedUrlPropTypes {
data: { fileName: string; url: string };
}

const fetchPresignedUrl = async () => {
try {
const response = await get<PresignedUrlPropTypes>("/api/image/upload");
console.log(response.data);

return response.data;
} catch (err) {
console.error("error", err);
}
};

export const usePresignedUrl = () => {
const { data } = useQuery({
queryKey: [QUERY_KEY_POST.getPresignedUrl],
queryFn: () => fetchPresignedUrl(),
});

const fileName = data && data?.data?.fileName;
const url = data && data?.data?.url;
return { fileName, url };
};
30 changes: 30 additions & 0 deletions src/apis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import axios from "axios";

export const instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
// withCredentials: true,

headers: {
Authorization: `Bearer ${localStorage.getItem("accessToken") || ""}`,
},
});

export function get<T>(...args: Parameters<typeof instance.get>) {
return instance.get<T>(...args);
}

export function post<T>(...args: Parameters<typeof instance.post>) {
return instance.post<T>(...args);
}

export function put<T>(...args: Parameters<typeof instance.put>) {
return instance.put<T>(...args);
}

export function patch<T>(...args: Parameters<typeof instance.patch>) {
return instance.patch<T>(...args);
}

export function del<T>(...args: Parameters<typeof instance.delete>) {
return instance.delete<T>(...args);
}
6 changes: 3 additions & 3 deletions src/components/commons/chip/Chip.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ export const ChipWrapper = styled.div<{ color?: ChipsColorTypes }>`
}}
`;

export const ChipIcon = styled.span<{ iconColor?: string }>`
export const ChipIcon = styled.span<{ $iconColor?: string }>`
width: 1.6rem;
height: 1.6rem;
${({ theme, iconColor }) => {
switch (iconColor) {
${({ theme, $iconColor }) => {
switch ($iconColor) {
case "pink":
return `
color: ${theme.colors.pink_400};
Expand Down
2 changes: 1 addition & 1 deletion src/components/commons/chip/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface ChipProps {
const Chip = ({ label, color, icon, iconColor, onClick }: ChipProps) => {
return (
<S.ChipWrapper color={color} onClick={onClick}>
{icon && <S.ChipIcon iconColor={iconColor}>{icon}</S.ChipIcon>}
{icon && <S.ChipIcon $iconColor={iconColor}>{icon}</S.ChipIcon>}
<span>{label}</span>
</S.ChipWrapper>
);
Expand Down
5 changes: 5 additions & 0 deletions src/components/commons/hamburger/Hamburger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const Hamburger = () => {

const handlerOutside = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
closeHamburger();

e.stopPropagation();
};

Expand Down Expand Up @@ -54,6 +55,7 @@ const Hamburger = () => {
const handleKakaoLogin = (url: string) => {
setNavigateUrl(url);
requestKakaoLogin();
closeHamburger();
};

return (
Expand All @@ -79,6 +81,7 @@ const Hamburger = () => {
<S.NavigateBtn
onClick={() => {
navigate("/gig-register");
closeHamburger();
}}
>
<S.NavigateBtnText>내가 등록한 공연</S.NavigateBtnText>
Expand All @@ -87,6 +90,7 @@ const Hamburger = () => {
<S.NavigateBtn
onClick={() => {
navigate("/lookup");
closeHamburger();
}}
>
<S.NavigateBtnText>내가 예매한 공연</S.NavigateBtnText>
Expand All @@ -103,6 +107,7 @@ const Hamburger = () => {
<S.NavigateBtn
onClick={() => {
navigate("/nonmb-lookup");
closeHamburger();
}}
>
<S.NavigateBtnText>비회원 예매 조회</S.NavigateBtnText>
Expand Down
77 changes: 77 additions & 0 deletions src/hooks/useTokenRefresher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { instance } from "@apis/index";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

export default function TokenRefresher() {
const navigate = useNavigate();

useEffect(() => {
const interceptor = instance.interceptors.response.use(
// 성공적인 응답 처리
(response) => {
console.log("Starting Request", response);
return response;
},
async (error) => {
const originalConfig = error.config; // 기존에 수행하려고 했던 작업
const msg = error.response.data.msg; // error msg from backend
const status = error.response.status; // 현재 발생한 에러 코드
// access_token 재발급

if (status === 401) {
if (msg === "Expired Access Token. 토큰이 만료되었습니다") {
// console.log("토큰 재발급 요청");
await instance
.post(
"/users/refresh-token",
{},
{
// TODO: 쿠키로 변경 ?
headers: {
Authorization: `${localStorage.getItem("Authorization")}`,
Refresh: `${localStorage.getItem("Refresh")}`,
},
}
)
.then((res) => {
console.log("res: ", res);
// 새 토큰 저장
localStorage.setItem("Authorization", res.headers.authorization);
localStorage.setItem("Refresh", res.headers.refresh);

// 새로 응답받은 데이터로 토큰 만료로 실패한 요청에 대한 인증 시도 (header에 토큰 담아 보낼 때 사용)
originalConfig.headers["authorization"] = `Bearer ${res.headers.authorization}`;
originalConfig.headers["refresh"] = res.headers.refresh;

// console.log("New access token obtained.");
// 새로운 토큰으로 재요청
return instance(originalConfig);
})
.catch(() => {
console.error("An error occurred while refreshing the token:", error);
});
}
// refresh_token 재발급과 예외 처리
// else if(msg == "만료된 리프레시 토큰입니다") {
else {
localStorage.clear();
navigate("/main");
alert("토큰이 만료되어 자동으로 로그아웃 되었습니다.");
}
} else if (status === 400 || status === 404 || status === 409) {
console.log("hi3");
// window.alert(msg);
// console.log(msg)
}
// console.error('Error response:', error);
// 다른 모든 오류를 거부하고 처리
return Promise.reject(error);
}
);
return () => {
instance.interceptors.response.eject(interceptor);
};
}, []);

return <></>;
}
Loading

0 comments on commit df970bf

Please sign in to comment.