habib-analyst's picture
Fix: Hardcode live backend URL as fallback in frontend
03d5c71
Raw History Blame Contribute Delete
4.27 kB
import axios from "axios";
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || "https://habib-analyst-email-agent-api.hf.space/api",
headers: { "Content-Type": "application/json" },
timeout: 180000,
});
// ─── Token Management ──────────────────────────────────────────────
let _token = null;
export function setAuthToken(token) {
_token = token;
if (token) {
sessionStorage.setItem("session_token", token);
} else {
sessionStorage.removeItem("session_token");
}
}
export function getAuthToken() {
if (!_token) {
_token = sessionStorage.getItem("session_token");
}
return _token;
}
export function clearAuthToken() {
_token = null;
sessionStorage.removeItem("session_token");
}
// ─── Attach JWT to every request ───────────────────────────────────
api.interceptors.request.use((config) => {
const token = getAuthToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// ─── Handle 401 responses globally ─────────────────────────────────
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Don't redirect if we're already on auth routes
const url = error.config?.url || "";
if (!url.includes("/auth/") && !url.includes("/admin/login")) {
clearAuthToken();
// Dispatch a custom event so AuthContext can react
window.dispatchEvent(new CustomEvent("auth:expired"));
}
}
return Promise.reject(error);
}
);
export const authAPI = {
getStatus: () => api.get("/auth/status"),
startAuth: () => api.get("/auth/gmail"),
disconnect: () => api.post("/auth/disconnect"),
};
export const adminAPI = {
login: (email, password) => api.post("/admin/login", { email, password }),
getStats: () => api.get("/admin/stats"),
getUsers: () => api.get("/admin/users"),
userAction: (email, action) => api.post("/admin/users/action", { email, action }),
};
export const quotaAPI = {
getStatus: () => api.get("/quota/status"),
checkDuplicate: (recipientEmail) => api.get(`/quota/check-duplicate/${recipientEmail}`),
};
export const emailAPI = {
loadTemplate: () => api.post("/email/load-template"),
applyTemplate: (recipientInput) =>
api.post("/email/apply-template", { professor_input: recipientInput }),
verify: (toEmail, subject, htmlBody) =>
api.post("/email/verify", { to_email: toEmail, subject, html_body: htmlBody }),
send: (toEmail, subject, htmlBody) =>
api.post("/email/send", { to_email: toEmail, subject, html_body: htmlBody }),
getAttachments: () => api.get("/email/attachments"),
uploadAttachment: (file) => {
const form = new FormData();
form.append("file", file);
return api.post("/email/attachments/upload", form, {
headers: { "Content-Type": "multipart/form-data" },
});
},
deleteAttachment: (filename) => api.delete(`/email/attachments/${filename}`),
contactSales: (data) => api.post("/email/contact-sales", data),
};
export const batchAPI = {
uploadCSV: (file) => {
const form = new FormData();
form.append("file", file);
return api.post("/batch/upload-csv", form, {
headers: { "Content-Type": "multipart/form-data" },
});
},
extractEmails: (text) =>
api.post("/batch/extract-emails", { text }),
scrapeFaculty: (url) =>
api.post("/batch/scrape-faculty", { url }, { timeout: 120000 }),
start: (recipientList, mode = "manual", delayMin = 15, delayMax = 45) =>
api.post("/batch/start", {
professor_list: recipientList,
mode,
delay_min: delayMin,
delay_max: delayMax,
}),
getQueue: () => api.get("/batch/queue"),
generate: (index) => api.post(`/batch/generate/${index}`),
send: (index) => api.post(`/batch/send/${index}`),
skip: (index) => api.post(`/batch/skip/${index}`),
autoRun: () => api.post("/batch/auto-run", {}, { timeout: 3600000 }),
stop: () => api.post("/batch/stop"),
clear: () => api.post("/batch/clear"),
};
export default api;