diff --git a/frontend/src/client.ts b/frontend/src/client.ts index 55705c0..97ca081 100644 --- a/frontend/src/client.ts +++ b/frontend/src/client.ts @@ -1,7 +1,9 @@ import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; +import { apiUrl } from "./utils/api"; + const client = new ApolloClient({ - link: new HttpLink({ uri: import.meta.env.DEV ? "/graphql/" : `${import.meta.env.VITE_API_URL}/graphql/` }), + link: new HttpLink({ uri: apiUrl("/graphql/") }), cache: new InMemoryCache(), }); diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 5cb3147..a812671 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -3,8 +3,9 @@ import React from "react"; import { NavLink } from "react-router-dom"; import styles from "./Header.module.css"; +import { apiUrl } from "../utils/api"; -const ADMIN_URL = `${import.meta.env.VITE_API_URL}/admin/`; +const ADMIN_URL = apiUrl("/admin/"); const navLinkStyles = ({ isActive }: { isActive: boolean }) => clsx(styles.navLink, { [styles.active]: isActive }); diff --git a/frontend/src/features/inventory/InventoryAdd.tsx b/frontend/src/features/inventory/InventoryAdd.tsx index 9c85b03..3af6f29 100644 --- a/frontend/src/features/inventory/InventoryAdd.tsx +++ b/frontend/src/features/inventory/InventoryAdd.tsx @@ -6,8 +6,9 @@ import styles from "./Inventory.module.css"; import { GQLError } from "../../components/GQLError"; import { useToast } from "../../hooks/useToast"; import { CreateInventoryTransactionDocument, InventoryProductsDocument } from "../../types"; +import { loginUrl } from "../../utils/api"; -const LOGIN_URL = `${import.meta.env.VITE_API_URL}/accounts/login/?next=/inventory/add`; +const LOGIN_URL = loginUrl("/inventory/add"); const InventoryAdd = () => { const navigate = useNavigate(); diff --git a/frontend/src/utils/api.ts b/frontend/src/utils/api.ts new file mode 100644 index 0000000..f238e95 --- /dev/null +++ b/frontend/src/utils/api.ts @@ -0,0 +1,6 @@ +// URL of a backend path such as /graphql/. In development this goes through Vite's proxy on the app's +// own address (see vite.config.ts), so the login cookie Django sets belongs to the host the app runs on; +// browsers keep cookies for localhost and 127.0.0.1 apart. Builds use VITE_API_URL. +export const apiUrl = (path: string) => (import.meta.env.DEV ? path : `${import.meta.env.VITE_API_URL}${path}`); + +export const loginUrl = (next: string) => apiUrl(`/accounts/login/?next=${encodeURIComponent(next)}`); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 74e782c..ca4ea98 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -13,9 +13,15 @@ export default defineConfig({ server: { host: "127.0.0.1", proxy: Object.fromEntries( - ["/admin", "/accounts", "/static", "/media", "/graphql"].map( - (path) => [path, process.env.VITE_DEV_API_PROXY || "http://127.0.0.1:3000"], - ), + ["/admin", "/accounts", "/static", "/media", "/graphql"].map((path) => [ + path, + { + target: process.env.VITE_DEV_API_PROXY || "http://127.0.0.1:3000", + // Django only accepts a login form posted from the Host it was sent to (CSRF), so keep the + // app's own Host for the local backend. A remote backend needs its own Host to be reached. + changeOrigin: !!process.env.VITE_DEV_API_PROXY, + }, + ]), ), }, build: { outDir: "../static", emptyOutDir: false },