Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat/#155] react query 추가 #184

Merged
merged 19 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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] });
},
});
};
Comment on lines +8 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2


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;
Comment on lines +8 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1

}
};
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
Loading