mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-04 00:02:05 +00:00
A drag handle between the scope rail and the list reuses useResizablePane: drag, arrow keys (Shift for a bigger step), Home/End, Enter or a double-click to restore the default 220px. The width is kept in the persisted store and clamped on rehydration; the list re-measures its cap when the rail moves.
125 lines
5.7 KiB
TypeScript
125 lines
5.7 KiB
TypeScript
import { create } from 'zustand'
|
|
import { devtools, persist } from 'zustand/middleware'
|
|
import { useShallow } from 'zustand/react/shallow'
|
|
import { createUserSlice, type UserSlice } from './slices/userSlice'
|
|
import { createOrganizationSlice, type OrganizationSlice } from './slices/organizationSlice'
|
|
import { createUISlice, clampUniboxListWidth, clampUniboxRailWidth, sanitizeNavCollapsedSections, sanitizeUniboxRailFavorites, sanitizeUniboxRailHidden, sanitizeUniboxRailOrder, type UISlice } from './slices/uiSlice'
|
|
import { createShortcutSlice, type ShortcutSlice } from './slices/shortcutSlice'
|
|
import { createDataSlice, type DataSlice } from './slices/dataSlice'
|
|
import { createRealtimeSlice, type RealtimeSlice } from './slices/realtimeSlice'
|
|
import { createCRMSlice, type CRMSlice } from './slices/crmSlice'
|
|
import { createUniboxSlice, type UniboxSlice } from './slices/uniboxSlice'
|
|
import { createAnalyticsSlice, type AnalyticsSlice } from './slices/analyticsSlice'
|
|
import { createSubscriptionSlice, type SubscriptionSlice } from './slices/subscriptionSlice'
|
|
import { createAPIKeysSlice, type APIKeysSlice } from './slices/apiKeysSlice'
|
|
import { createPresenceSlice, type PresenceSlice } from './slices/presenceSlice'
|
|
import { createAgentSlice, type AgentSlice } from './slices/agentSlice'
|
|
|
|
export type AppStore = UserSlice & OrganizationSlice & UISlice & ShortcutSlice & DataSlice
|
|
& RealtimeSlice & CRMSlice & UniboxSlice & AnalyticsSlice & SubscriptionSlice & APIKeysSlice & PresenceSlice & AgentSlice
|
|
|
|
export const useAppStore = create<AppStore>()(
|
|
devtools(
|
|
persist(
|
|
(...args) => ({
|
|
...createUserSlice(...args),
|
|
...createOrganizationSlice(...args),
|
|
...createUISlice(...args),
|
|
...createShortcutSlice(...args),
|
|
...createDataSlice(...args),
|
|
...createRealtimeSlice(...args),
|
|
...createCRMSlice(...args),
|
|
...createUniboxSlice(...args),
|
|
...createAnalyticsSlice(...args),
|
|
...createSubscriptionSlice(...args),
|
|
...createAPIKeysSlice(...args),
|
|
...createPresenceSlice(...args),
|
|
...createAgentSlice(...args),
|
|
}),
|
|
{
|
|
name: 'warmbly-storage',
|
|
// Rehydration does not go through the slice setters, so re-clamp the
|
|
// stored values that have bounds. Without this a value from an older build
|
|
// (or a hand-edited one) renders as `width: NaNpx`.
|
|
merge: (persisted, current) => {
|
|
const p = (persisted ?? {}) as Partial<AppStore>
|
|
return {
|
|
...current,
|
|
...p,
|
|
uniboxListWidth: clampUniboxListWidth(p.uniboxListWidth),
|
|
uniboxRailWidth: clampUniboxRailWidth(p.uniboxRailWidth),
|
|
navCollapsedSections: sanitizeNavCollapsedSections(p.navCollapsedSections),
|
|
uniboxRailFolded: sanitizeNavCollapsedSections(p.uniboxRailFolded),
|
|
uniboxRailHidden: sanitizeUniboxRailHidden(p.uniboxRailHidden),
|
|
uniboxRailOrder: sanitizeUniboxRailOrder(p.uniboxRailOrder),
|
|
uniboxRailSectionOrder: sanitizeUniboxRailHidden(p.uniboxRailSectionOrder),
|
|
uniboxRailFavorites: sanitizeUniboxRailFavorites(p.uniboxRailFavorites),
|
|
}
|
|
},
|
|
partialize: (state) => ({
|
|
// Only persist UI preferences
|
|
theme: state.theme,
|
|
navCollapsed: state.navCollapsed,
|
|
navCollapsedSections: state.navCollapsedSections,
|
|
// Assistant panel layout (edge + width + floating window geometry)
|
|
agentSide: state.agentSide,
|
|
agentWidth: state.agentWidth,
|
|
agentFloating: state.agentFloating,
|
|
agentFloatRect: state.agentFloatRect,
|
|
// Unibox layout (pane widths + CRM rail default)
|
|
uniboxListWidth: state.uniboxListWidth,
|
|
uniboxRailWidth: state.uniboxRailWidth,
|
|
uniboxContactRailOpen: state.uniboxContactRailOpen,
|
|
// Unibox scope rail (folds, hidden rows, row and section order, favorites)
|
|
uniboxRailFolded: state.uniboxRailFolded,
|
|
uniboxRailHidden: state.uniboxRailHidden,
|
|
uniboxRailOrder: state.uniboxRailOrder,
|
|
uniboxRailSectionOrder: state.uniboxRailSectionOrder,
|
|
uniboxRailFavorites: state.uniboxRailFavorites,
|
|
// Persist current organization selection
|
|
currentOrganization: state.currentOrganization,
|
|
}),
|
|
}
|
|
),
|
|
{
|
|
name: 'Warmbly Store',
|
|
}
|
|
)
|
|
)
|
|
|
|
// Selectors for commonly used state combinations
|
|
export const useUser = () => useAppStore((state) => state.user)
|
|
export const useIsAuthenticated = () => useAppStore((state) => state.isAuthenticated)
|
|
export const useTheme = () =>
|
|
useAppStore(useShallow((state) => ({ theme: state.theme, resolvedTheme: state.resolvedTheme })))
|
|
export const useSidebar = () =>
|
|
useAppStore(useShallow((state) => ({
|
|
collapsed: state.navCollapsed,
|
|
mobileOpen: state.sidebarMobileOpen,
|
|
toggle: state.toggleSidebar,
|
|
setCollapsed: state.setSidebarCollapsed,
|
|
setMobileOpen: state.setSidebarMobileOpen,
|
|
})))
|
|
export const useCurrentOrg = () => useAppStore((state) => state.currentOrganization)
|
|
export const useOrganizations = () =>
|
|
useAppStore(useShallow((state) => ({
|
|
organizations: state.organizations,
|
|
current: state.currentOrganization,
|
|
switch: state.switchOrganization,
|
|
})))
|
|
export const useCachedData = () =>
|
|
useAppStore(useShallow((state) => ({
|
|
campaigns: state.campaigns,
|
|
emails: state.emails,
|
|
tags: state.tags,
|
|
folders: state.folders,
|
|
categories: state.categories,
|
|
})))
|
|
export const useConnectionStatus = () =>
|
|
useAppStore(useShallow((state) => ({
|
|
status: state.connectionStatus,
|
|
quality: state.connectionQuality,
|
|
reconnectAttempt: state.reconnectAttempt,
|
|
})))
|
|
export const useUnseenCount = () => useAppStore((state) => state.unseenCount)
|