import { AuthSettingsWriteScope } from "@t3tools/contracts"; import { useEnvironmentsWithScope } from "../../state/session"; import { requiredScopesForServerSettingsPatch, EnvironmentAuthorizationError, } from "@t3tools/contracts"; import { readEnvironmentScope } from "../../state/session"; import * as Cause from "effect/Cause"; import { AsyncResult } from "effect/reactivity"; import { DEFAULT_SERVER_SETTINGS, type ProjectScopedServerSettingKey, type ServerSettings, type UnifiedSettings, } from "@t3tools/contracts"; import { persistClientSettingsPatch, useMergedSettings } from "../../hooks/useSettings"; import { serverEnvironment } from "../../state/server"; import { useAtomCommand } from "../../state/use-atom-command"; import { toastManager } from "../ui/toast"; import { useOptionalSettingsScope, useSettingsScope } from "./SettingsScopeContext"; import { persistScopedSettingsPatch, planProjectOverridesClear, planScopedSettingsClear, planScopedSettingsPatch, scopedSettingsAreMixed, scopedSettingsSource, type ProjectOverrideEntry, type ScopedSettingsPatch, } from "./scopedSettings"; export function useScopedSettings( selector?: (settings: UnifiedSettings) => T, ): T { const { target } = useSettingsScope(); return useMergedSettings(target?.settings ?? DEFAULT_SERVER_SETTINGS, selector); } export function useScopedSettingsMixed(keys: readonly (keyof ServerSettings)[]): boolean { const { targets } = useSettingsScope(); return scopedSettingsAreMixed(targets, keys); } export function useScopedSettingSource(keys: readonly (keyof ServerSettings)[]) { const { targets } = useSettingsScope(); return scopedSettingsSource(targets, keys); } function useRunScopedPlan() { const persistServer = useAtomCommand(serverEnvironment.updateSettings, { reportFailure: false }); return (plan: ReturnType, onFailed?: () => void) => { if (plan.unavailableReason) { toastManager.add({ type: "warning", title: "Setting not saved", description: plan.unavailableReason, }); onFailed?.(); return; } void persistScopedSettingsPatch( plan, async (request) => { const missing = requiredScopesForServerSettingsPatch(request.input.patch).find( (scope) => !readEnvironmentScope(request.environmentId, scope), ); if (missing) return AsyncResult.failure( Cause.fail( new EnvironmentAuthorizationError({ requiredScope: missing, message: "This connection lacks permission to change these settings.", }), ), ); return persistServer(request); }, persistClientSettingsPatch, ).then(({ failedEnvironments, savedEnvironments, savedEnvironmentCount }) => { if (failedEnvironments.length === 0) return; onFailed?.(); toastManager.add({ type: "error", title: savedEnvironmentCount > 0 ? "Setting saved on some environments" : "Setting not saved", description: [ ...failedEnvironments.map( ({ label, message }) => `Could not save on ${label}: ${message}`, ), ...(savedEnvironmentCount > 0 ? [`Saved on ${savedEnvironments.map(({ label }) => label).join(", ")}.`] : []), ].join("\n"), }); }); }; } export function useUpdateScopedSettings() { const { scope, environments } = useSettingsScope(); const run = useRunScopedPlan(); /** `onFailed` runs when any target rejects or drops the patch, so a draft can fall back. */ return (patch: ScopedSettingsPatch, options?: { readonly onFailed?: () => void }) => run(planScopedSettingsPatch(scope, environments, patch), options?.onFailed); } export function useClearScopedSettings() { const context = useOptionalSettingsScope(); const run = useRunScopedPlan(); return (keys: readonly ProjectScopedServerSettingKey[]) => { if (context === null) return; run(planScopedSettingsClear(context.scope, context.environments, keys)); }; } export function useClearProjectOverrides() { const context = useOptionalSettingsScope(); const run = useRunScopedPlan(); return ( entries: readonly ProjectOverrideEntry[], keys: readonly ProjectScopedServerSettingKey[], ) => { if (context === null) return; run(planProjectOverridesClear(context.environments, entries, keys)); }; } export function useScopedSettingsWriteAllowed() { const { connectedEnvironments } = useSettingsScope(); const writable = useEnvironmentsWithScope(connectedEnvironments, AuthSettingsWriteScope); return ( connectedEnvironments.length > 0 && connectedEnvironments.every((target) => writable.has(target.environmentId)) ); }