import { FetchError } from "@medusajs/js-sdk" import { HttpTypes } from "@medusajs/types" import { toast } from "@medusajs/ui" import { MutateOptions } from "@tanstack/react-query" import { useCallback, useMemo } from "react" import { useTranslation } from "react-i18next" import type { LayoutPreference } from "../components/layout-composer/types" import { useLayoutConfiguration, useSetLayoutConfiguration, } from "./api/layouts" const EMPTY_PREFERENCE: LayoutPreference = { widgets: {} } export type LayoutScope = "personal" | "default" export type SetPreferenceOptions = { /** * Persist as the zone's system default (applies to all users) instead of the * current user's personal configuration. */ asDefault?: boolean } /** * Per-call react-query mutate options for `setPreference`, used to run * caller-specific side effects (e.g. exiting edit mode) once the save settles. * Generic toasts are already handled inside the hook. */ export type SetPreferenceMutateOptions = MutateOptions< HttpTypes.AdminLayoutConfigurationResponse, FetchError, HttpTypes.AdminSetLayoutConfiguration > export type UseLayoutPreferenceReturn = { /** The current user's personal configuration, seeded from the default when unset. */ personalPreference: LayoutPreference /** The zone's system default configuration. */ defaultPreference: LayoutPreference /** The persisted scope the user is actively viewing for this zone. */ activeScope: LayoutScope /** * Which scope actually has a saved configuration for this zone, or `null` * when neither the user nor the system has defined one. Personal takes * precedence since it overrides the system default for the current user. */ definedScope: LayoutScope | null setPreference: ( next: LayoutPreference, options?: SetPreferenceOptions, mutateOptions?: SetPreferenceMutateOptions ) => void isSaving: boolean } function toPreference( configuration?: { widgets?: LayoutPreference["widgets"] } | null ): LayoutPreference | null { const widgets = configuration?.widgets return widgets ? { widgets } : null } export function useLayoutPreference(zone: string): UseLayoutPreferenceReturn { const { t } = useTranslation() const { personal_configuration, default_configuration, active_scope } = useLayoutConfiguration(zone) const { mutate: setLayoutConfiguration, isPending: isSaving } = useSetLayoutConfiguration(zone, { onSuccess: () => toast.success(t("layout.saveSuccess")), onError: (error) => toast.error(error.message), }) const defaultPreference = useMemo( () => toPreference(default_configuration?.configuration) ?? EMPTY_PREFERENCE, [default_configuration] ) const personalPreference = useMemo( () => toPreference(personal_configuration?.configuration) ?? defaultPreference, [personal_configuration, defaultPreference] ) const activeScope: LayoutScope = active_scope ?? "personal" // A configuration record only exists once a user or admin has saved one, so // its presence is the signal that a layout has been deliberately defined. // Personal wins over default since it's what the current user actually sees. const definedScope: LayoutScope | null = personal_configuration ? "personal" : default_configuration ? "default" : null const setPreference = useCallback( ( next: LayoutPreference, options?: SetPreferenceOptions, mutateOptions?: SetPreferenceMutateOptions ) => { setLayoutConfiguration( { is_default: options?.asDefault ?? false, configuration: { widgets: next.widgets }, }, mutateOptions ) }, [setLayoutConfiguration] ) return { personalPreference, defaultPreference, activeScope, definedScope, setPreference, isSaving, } }