diff --git a/apps/accounts/serializers.py b/apps/accounts/serializers.py index 66c665fc..c90ffef4 100644 --- a/apps/accounts/serializers.py +++ b/apps/accounts/serializers.py @@ -115,10 +115,20 @@ class UserSerializer(serializers.ModelSerializer): channel_profiles = validated_data.pop("channel_profiles", None) # Merge custom_properties instead of replacing (prevents data loss) + # Strip null values — sending null for a key omits it rather than overwriting with null custom_properties = validated_data.pop("custom_properties", None) if custom_properties is not None: existing = instance.custom_properties or {} - instance.custom_properties = {**existing, **custom_properties} + cleaned = {k: v for k, v in custom_properties.items() if v is not None} + merged = {**existing, **cleaned} + # Scrub stale nav IDs so the DB self-heals on next save + for nav_field in ('navOrder', 'hiddenNav'): + if nav_field in merged and isinstance(merged[nav_field], list): + merged[nav_field] = [ + item for item in merged[nav_field] + if item in VALID_NAV_ITEM_IDS + ] + instance.custom_properties = merged for attr, value in validated_data.items(): setattr(instance, attr, value) diff --git a/frontend/src/store/auth.jsx b/frontend/src/store/auth.jsx index 8f0362c7..6d36e88c 100644 --- a/frontend/src/store/auth.jsx +++ b/frontend/src/store/auth.jsx @@ -8,6 +8,7 @@ import useUserAgentsStore from './userAgents'; import useUsersStore from './users'; import API from '../api'; import { USER_LEVELS } from '../constants'; +import { DEFAULT_ADMIN_ORDER, DEFAULT_USER_ORDER } from '../config/navigation'; const decodeToken = (token) => { if (!token) return null; @@ -76,7 +77,13 @@ const useAuthStore = create((set, get) => ({ getHiddenNav: () => { const user = get().user; - return user?.custom_properties?.hiddenNav || []; + const hiddenNav = user?.custom_properties?.hiddenNav || []; + // Filter out stale IDs that are no longer valid for this user's role + const isAdmin = user?.user_level >= USER_LEVELS.ADMIN; + const validIds = new Set( + isAdmin ? DEFAULT_ADMIN_ORDER : DEFAULT_USER_ORDER + ); + return hiddenNav.filter((id) => validIds.has(id)); }, toggleNavVisibility: async (itemId) => {