182 lines
5.1 KiB
TypeScript
182 lines
5.1 KiB
TypeScript
/**
|
|
* Memoized Redux Selectors
|
|
*
|
|
* Centralized selectors using createSelector for performance optimization.
|
|
* These selectors prevent unnecessary re-renders by memoizing derived state.
|
|
*/
|
|
|
|
import { createSelector } from '@reduxjs/toolkit';
|
|
import type { RootState } from './store';
|
|
|
|
// ============================================================================
|
|
// Auth Selectors
|
|
// ============================================================================
|
|
|
|
/** Select auth slice */
|
|
const selectAuthState = (state: RootState) => state.auth;
|
|
|
|
/** Select current user with memoization */
|
|
export const selectCurrentUser = createSelector(
|
|
[selectAuthState],
|
|
(auth) => auth.currentUser,
|
|
);
|
|
|
|
/** Select if user is authenticated */
|
|
export const selectIsAuthenticated = createSelector(
|
|
[selectCurrentUser],
|
|
(user) => !!user,
|
|
);
|
|
|
|
/** Select user's role */
|
|
export const selectUserRole = createSelector(
|
|
[selectCurrentUser],
|
|
(user) => user?.app_role,
|
|
);
|
|
|
|
/** Select user's permissions */
|
|
export const selectUserPermissions = createSelector(
|
|
[selectUserRole],
|
|
(role) => {
|
|
if (!role) return [];
|
|
return role.permissions || [];
|
|
},
|
|
);
|
|
|
|
// ============================================================================
|
|
// Entity Selectors Factory
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create memoized selectors for an entity slice
|
|
*
|
|
* @example
|
|
* const { selectData, selectCount, selectLoading } = createEntitySelectors('assets');
|
|
* const assets = useAppSelector(selectData);
|
|
*/
|
|
export function createEntitySelectors<T>(sliceName: keyof RootState) {
|
|
type EntityState = {
|
|
data: T[];
|
|
loading: boolean;
|
|
count: number;
|
|
refetch: boolean;
|
|
};
|
|
|
|
const selectSlice = (state: RootState) =>
|
|
state[sliceName] as unknown as EntityState;
|
|
|
|
const selectData = createSelector([selectSlice], (slice) => slice.data);
|
|
|
|
const selectLoading = createSelector([selectSlice], (slice) => slice.loading);
|
|
|
|
const selectCount = createSelector([selectSlice], (slice) => slice.count);
|
|
|
|
const selectRefetch = createSelector([selectSlice], (slice) => slice.refetch);
|
|
|
|
const selectFirstItem = createSelector([selectData], (data) => data[0]);
|
|
|
|
const selectById = (id: string) =>
|
|
createSelector([selectData], (data) =>
|
|
data.find((item: T & { id: string }) => item.id === id),
|
|
);
|
|
|
|
return {
|
|
selectSlice,
|
|
selectData,
|
|
selectLoading,
|
|
selectCount,
|
|
selectRefetch,
|
|
selectFirstItem,
|
|
selectById,
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Pre-built Entity Selectors
|
|
// ============================================================================
|
|
|
|
import type { Asset, TourPage, Project, Role, User } from '../types/entities';
|
|
|
|
/** Asset selectors */
|
|
export const assetSelectors = createEntitySelectors<Asset>('assets');
|
|
|
|
/** Tour page selectors */
|
|
export const tourPageSelectors = createEntitySelectors<TourPage>('tour_pages');
|
|
|
|
/** Project selectors */
|
|
export const projectSelectors = createEntitySelectors<Project>('projects');
|
|
|
|
/** Role selectors */
|
|
export const roleSelectors = createEntitySelectors<Role>('roles');
|
|
|
|
/** User selectors */
|
|
export const userSelectors = createEntitySelectors<User>('users');
|
|
|
|
// ============================================================================
|
|
// Style Selectors
|
|
// ============================================================================
|
|
|
|
/** Select style slice */
|
|
const selectStyleState = (state: RootState) => state.style;
|
|
|
|
/** Select dark mode with memoization */
|
|
export const selectDarkMode = createSelector(
|
|
[selectStyleState],
|
|
(style) => style.darkMode,
|
|
);
|
|
|
|
/** Select background layout color */
|
|
export const selectBgLayoutColor = createSelector(
|
|
[selectStyleState],
|
|
(style) => style.bgLayoutColor,
|
|
);
|
|
|
|
/** Select focus ring color */
|
|
export const selectFocusRingColor = createSelector(
|
|
[selectStyleState],
|
|
(style) => style.focusRingColor,
|
|
);
|
|
|
|
/** Select corners style */
|
|
export const selectCorners = createSelector(
|
|
[selectStyleState],
|
|
(style) => style.corners,
|
|
);
|
|
|
|
/** Select cards color */
|
|
export const selectCardsColor = createSelector(
|
|
[selectStyleState],
|
|
(style) => style.cardsColor,
|
|
);
|
|
|
|
// ============================================================================
|
|
// Constructor Selectors
|
|
// ============================================================================
|
|
|
|
/** Select constructor slice */
|
|
const selectConstructorState = (state: RootState) => state.constructorUI;
|
|
|
|
/** Select editor tab */
|
|
export const selectEditorTab = createSelector(
|
|
[selectConstructorState],
|
|
(constructor) => constructor.editorTab,
|
|
);
|
|
|
|
/** Select if menu is open */
|
|
export const selectIsMenuOpen = createSelector(
|
|
[selectConstructorState],
|
|
(constructor) => constructor.isMenuOpen,
|
|
);
|
|
|
|
/** Select if editor is collapsed */
|
|
export const selectIsEditorCollapsed = createSelector(
|
|
[selectConstructorState],
|
|
(constructor) => constructor.isEditorCollapsed,
|
|
);
|
|
|
|
/** Select last active page ID for a project */
|
|
export const selectLastActivePageId = (projectId: string) =>
|
|
createSelector(
|
|
[selectConstructorState],
|
|
(constructor) => constructor.lastActivePageId[projectId],
|
|
);
|