Files
Work/Frontend/src/components/app-shell/data/app-shell.context.tsx
T
2026-07-06 17:02:40 +01:00

99 lines
3.2 KiB
TypeScript

// Path: Frontend/src/components/app-shell/data/app-shell.context.tsx
import { createContext, createMemo, createSignal, onMount, useContext, type JSX } from "solid-js";
import { resolveAPIBase } from "../../../lib/api";
import { applyThemePresetById, resolvePreferredThemePresetId } from "../../../helper/themeRuntime";
import {
buildActiveDepartment,
buildActiveProject,
buildActiveServer,
buildActiveUserProfile,
buildDepartmentItems,
buildProjectItems,
buildRailItems,
buildWorkspaceTree,
} from "./app-shell.builders";
import type { AppShellContextValue, AppShellPayload } from "./app-shell.types";
import { normalizeAppShellPayload } from "./app-shell.types";
const AppShellContext = createContext<AppShellContextValue>();
export const AppShellDataProvider = (props: { children: JSX.Element }): JSX.Element => {
const [status, setStatus] = createSignal<"idle" | "loading" | "success" | "error">("idle");
const [error, setError] = createSignal("");
const [payload, setPayload] = createSignal<AppShellPayload | null>(null);
const load = async (): Promise<void> => {
setStatus("loading");
setError("");
try {
const response = await fetch(`${resolveAPIBase()}/app-shell`, {
headers: {
Accept: "application/json",
},
});
const body = (await response.json()) as {
data?: AppShellPayload;
error?: { message?: string } | string;
message?: string;
};
const errorMessage =
typeof body.message === "string"
? body.message
: typeof body.error === "string"
? body.error
: body.error?.message;
if (!response.ok || !body.data) {
throw new Error(errorMessage || "Failed to load app shell state.");
}
const normalizedPayload = normalizeAppShellPayload(body.data);
const persistedThemePresetId = normalizedPayload.admin?.themePresetId?.trim();
if (persistedThemePresetId && persistedThemePresetId !== resolvePreferredThemePresetId()) {
await applyThemePresetById(persistedThemePresetId);
}
setPayload(normalizedPayload);
setStatus("success");
} catch (loadError) {
setStatus("error");
setError(loadError instanceof Error ? loadError.message : "Failed to load app shell state.");
}
};
onMount(() => {
void load();
});
const value: AppShellContextValue = {
status,
error,
installation: createMemo(() => payload()?.installation),
admin: createMemo(() => payload()?.admin),
railItems: createMemo(() => buildRailItems(payload())),
activeServer: createMemo(() => buildActiveServer(payload())),
activeProject: createMemo(() => buildActiveProject(payload())),
activeDepartment: createMemo(() => buildActiveDepartment(payload())),
projectItems: createMemo(() => buildProjectItems(payload())),
departmentItems: createMemo(() => buildDepartmentItems(payload())),
workspaceTree: createMemo(() => buildWorkspaceTree(payload())),
activeUserProfile: createMemo(() => buildActiveUserProfile(payload())),
reload: load,
};
return <AppShellContext.Provider value={value}>{props.children}</AppShellContext.Provider>;
};
export const useAppShellData = (): AppShellContextValue => {
const context = useContext(AppShellContext);
if (!context) {
throw new Error("useAppShellData must be used within AppShellDataProvider");
}
return context;
};