Floating dismissable alert - initial commit
This commit is contained in:
@@ -2,22 +2,42 @@ import type { ApiSettings } from "../types";
|
||||
|
||||
const STORAGE_KEY = "multimailer.apiSettings";
|
||||
|
||||
/**
|
||||
* API endpoint helpers already pass paths beginning with /api/v1. The configured
|
||||
* value is therefore an origin only. Normalize legacy values that included the
|
||||
* API prefix so old localStorage settings cannot produce /api/v1/api/v1 URLs.
|
||||
*/
|
||||
export function normalizeApiBaseUrl(value: string): string {
|
||||
const withoutTrailingSlash = value.trim().replace(/\/+$/, "");
|
||||
return withoutTrailingSlash.replace(/\/api(?:\/v1)?$/i, "");
|
||||
}
|
||||
|
||||
export function apiUrl(settings: ApiSettings, path: string): string {
|
||||
const baseUrl = normalizeApiBaseUrl(settings.apiBaseUrl);
|
||||
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
||||
return baseUrl ? `${baseUrl}${normalizedPath}` : normalizedPath;
|
||||
}
|
||||
|
||||
export function loadApiSettings(): ApiSettings {
|
||||
const storedBaseUrl = localStorage.getItem(`${STORAGE_KEY}.baseUrl`);
|
||||
const configuredBaseUrl = storedBaseUrl !== null ? storedBaseUrl : import.meta.env.VITE_API_BASE_URL ?? "";
|
||||
const apiBaseUrl = normalizeApiBaseUrl(configuredBaseUrl);
|
||||
|
||||
// Repair legacy values once loaded, while keeping an empty value for same-origin use.
|
||||
if (storedBaseUrl !== null && storedBaseUrl !== apiBaseUrl) {
|
||||
localStorage.setItem(`${STORAGE_KEY}.baseUrl`, apiBaseUrl);
|
||||
}
|
||||
|
||||
return {
|
||||
// Empty base URL means "same origin". In Vite dev, /api is proxied to FastAPI.
|
||||
apiBaseUrl:
|
||||
storedBaseUrl !== null
|
||||
? storedBaseUrl
|
||||
: import.meta.env.VITE_API_BASE_URL ?? "",
|
||||
apiBaseUrl,
|
||||
apiKey: localStorage.getItem(`${STORAGE_KEY}.apiKey`) || "",
|
||||
accessToken: localStorage.getItem(`${STORAGE_KEY}.accessToken`) || ""
|
||||
};
|
||||
}
|
||||
|
||||
export function saveApiSettings(settings: ApiSettings): void {
|
||||
localStorage.setItem(`${STORAGE_KEY}.baseUrl`, settings.apiBaseUrl);
|
||||
localStorage.setItem(`${STORAGE_KEY}.baseUrl`, normalizeApiBaseUrl(settings.apiBaseUrl));
|
||||
localStorage.setItem(`${STORAGE_KEY}.apiKey`, settings.apiKey);
|
||||
localStorage.setItem(`${STORAGE_KEY}.accessToken`, settings.accessToken);
|
||||
}
|
||||
@@ -27,9 +47,6 @@ export function clearAccessToken(): void {
|
||||
}
|
||||
|
||||
export async function apiFetch<T>(settings: ApiSettings, path: string, init?: RequestInit): Promise<T> {
|
||||
const baseUrl = settings.apiBaseUrl.trim().replace(/\/$/, "");
|
||||
const url = baseUrl ? `${baseUrl}${path}` : path;
|
||||
|
||||
const headers = new Headers(init?.headers || {});
|
||||
|
||||
if (!(init?.body instanceof FormData) && !headers.has("Content-Type")) {
|
||||
@@ -42,7 +59,7 @@ export async function apiFetch<T>(settings: ApiSettings, path: string, init?: Re
|
||||
headers.set("X-API-Key", settings.apiKey);
|
||||
}
|
||||
|
||||
const response = await fetch(url, { ...init, headers });
|
||||
const response = await fetch(apiUrl(settings, path), { ...init, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { apiFetch } from "./client";
|
||||
import { apiFetch, apiUrl } from "./client";
|
||||
import type { ApiSettings } from "../types";
|
||||
|
||||
export type FileSpace = {
|
||||
@@ -71,11 +71,6 @@ function authHeaders(settings: ApiSettings): Headers {
|
||||
return headers;
|
||||
}
|
||||
|
||||
function apiUrl(settings: ApiSettings, path: string): string {
|
||||
const baseUrl = settings.apiBaseUrl.trim().replace(/\/$/, "");
|
||||
return baseUrl ? `${baseUrl}${path}` : path;
|
||||
}
|
||||
|
||||
export function listFileSpaces(settings: ApiSettings): Promise<FileSpacesResponse> {
|
||||
return apiFetch<FileSpacesResponse>(settings, "/api/v1/files/spaces");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user