diff --git a/admin-ui/src/app/providers.tsx b/admin-ui/src/app/providers.tsx
index f9a5528..361f1e7 100644
--- a/admin-ui/src/app/providers.tsx
+++ b/admin-ui/src/app/providers.tsx
@@ -3,6 +3,18 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useState, useEffect } from "react";
import { syncStoredApiKey } from "@/lib/api";
+import { EnvironmentProvider, useEnvironment } from "@/lib/EnvironmentContext";
+
+/** Invalidates all queries whenever the active environment changes. */
+function QueryInvalidatorOnEnvChange({ queryClient }: { queryClient: QueryClient }) {
+ const { environment } = useEnvironment();
+ useEffect(() => {
+ if (environment != null) {
+ void queryClient.invalidateQueries();
+ }
+ }, [environment, queryClient]);
+ return null;
+}
export function Providers({ children }: { children: React.ReactNode }) {
useEffect(() => {
@@ -17,7 +29,13 @@ export function Providers({ children }: { children: React.ReactNode }) {
},
})
);
+
return (
- {children}
+
+
+
+ {children}
+
+
);
}
diff --git a/admin-ui/src/components/EnvironmentSwitcher.tsx b/admin-ui/src/components/EnvironmentSwitcher.tsx
new file mode 100644
index 0000000..a42b030
--- /dev/null
+++ b/admin-ui/src/components/EnvironmentSwitcher.tsx
@@ -0,0 +1,60 @@
+"use client";
+
+import { useEffect } from "react";
+import { useQuery } from "@tanstack/react-query";
+import { useTranslations } from "next-intl";
+import { fetchEnvironments } from "@/lib/api";
+import { useEnvironment } from "@/lib/EnvironmentContext";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+
+export function EnvironmentSwitcher() {
+ const t = useTranslations("EnvironmentSwitcher");
+ const { environment, setEnvironment } = useEnvironment();
+ const { data } = useQuery({
+ queryKey: ["environments"],
+ queryFn: fetchEnvironments,
+ });
+
+ const environments = data?.environments ?? [];
+ const defaultEnv = data?.default ?? null;
+
+ useEffect(() => {
+ if (environments.length > 0 && !environment && defaultEnv) {
+ setEnvironment(defaultEnv);
+ }
+ }, [environments.length, environment, defaultEnv, setEnvironment]);
+
+ if (environments.length === 0) return null;
+
+ const value = environment ?? defaultEnv ?? environments[0];
+
+ const handleChange = (next: string) => {
+ setEnvironment(next);
+ };
+
+ return (
+
+
+ {t("label")}:
+
+
+
+ );
+}
diff --git a/admin-ui/src/lib/EnvironmentContext.tsx b/admin-ui/src/lib/EnvironmentContext.tsx
new file mode 100644
index 0000000..586c3d9
--- /dev/null
+++ b/admin-ui/src/lib/EnvironmentContext.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { createContext, useContext, useState, useEffect, type ReactNode } from "react";
+import { getCurrentEnvironment, setCurrentEnvironment } from "@/lib/api";
+
+type EnvironmentContextType = {
+ environment: string | null;
+ setEnvironment: (env: string) => void;
+};
+
+const EnvironmentContext = createContext({
+ environment: null,
+ setEnvironment: () => {},
+});
+
+export function EnvironmentProvider({ children }: { children: ReactNode }) {
+ const [environment, setEnv] = useState(null);
+
+ useEffect(() => {
+ setEnv(getCurrentEnvironment());
+ }, []);
+
+ function setEnvironment(env: string) {
+ setCurrentEnvironment(env);
+ setEnv(env);
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useEnvironment() {
+ return useContext(EnvironmentContext);
+}