From 4160ed4540f2507a835dc0b7761cd2c2eb81767a Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Fri, 23 May 2025 13:14:26 +0300 Subject: [PATCH] stores/order: split into slices --- apps/web/stores/lib/slices/client-slice.ts | 11 +++++ apps/web/stores/lib/slices/datetime-slice.ts | 15 ++++++ apps/web/stores/lib/slices/index.ts | 6 +++ apps/web/stores/lib/slices/master-slice.ts | 11 +++++ apps/web/stores/lib/slices/service-slice.ts | 11 +++++ apps/web/stores/lib/slices/slot-slice.ts | 11 +++++ apps/web/stores/lib/slices/steps-slice.ts | 41 +++++++++++++++++ apps/web/stores/order/context.tsx | 16 ++----- apps/web/stores/order/hooks.tsx | 18 ++------ apps/web/stores/order/index.tsx | 1 - apps/web/stores/order/store.ts | 48 ++++++-------------- apps/web/stores/order/types.tsx | 44 +++++++----------- apps/web/utils/zustand/context.tsx | 29 ++++++++++++ 13 files changed, 172 insertions(+), 90 deletions(-) create mode 100644 apps/web/stores/lib/slices/client-slice.ts create mode 100644 apps/web/stores/lib/slices/datetime-slice.ts create mode 100644 apps/web/stores/lib/slices/index.ts create mode 100644 apps/web/stores/lib/slices/master-slice.ts create mode 100644 apps/web/stores/lib/slices/service-slice.ts create mode 100644 apps/web/stores/lib/slices/slot-slice.ts create mode 100644 apps/web/stores/lib/slices/steps-slice.ts create mode 100644 apps/web/utils/zustand/context.tsx diff --git a/apps/web/stores/lib/slices/client-slice.ts b/apps/web/stores/lib/slices/client-slice.ts new file mode 100644 index 0000000..2f760e0 --- /dev/null +++ b/apps/web/stores/lib/slices/client-slice.ts @@ -0,0 +1,11 @@ +import { type StateCreator } from 'zustand'; + +export type ClientSlice = { + clientId: null | string; + setClientId: (id: null | string) => void; +}; + +export const createClientSlice: StateCreator = (set) => ({ + clientId: null, + setClientId: (id) => set({ clientId: id }), +}); diff --git a/apps/web/stores/lib/slices/datetime-slice.ts b/apps/web/stores/lib/slices/datetime-slice.ts new file mode 100644 index 0000000..05f50c2 --- /dev/null +++ b/apps/web/stores/lib/slices/datetime-slice.ts @@ -0,0 +1,15 @@ +import { type StateCreator } from 'zustand'; + +export type DateTimeSlice = { + date: Date; + setDate: (date: Date) => void; + setTime: (time: null | string) => void; + time: null | string; +}; + +export const createDateTimeSlice: StateCreator = (set) => ({ + date: new Date(), + setDate: (date) => set({ date }), + setTime: (time) => set({ time }), + time: null, +}); diff --git a/apps/web/stores/lib/slices/index.ts b/apps/web/stores/lib/slices/index.ts new file mode 100644 index 0000000..e3724af --- /dev/null +++ b/apps/web/stores/lib/slices/index.ts @@ -0,0 +1,6 @@ +export * from './client-slice'; +export * from './datetime-slice'; +export * from './master-slice'; +export * from './service-slice'; +export * from './slot-slice'; +export * from './steps-slice'; diff --git a/apps/web/stores/lib/slices/master-slice.ts b/apps/web/stores/lib/slices/master-slice.ts new file mode 100644 index 0000000..2dc8d36 --- /dev/null +++ b/apps/web/stores/lib/slices/master-slice.ts @@ -0,0 +1,11 @@ +import { type StateCreator } from 'zustand'; + +export type MasterSlice = { + masterId: null | string; + setMasterId: (id: null | string) => void; +}; + +export const createMasterSlice: StateCreator = (set) => ({ + masterId: null, + setMasterId: (id) => set({ masterId: id }), +}); diff --git a/apps/web/stores/lib/slices/service-slice.ts b/apps/web/stores/lib/slices/service-slice.ts new file mode 100644 index 0000000..8ca4b15 --- /dev/null +++ b/apps/web/stores/lib/slices/service-slice.ts @@ -0,0 +1,11 @@ +import { type StateCreator } from 'zustand'; + +export type ServiceSlice = { + serviceId: null | string; + setServiceId: (id: null | string) => void; +}; + +export const createServiceSlice: StateCreator = (set) => ({ + serviceId: null, + setServiceId: (id) => set({ serviceId: id }), +}); diff --git a/apps/web/stores/lib/slices/slot-slice.ts b/apps/web/stores/lib/slices/slot-slice.ts new file mode 100644 index 0000000..8ea1122 --- /dev/null +++ b/apps/web/stores/lib/slices/slot-slice.ts @@ -0,0 +1,11 @@ +import { type StateCreator } from 'zustand'; + +export type SlotSlice = { + setSlotId: (slot: null | string) => void; + slotId: null | string; +}; + +export const createSlotSlice: StateCreator = (set) => ({ + setSlotId: (slot) => set({ slotId: slot }), + slotId: null, +}); diff --git a/apps/web/stores/lib/slices/steps-slice.ts b/apps/web/stores/lib/slices/steps-slice.ts new file mode 100644 index 0000000..64861a3 --- /dev/null +++ b/apps/web/stores/lib/slices/steps-slice.ts @@ -0,0 +1,41 @@ +/* eslint-disable canonical/id-match */ +/* eslint-disable @typescript-eslint/naming-convention */ +import { type StateCreator } from 'zustand'; + +export type Steps = + | 'client-select' + | 'datetime-select' + | 'error' + | 'loading' + | 'master-select' + | 'service-select' + | 'success'; + +export type StepsSlice = { + _setStepSequence: (steps: Steps[]) => void; + _stepSequence: Steps[]; + nextStep: () => void; + prevStep: () => void; + setStep: (step: Steps) => void; + step: Steps; +}; + +export const createStepsSlice: StateCreator = (set, get) => ({ + _setStepSequence: (steps) => set({ _stepSequence: steps }), + _stepSequence: [], + nextStep: () => { + const { _stepSequence, step } = get(); + const index = _stepSequence.indexOf(step); + const next = _stepSequence[index + 1]; + if (next) set({ step: next }); + }, + + prevStep: () => { + const { _stepSequence, step } = get(); + const index = _stepSequence.indexOf(step); + const previous = _stepSequence[index - 1]; + if (previous) set({ step: previous }); + }, + setStep: (step) => set({ step }), + step: 'loading', +}); diff --git a/apps/web/stores/order/context.tsx b/apps/web/stores/order/context.tsx index cbb9ae5..21fd3f7 100644 --- a/apps/web/stores/order/context.tsx +++ b/apps/web/stores/order/context.tsx @@ -1,17 +1,7 @@ 'use client'; - import { createOrderStore } from './store'; -import { createContext, type PropsWithChildren, useRef } from 'react'; +import { createZustandStore } from '@/utils/zustand/context'; -export type OrderStoreApi = ReturnType; +const { Provider, useZustandStore } = createZustandStore(createOrderStore); -export const OrderStoreContext = createContext(undefined); - -export function OrderStoreProvider({ children }: Readonly) { - const storeRef = useRef(null); - if (storeRef.current === null) { - storeRef.current = createOrderStore(); - } - - return {children}; -} +export { Provider as OrderStoreProvider, useZustandStore as useOrderStore }; diff --git a/apps/web/stores/order/hooks.tsx b/apps/web/stores/order/hooks.tsx index 8e830a7..b64c07d 100644 --- a/apps/web/stores/order/hooks.tsx +++ b/apps/web/stores/order/hooks.tsx @@ -1,22 +1,10 @@ /* eslint-disable canonical/id-match */ 'use client'; - -import { OrderStoreContext } from './context'; -import { type OrderStore, type Steps } from './types'; +import { useOrderStore } from './context'; +import { type Steps } from './types'; import { useCustomerQuery } from '@/hooks/api/customers'; import { Enum_Customer_Role } from '@repo/graphql/types'; -import { useContext, useEffect } from 'react'; -import { useStore } from 'zustand'; - -export const useOrderStore = (selector: (store: OrderStore) => T): T => { - const orderStoreContext = useContext(OrderStoreContext); - - if (!orderStoreContext) { - throw new Error(`useOrderStore must be used within OrderStoreProvider`); - } - - return useStore(orderStoreContext, selector); -}; +import { useEffect } from 'react'; const STEPS: Steps[] = [ 'master-select', diff --git a/apps/web/stores/order/index.tsx b/apps/web/stores/order/index.tsx index 5cc2357..c38e8e8 100644 --- a/apps/web/stores/order/index.tsx +++ b/apps/web/stores/order/index.tsx @@ -1,2 +1 @@ export * from './context'; -export { useOrderStore } from './hooks'; diff --git a/apps/web/stores/order/store.ts b/apps/web/stores/order/store.ts index f9ed626..974e8d7 100644 --- a/apps/web/stores/order/store.ts +++ b/apps/web/stores/order/store.ts @@ -1,39 +1,21 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable canonical/id-match */ +import { + createClientSlice, + createDateTimeSlice, + createMasterSlice, + createServiceSlice, + createSlotSlice, + createStepsSlice, +} from '../lib/slices'; import { type OrderStore } from './types'; import { createStore } from 'zustand'; export function createOrderStore() { - return createStore((set, get) => ({ - _setStepSequence: (steps) => set({ _stepSequence: steps }), - _stepSequence: [], - clientId: null, - date: new Date(), - - masterId: null, - nextStep: () => { - const { _stepSequence, step } = get(); - const index = _stepSequence.indexOf(step); - const next = _stepSequence[index + 1]; - if (next) set({ step: next }); - }, - - prevStep: () => { - const { _stepSequence, step } = get(); - const index = _stepSequence.indexOf(step); - const previous = _stepSequence[index - 1]; - if (previous) set({ step: previous }); - }, - serviceId: null, - setClientId: (id) => set({ clientId: id }), - setDate: (date) => set({ date }), - setMasterId: (id) => set({ masterId: id }), - setServiceId: (id) => set({ serviceId: id }), - setSlotId: (id) => set({ slotId: id }), - setStep: (step) => set({ step }), - setTime: (time) => set({ time }), - slotId: null, - step: 'loading', - time: null, + return createStore((...args) => ({ + ...createClientSlice(...args), + ...createDateTimeSlice(...args), + ...createMasterSlice(...args), + ...createServiceSlice(...args), + ...createSlotSlice(...args), + ...createStepsSlice(...args), })); } diff --git a/apps/web/stores/order/types.tsx b/apps/web/stores/order/types.tsx index ba95482..06a0382 100644 --- a/apps/web/stores/order/types.tsx +++ b/apps/web/stores/order/types.tsx @@ -1,29 +1,17 @@ -export type OrderStore = { - _setStepSequence: (steps: Steps[]) => void; - _stepSequence: Steps[]; - clientId: null | string; - date: Date; - masterId: null | string; - nextStep: () => void; - prevStep: () => void; - serviceId: null | string; - setClientId: (id: null | string) => void; - setDate: (date: Date) => void; - setMasterId: (id: null | string) => void; - setServiceId: (id: null | string) => void; - setSlotId: (slot: null | string) => void; - setStep: (step: Steps) => void; - setTime: (time: null | string) => void; - slotId: null | string; - step: Steps; - time: null | string; -}; +import { + type ClientSlice, + type DateTimeSlice, + type MasterSlice, + type ServiceSlice, + type SlotSlice, + type StepsSlice, +} from '../lib/slices'; -export type Steps = - | 'client-select' - | 'datetime-select' - | 'error' - | 'loading' - | 'master-select' - | 'service-select' - | 'success'; +export type OrderStore = ClientSlice & + DateTimeSlice & + MasterSlice & + ServiceSlice & + SlotSlice & + StepsSlice; + +export { type Steps } from '../lib/slices'; diff --git a/apps/web/utils/zustand/context.tsx b/apps/web/utils/zustand/context.tsx new file mode 100644 index 0000000..20abec6 --- /dev/null +++ b/apps/web/utils/zustand/context.tsx @@ -0,0 +1,29 @@ +import { createContext, type PropsWithChildren, useContext, useRef } from 'react'; +import { type StoreApi, useStore } from 'zustand'; + +export function createZustandStore(createStore: () => StoreApi) { + type ZustandStoreApi = StoreApi; + + const StoreContext = createContext(undefined); + + function Provider({ children }: Readonly) { + const storeRef = useRef(null); + if (storeRef.current === null) { + storeRef.current = createStore(); + } + + return {children}; + } + + const useZustandStore = (selector: (store: S) => T): T => { + const orderStoreContext = useContext(StoreContext); + + if (!orderStoreContext) { + throw new Error(`useZustandStore must be used within OrderStoreProvider`); + } + + return useStore(orderStoreContext, selector); + }; + + return { Provider, useZustandStore }; +}