Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1aa85540e6 | |||
| 64ee8f8603 |
Generated
+25
@@ -39,6 +39,7 @@
|
|||||||
"@tanstack/react-query": "^5.96.1",
|
"@tanstack/react-query": "^5.96.1",
|
||||||
"@tanstack/react-query-devtools": "^5.100.9",
|
"@tanstack/react-query-devtools": "^5.100.9",
|
||||||
"axios": "^1.14.0",
|
"axios": "^1.14.0",
|
||||||
|
"axios-retry": "^4.5.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -3769,6 +3770,18 @@
|
|||||||
"proxy-from-env": "^2.1.0"
|
"proxy-from-env": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios-retry": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"is-retry-allowed": "^2.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"axios": "0.x || 1.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bail": {
|
"node_modules/bail": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
||||||
@@ -5695,6 +5708,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/is-retry-allowed": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/isexe": {
|
"node_modules/isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"@tanstack/react-query": "^5.96.1",
|
"@tanstack/react-query": "^5.96.1",
|
||||||
"@tanstack/react-query-devtools": "^5.100.9",
|
"@tanstack/react-query-devtools": "^5.100.9",
|
||||||
"axios": "^1.14.0",
|
"axios": "^1.14.0",
|
||||||
|
"axios-retry": "^4.5.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,5 @@
|
|||||||
import { Toaster as Sonner } from "@/components/ui/sonner";
|
import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { ThemeProvider } from "@/contexts/ThemeContext";
|
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
import { AnimatePresence } from "framer-motion";
|
import { AnimatePresence } from "framer-motion";
|
||||||
import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom";
|
import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom";
|
||||||
@@ -17,6 +16,7 @@ import Projects from "./pages/Projects";
|
|||||||
import Technologies from "./pages/Technologies";
|
import Technologies from "./pages/Technologies";
|
||||||
import { QueryProvider } from "./provider/QueryProvider";
|
import { QueryProvider } from "./provider/QueryProvider";
|
||||||
import OverviewPage from "./components/admin/dashboards/OverviewPage";
|
import OverviewPage from "./components/admin/dashboards/OverviewPage";
|
||||||
|
import { ThemeProvider } from "./provider/ThemeProvider";
|
||||||
|
|
||||||
function AnimatedRoutes() {
|
function AnimatedRoutes() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|||||||
@@ -1,11 +1,42 @@
|
|||||||
|
// src/api/axiosInstance.ts
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import axiosRetry from "axios-retry";
|
||||||
|
|
||||||
|
import { API_URL } from "./config";
|
||||||
|
|
||||||
|
import {
|
||||||
|
requestInterceptor,
|
||||||
|
responseSuccessInterceptor,
|
||||||
|
responseErrorInterceptor,
|
||||||
|
} from "./interceptors";
|
||||||
|
|
||||||
const axiosInstance = axios.create({
|
const axiosInstance = axios.create({
|
||||||
baseURL: import.meta.env.VITE_API_URL,
|
baseURL: API_URL,
|
||||||
timeout: 5000,
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
axiosInstance.interceptors.request.use(
|
||||||
|
requestInterceptor
|
||||||
|
);
|
||||||
|
|
||||||
|
axiosInstance.interceptors.response.use(
|
||||||
|
responseSuccessInterceptor,
|
||||||
|
responseErrorInterceptor
|
||||||
|
);
|
||||||
|
|
||||||
|
axiosRetry(axiosInstance, {
|
||||||
|
retries: 3,
|
||||||
|
retryDelay: axiosRetry.exponentialDelay,
|
||||||
|
|
||||||
|
retryCondition: (error) =>
|
||||||
|
axiosRetry.isNetworkOrIdempotentRequestError(
|
||||||
|
error
|
||||||
|
) ||
|
||||||
|
error.response?.status === 429,
|
||||||
|
});
|
||||||
|
|
||||||
export default axiosInstance;
|
export default axiosInstance;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const apiUrl = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
|
if (!apiUrl) {
|
||||||
|
throw new Error(
|
||||||
|
"Missing VITE_API_URL environment variable"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const API_URL = apiUrl;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// src/api/errors.ts
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
import { ApiError } from "./types";
|
||||||
|
|
||||||
|
export function normalizeApiError(error: unknown): ApiError {
|
||||||
|
if (!axios.isAxiosError(error)) {
|
||||||
|
return {
|
||||||
|
status: 0,
|
||||||
|
message: "Unexpected error occurred",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.code === "ECONNABORTED") {
|
||||||
|
return {
|
||||||
|
status: 408,
|
||||||
|
message: "Request timeout",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error.response) {
|
||||||
|
return {
|
||||||
|
status: 0,
|
||||||
|
message: "Network error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { status, data } = error.response;
|
||||||
|
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
message:
|
||||||
|
data?.message ||
|
||||||
|
data?.error ||
|
||||||
|
"Something went wrong",
|
||||||
|
code: data?.code,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { getAccessToken } from "@/lib/auth";
|
||||||
|
import { AxiosResponse, InternalAxiosRequestConfig } from "axios";
|
||||||
|
import { normalizeApiError } from "./errors";
|
||||||
|
|
||||||
|
export function requestInterceptor(config: InternalAxiosRequestConfig) {
|
||||||
|
const token = getAccessToken();
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const responseSuccessInterceptor = (response: AxiosResponse) => response;
|
||||||
|
|
||||||
|
export function responseErrorInterceptor(error: unknown) {
|
||||||
|
const normalizedError = normalizeApiError(error);
|
||||||
|
|
||||||
|
return Promise.reject(normalizedError);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface ApiError {
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { Moon, PanelLeftClose, PanelLeftOpen, Sun } from "lucide-react";
|
import { Moon, PanelLeftClose, PanelLeftOpen, Sun } from "lucide-react";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function getAccessToken() {
|
||||||
|
return localStorage.getItem("accessToken");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user