The Axios instance every authenticated request goes through: environment-aware base URL, a CSRF token attached to every mutation, and Authorization state kept in sync with localStorage.

// src/services/api/privateApi.ts

// lines 60–122 (instance + CSRF interceptor)

const API_BASE = resolveApiBase();

const privateApi: AxiosInstance = axios.create({
  baseURL: API_BASE,
  withCredentials: true,
  headers: {
    Accept: "application/json",
    "X-Requested-With": "XMLHttpRequest",
  },
});

type CommonAuthHeaders = {
  Authorization?: string;
  [key: string]: unknown;
};

type PrivateAxiosHeaders = AxiosHeaders & {
  common?: CommonAuthHeaders;
};

function getMutableDefaultHeaders(): PrivateAxiosHeaders {
  return privateApi.defaults.headers as unknown as PrivateAxiosHeaders;
}

// Bootstrap: hydrate Authorization from localStorage on page load
try {
  if (typeof window !== "undefined") {
    const stored = localStorage.getItem("authToken");
    if (stored) {
      const headers = getMutableDefaultHeaders();
      headers.common = { ...(headers.common ?? {}), Authorization: stored };
    }
  }
} catch {
  void 0;
}

// Request: attach CSRF token on mutations
privateApi.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  const method = (config.method ?? "get").toLowerCase();
  if (["post", "put", "patch", "delete"].includes(method)) {
    const m = document.cookie.match(/(?:^|;\s*)CSRF-TOKEN=([^;]+)/);
    if (m) {
      const token = decodeURIComponent(m[1]);
      const headers = (config.headers ??= new AxiosHeaders(config.headers));
      headers.set("X-CSRF-Token", token);
    }
  }
  return config;
});
  
Base URL resolution is environment-aware (dev hits the local SSL Puma server directly); the CSRF token is read straight off the cookie set at login.
// src/services/api/privateApi.ts

// lines 60–122 (instance + CSRF interceptor)

const API_BASE = resolveApiBase();

const privateApi: AxiosInstance = axios.create({
  baseURL: API_BASE,
  withCredentials: true,
  headers: {
    Accept: "application/json",
    "X-Requested-With": "XMLHttpRequest",
  },
});

type CommonAuthHeaders = {
  Authorization?: string;
  [key: string]: unknown;
};

type PrivateAxiosHeaders = AxiosHeaders & {
  common?: CommonAuthHeaders;
};

function getMutableDefaultHeaders(): PrivateAxiosHeaders {
  return privateApi.defaults.headers as unknown as PrivateAxiosHeaders;
}

// Bootstrap: hydrate Authorization from localStorage on page load
try {
  if (typeof window !== "undefined") {
    const stored = localStorage.getItem("authToken");
    if (stored) {
      const headers = getMutableDefaultHeaders();
      headers.common = { ...(headers.common ?? {}), Authorization: stored };
    }
  }
} catch {
  void 0;
}

// Request: attach CSRF token on mutations
privateApi.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  const method = (config.method ?? "get").toLowerCase();
  if (["post", "put", "patch", "delete"].includes(method)) {
    const m = document.cookie.match(/(?:^|;\s*)CSRF-TOKEN=([^;]+)/);
    if (m) {
      const token = decodeURIComponent(m[1]);
      const headers = (config.headers ??= new AxiosHeaders(config.headers));
      headers.set("X-CSRF-Token", token);
    }
  }
  return config;
});
 
// src/services/api/privateApi.ts

// lines 60–122 (instance + CSRF interceptor)

const API_BASE = resolveApiBase();

const privateApi: AxiosInstance = axios.create({
  baseURL: API_BASE,
  withCredentials: true,
  headers: {
    Accept: "application/json",
    "X-Requested-With": "XMLHttpRequest",
  },
});

type CommonAuthHeaders = {
  Authorization?: string;
  [key: string]: unknown;
};

type PrivateAxiosHeaders = AxiosHeaders & {
  common?: CommonAuthHeaders;
};

function getMutableDefaultHeaders(): PrivateAxiosHeaders {
  return privateApi.defaults.headers as unknown as PrivateAxiosHeaders;
}

// Bootstrap: hydrate Authorization from localStorage on page load
try {
  if (typeof window !== "undefined") {
    const stored = localStorage.getItem("authToken");
    if (stored) {
      const headers = getMutableDefaultHeaders();
      headers.common = { ...(headers.common ?? {}), Authorization: stored };
    }
  }
} catch {
  void 0;
}

// Request: attach CSRF token on mutations
privateApi.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  const method = (config.method ?? "get").toLowerCase();
  if (["post", "put", "patch", "delete"].includes(method)) {
    const m = document.cookie.match(/(?:^|;\s*)CSRF-TOKEN=([^;]+)/);
    if (m) {
      const token = decodeURIComponent(m[1]);
      const headers = (config.headers ??= new AxiosHeaders(config.headers));
      headers.set("X-CSRF-Token", token);
    }
  }
  return config;
});