useApi.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import axios from 'axios'
  2. import { useLoading } from './useLoading'
  3. // API Base URL (환경변수 또는 기본값)
  4. const API_BASE_URL = process.env.NUXT_PUBLIC_API_BASE || 'https://gojinaudi.mycafe24.com/api'
  5. // Axios 인스턴스 생성
  6. const apiClient = axios.create({
  7. baseURL: API_BASE_URL,
  8. timeout: 30000,
  9. headers: {
  10. 'Content-Type': 'application/json'
  11. }
  12. })
  13. // 로딩 카운터 (동시에 여러 요청이 있을 수 있으므로)
  14. let pendingRequests = 0
  15. const { showLoading, hideLoading } = useLoading()
  16. // Request Interceptor (토큰 자동 추가 + 로딩 시작)
  17. apiClient.interceptors.request.use(
  18. (config) => {
  19. // 로딩 카운터 증가
  20. pendingRequests++
  21. if (pendingRequests === 1) {
  22. showLoading()
  23. }
  24. if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') {
  25. const token = localStorage.getItem('admin_token')
  26. if (token) {
  27. config.headers.Authorization = `Bearer ${token}`
  28. console.log('[useApi] Request with token:', config.url, token.substring(0, 20) + '...')
  29. } else {
  30. console.log('[useApi] Request without token:', config.url)
  31. }
  32. }
  33. return config
  34. },
  35. (error) => {
  36. // 에러 발생시에도 카운터 감소
  37. pendingRequests--
  38. if (pendingRequests === 0) {
  39. hideLoading()
  40. }
  41. return Promise.reject(error)
  42. }
  43. )
  44. // Response Interceptor (에러 처리 + 로딩 종료)
  45. apiClient.interceptors.response.use(
  46. (response) => {
  47. // 로딩 카운터 감소
  48. pendingRequests--
  49. if (pendingRequests === 0) {
  50. hideLoading()
  51. }
  52. console.log('[useApi] Response:', response.config.url, response.status)
  53. return response
  54. },
  55. (error) => {
  56. // 에러 시에도 카운터 감소
  57. pendingRequests--
  58. if (pendingRequests === 0) {
  59. hideLoading()
  60. }
  61. const status = error.response?.status
  62. const url = error.config?.url
  63. console.log('[useApi] Error:', {
  64. url,
  65. status,
  66. message: error.message,
  67. hasToken: !!localStorage.getItem('admin_token')
  68. })
  69. // 401 에러만 토큰 삭제 및 로그아웃 처리
  70. if (status === 401) {
  71. console.log('[useApi] 401 Unauthorized - 토큰 삭제 및 로그아웃')
  72. if (typeof window !== 'undefined') {
  73. localStorage.removeItem('admin_token')
  74. localStorage.removeItem('admin_user')
  75. window.location.href = '/admin'
  76. }
  77. } else {
  78. console.log('[useApi] 401이 아닌 에러 - 토큰 유지')
  79. }
  80. return Promise.reject(error)
  81. }
  82. )
  83. export const useApi = () => {
  84. // GET 요청
  85. const get = async (url, params = {}) => {
  86. try {
  87. const response = await apiClient.get(url, { params })
  88. // 전체 응답 반환: { success, data, message }
  89. return { data: response.data, error: null }
  90. } catch (error) {
  91. return { data: null, error: error.response?.data || error.message }
  92. }
  93. }
  94. // POST 요청
  95. const post = async (url, data = {}) => {
  96. try {
  97. const response = await apiClient.post(url, data)
  98. // 전체 응답 반환: { success, data, message }
  99. return { data: response.data, error: null }
  100. } catch (error) {
  101. return { data: null, error: error.response?.data || error.message }
  102. }
  103. }
  104. // PUT 요청
  105. const put = async (url, data = {}) => {
  106. try {
  107. const response = await apiClient.put(url, data)
  108. // 전체 응답 반환: { success, data, message }
  109. return { data: response.data, error: null }
  110. } catch (error) {
  111. return { data: null, error: error.response?.data || error.message }
  112. }
  113. }
  114. // DELETE 요청
  115. const del = async (url) => {
  116. try {
  117. const response = await apiClient.delete(url)
  118. // 전체 응답 반환: { success, data, message }
  119. return { data: response.data, error: null }
  120. } catch (error) {
  121. return { data: null, error: error.response?.data || error.message }
  122. }
  123. }
  124. // 파일 업로드
  125. const upload = async (url, formData) => {
  126. try {
  127. const response = await apiClient.post(url, formData, {
  128. headers: {
  129. 'Content-Type': 'multipart/form-data'
  130. }
  131. })
  132. // 전체 응답 반환: { success, data, message }
  133. return { data: response.data, error: null }
  134. } catch (error) {
  135. return { data: null, error: error.response?.data || error.message }
  136. }
  137. }
  138. return {
  139. get,
  140. post,
  141. put,
  142. del,
  143. upload
  144. }
  145. }