Skip to content

Commit 0dbf604

Browse files
committed
Add apiHelper as a Context
1 parent 96f8599 commit 0dbf604

File tree

4 files changed

+126
-15
lines changed

4 files changed

+126
-15
lines changed

website/src/common/helpers/api-helper.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import axios, { AxiosResponse } from "axios";
22
import { NavigateFunction } from "react-router-dom";
33

4+
// For backward compatibility, keep the static methods
45
export abstract class ApiHelper {
5-
// Add an optional navigate parameter
66
static async get<T>(path: string, navigate?: NavigateFunction): Promise<T | null> {
77
try {
88
const response: AxiosResponse<T> = await axios.get("/api/" + path, {

website/src/common/hooks/use-api.ts

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import axios, { AxiosResponse } from "axios";
2+
import { createContext, useContext } from "react";
3+
import { useNavigate } from "react-router-dom";
4+
5+
// Define the API Context type
6+
interface ApiContextType {
7+
get: <T>(path: string) => Promise<T | null>;
8+
post: <T>(path: string, data: unknown) => Promise<T | null>;
9+
}
10+
11+
// Create and export the context
12+
export const ApiContext = createContext<ApiContextType | undefined>(undefined);
13+
14+
// Custom hook to use the API context
15+
export const useApi = () => {
16+
const context = useContext(ApiContext);
17+
if (context === undefined) {
18+
throw new Error("useApi must be used within an ApiProvider");
19+
}
20+
return context;
21+
};
22+
23+
// Hook to provide API context values
24+
export const useApiProvider = () => {
25+
const navigate = useNavigate();
26+
27+
const get = async <T,>(path: string): Promise<T | null> => {
28+
try {
29+
const response: AxiosResponse<T> = await axios.get("/api/" + path, {
30+
timeout: 10000, // 10 seconds timeout
31+
});
32+
return response.data;
33+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
34+
} catch (error: any) {
35+
if (error.response?.status === 401) {
36+
console.log("Unauthorized");
37+
// Only redirect if we're not already on the login page
38+
if (!window.location.pathname.includes("/login")) {
39+
navigate("/login");
40+
}
41+
return null;
42+
}
43+
if (
44+
(error.response?.status >= 500 && error.response?.status < 600) ||
45+
error.code === "ERR_CONNECTION_REFUSED" ||
46+
error.code === "ERR_CONNECTION_TIMED_OUT" ||
47+
error.code === "ERR_CONNECTION_RESET" ||
48+
error.code === "ECONNABORTED" ||
49+
error.code === "ERR_NETWORK" ||
50+
error.message?.includes("timeout") ||
51+
error.message === "Network Error" ||
52+
(error.request && error.request.status === 0)
53+
) {
54+
console.log("Unable to connect to server", {
55+
code: error.code,
56+
message: error.message,
57+
status: error.request?.status,
58+
});
59+
navigate("/system-unavailable");
60+
return null;
61+
}
62+
console.error("Error getting api " + path + ":", error);
63+
return null;
64+
}
65+
};
66+
67+
const post = async <T,>(path: string, data: unknown): Promise<T | null> => {
68+
try {
69+
const response: AxiosResponse<T> = await axios.post("/api/" + path, data, {
70+
timeout: 10000, // 10 seconds timeout
71+
});
72+
return response.data;
73+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
74+
} catch (error: any) {
75+
if (error.response?.status === 401) {
76+
console.log("Unauthorized");
77+
// Only redirect if we're not already on the login page
78+
if (!window.location.pathname.includes("/login")) {
79+
navigate("/login");
80+
}
81+
return null;
82+
}
83+
if (
84+
(error.response?.status >= 500 && error.response?.status < 600) ||
85+
error.code === "ERR_CONNECTION_REFUSED" ||
86+
error.code === "ERR_CONNECTION_TIMED_OUT" ||
87+
error.code === "ERR_CONNECTION_RESET" ||
88+
error.code === "ECONNABORTED" ||
89+
error.code === "ERR_NETWORK" ||
90+
error.message?.includes("timeout") ||
91+
error.message === "Network Error" ||
92+
(error.request && error.request.status === 0)
93+
) {
94+
console.log("Unable to connect to server", {
95+
code: error.code,
96+
message: error.message,
97+
status: error.request?.status,
98+
});
99+
navigate("/system-unavailable");
100+
return null;
101+
}
102+
console.error("Error posting to api " + path + ":", error);
103+
return null;
104+
}
105+
};
106+
107+
return { get, post };
108+
};

website/src/common/hooks/use-battery.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { createContext, useContext, useEffect, useState } from "react";
2-
import { useNavigate } from 'react-router-dom';
32
import { useAuth } from "./use-authentication";
43
import { FlashbarProps } from "@cloudscape-design/components";
5-
import { ApiHelper } from "../helpers/api-helper";
4+
import { useApi } from "./use-api";
65

76
// Add new interface for API response
87
interface BatteryResponse {
@@ -44,7 +43,7 @@ export const useBatteryProvider = () => {
4443
const [hasInitialReading, setHasInitialReading] = useState(false);
4544
const [pageLoadTime] = useState<number>(Date.now());
4645
const { isAuthenticated } = useAuth();
47-
const navigate = useNavigate();
46+
const { get: apiGet } = useApi();
4847

4948
// Battery notifications state
5049
const [batteryFlashbarItems, setBatteryFlashbarItems] = useState<
@@ -148,7 +147,7 @@ export const useBatteryProvider = () => {
148147

149148
const getBatteryStatus = async () => {
150149
try {
151-
const response = await ApiHelper.get<BatteryResponse>("get_battery_level", navigate);
150+
const response = await apiGet<BatteryResponse>("get_battery_level");
152151
return response;
153152
} catch (error) {
154153
console.error("Error fetching battery status:", error);
@@ -167,7 +166,7 @@ export const useBatteryProvider = () => {
167166
isSubscribed = false;
168167
clearInterval(batteryInterval);
169168
};
170-
}, [isAuthenticated, navigate]); // Add isAuthenticated as a dependency
169+
}, [isAuthenticated, apiGet]); // Add isAuthenticated as a dependency
171170

172171
const batteryContextValue: BatteryState = {
173172
batteryLevel,

website/src/components/context-provider.tsx

+13-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { SupportedApisContext, useSupportedApisProvider } from "../common/hooks/
55
import { ModelsContext, useModelsProvider } from "../common/hooks/use-models";
66
import { AuthContext, useAuthProvider } from "../common/hooks/use-authentication";
77
import { PreferencesContext, usePreferencesProvider } from "../common/hooks/use-preferences";
8+
import { ApiContext, useApiProvider } from "../common/hooks/use-api";
89

910
// Main Context Provider Component
1011
export const ContextProvider: React.FC<{
@@ -15,17 +16,20 @@ export const ContextProvider: React.FC<{
1516
const supportedApisContextValue = useSupportedApisProvider();
1617
const modelsContextValue = useModelsProvider();
1718
const preferencesContextValue = usePreferencesProvider();
19+
const apiContextValue = useApiProvider();
1820

1921
return (
20-
<SupportedApisContext.Provider value={supportedApisContextValue}>
21-
<PreferencesContext.Provider value={preferencesContextValue}>
22-
<BatteryContext.Provider value={batteryContextValue}>
23-
<NetworkContext.Provider value={networkContextValue}>
24-
<ModelsContext.Provider value={modelsContextValue}>{children}</ModelsContext.Provider>
25-
</NetworkContext.Provider>
26-
</BatteryContext.Provider>
27-
</PreferencesContext.Provider>
28-
</SupportedApisContext.Provider>
22+
<ApiContext.Provider value={apiContextValue}>
23+
<SupportedApisContext.Provider value={supportedApisContextValue}>
24+
<PreferencesContext.Provider value={preferencesContextValue}>
25+
<BatteryContext.Provider value={batteryContextValue}>
26+
<NetworkContext.Provider value={networkContextValue}>
27+
<ModelsContext.Provider value={modelsContextValue}>{children}</ModelsContext.Provider>
28+
</NetworkContext.Provider>
29+
</BatteryContext.Provider>
30+
</PreferencesContext.Provider>
31+
</SupportedApisContext.Provider>
32+
</ApiContext.Provider>
2933
);
3034
};
3135

0 commit comments

Comments
 (0)