migrate from order context to zustand store

This commit is contained in:
vchikalkin 2025-04-29 17:48:11 +03:00
parent 2bc7607800
commit e6f2e6ccaf
14 changed files with 127 additions and 173 deletions

View File

@ -1,13 +1,13 @@
'use client';
import { OrderContext } from '@/context/order';
import { useOrderStore } from '@/stores/order';
import { Button } from '@repo/ui/components/ui/button';
import { use } from 'react';
export function BackButton() {
const { prevStep, step } = use(OrderContext);
const step = useOrderStore((store) => store.step);
const previousStep = useOrderStore((store) => store.prevStep);
function handleOnClick() {
prevStep();
previousStep();
}
if (['master-select', 'success'].includes(step)) return null;

View File

@ -2,14 +2,15 @@
import { ContactsGridBase } from './components';
import { LoadingSpinner } from '@/components/common/spinner';
import { ContactsFilterProvider } from '@/context/contacts-filter';
import { OrderContext } from '@/context/order';
import { useCustomerContacts } from '@/hooks/contacts';
import { useOrderStore } from '@/stores/order';
import { withContext } from '@/utils/context';
import { use, useEffect } from 'react';
import { useEffect } from 'react';
export const MastersGrid = withContext(ContactsFilterProvider)(function () {
const { contacts, isLoading, setFilter } = useCustomerContacts({ includeSelf: true });
const { masterId, setMasterId } = use(OrderContext);
const masterId = useOrderStore((store) => store.masterId);
const setMasterId = useOrderStore((store) => store.setMasterId);
useEffect(() => {
setFilter('masters');
@ -29,7 +30,8 @@ export const MastersGrid = withContext(ContactsFilterProvider)(function () {
export const ClientsGrid = withContext(ContactsFilterProvider)(function () {
const { contacts, isLoading, setFilter } = useCustomerContacts();
const { clientId, setClientId } = use(OrderContext);
const clientId = useOrderStore((store) => store.clientId);
const setClientId = useOrderStore((store) => store.setClientId);
useEffect(() => {
setFilter('clients');

View File

@ -1,11 +1,11 @@
'use client';
import { OrderContext } from '@/context/order';
import { useOrderStore } from '@/stores/order';
import { Calendar } from '@repo/ui/components/ui/calendar';
import dayjs from 'dayjs';
import { use } from 'react';
export function DateSelect() {
const { date: selectedDate, setDate } = use(OrderContext);
const selectedDate = useOrderStore((store) => store.date);
const setDate = useOrderStore((store) => store.setDate);
return (
<Calendar

View File

@ -1,9 +1,8 @@
'use client';
import { OrderContext } from '@/context/order';
import { useServicesQuery } from '@/hooks/service';
import { useOrderStore } from '@/stores/order';
import { type ServiceFieldsFragment } from '@repo/graphql/types';
import { cn } from '@repo/ui/lib/utils';
import { use } from 'react';
export function ServiceSelect() {
const { data } = useServicesQuery();
@ -18,7 +17,8 @@ export function ServiceSelect() {
}
function ServiceCard({ documentId, name }: Readonly<ServiceFieldsFragment>) {
const { serviceId, setServiceId } = use(OrderContext);
const serviceId = useOrderStore((store) => store.serviceId);
const setServiceId = useOrderStore((store) => store.setServiceId);
const selected = serviceId === documentId;

View File

@ -1,10 +1,10 @@
'use client';
import { OrderContext } from '@/context/order';
import { useOrderStore } from '@/stores/order';
import { Button } from '@repo/ui/components/ui/button';
import { use } from 'react';
export function SubmitButton() {
const { nextStep, step } = use(OrderContext);
const step = useOrderStore((store) => store.step);
const nextStep = useOrderStore((store) => store.nextStep);
function handleOnClick() {
if (step !== 'success') {
@ -22,7 +22,10 @@ export function SubmitButton() {
}
function useButtonDisabled() {
const { clientId, masterId, serviceId, step } = use(OrderContext);
const clientId = useOrderStore((state) => state.clientId);
const masterId = useOrderStore((state) => state.masterId);
const serviceId = useOrderStore((state) => state.serviceId);
const step = useOrderStore((state) => state.step);
return (
(step === 'master-select' && !masterId) ||

View File

@ -7,9 +7,9 @@ import {
ServiceSelect,
SubmitButton,
} from './components';
import { OrderContext, OrderContextProvider } from '@/context/order';
import { withContext } from '@/utils/context';
import { type JSX, use } from 'react';
import { useProfileQuery } from '@/hooks/profile';
import { useOrderStore } from '@/stores/order';
import { type JSX, useEffect } from 'react';
const STEP_COMPONENTS: Record<string, JSX.Element> = {
'client-select': <ClientsGrid />,
@ -18,12 +18,14 @@ const STEP_COMPONENTS: Record<string, JSX.Element> = {
'service-select': <ServiceSelect />,
};
function getStepComponent(step: string) {
return STEP_COMPONENTS[step] ?? null;
}
export function OrderForm() {
const { data: customer } = useProfileQuery();
const step = useOrderStore((store) => store.step);
const initStepSequence = useOrderStore((store) => store.initStepSequence);
export const OrderForm = withContext(OrderContextProvider)(function () {
const { step } = use(OrderContext);
useEffect(() => {
initStepSequence(customer?.role);
}, [customer?.role, initStepSequence]);
return (
<div className="space-y-4 [&>*]:px-4">
@ -34,4 +36,8 @@ export const OrderForm = withContext(OrderContextProvider)(function () {
</div>
</div>
);
});
}
function getStepComponent(step: string) {
return STEP_COMPONENTS[step] ?? null;
}

View File

@ -1,34 +0,0 @@
/* eslint-disable canonical/id-match */
'use client';
import { useStepsReducer } from './reducer';
import { type Steps } from './types';
import { useProfileQuery } from '@/hooks/profile';
import { Enum_Customer_Role } from '@repo/graphql/types';
import { useEffect, useReducer, useState } from 'react';
export function useAutoSetClient({ entityId, setEntityId }: ReturnType<typeof useEntityState>) {
const { data: profile } = useProfileQuery();
useEffect(() => {
if (profile?.role === Enum_Customer_Role.Client && !entityId) {
setEntityId(profile.documentId);
}
}, [entityId, profile?.documentId, profile?.role, setEntityId]);
}
export function useEntityState() {
const [entityId, setEntityId] = useState<null | string>(null);
return { entityId, setEntityId };
}
export function useStep() {
const stepsReducer = useStepsReducer();
const [state, dispatch] = useReducer(stepsReducer, { step: 'master-select' });
const setStep = (payload: Steps) => dispatch({ payload, type: 'SET_STEP' });
const nextStep = () => dispatch({ type: 'NEXT_STEP' });
const previousStep = () => dispatch({ type: 'PREV_STEP' });
return { nextStep, prevStep: previousStep, setStep, ...state };
}

View File

@ -1,54 +0,0 @@
'use client';
import { useAutoSetClient, useEntityState, useStep } from './hooks';
import { type ContextType } from './types';
import { createContext, type PropsWithChildren, useMemo, useState } from 'react';
export const OrderContext = createContext<ContextType>({} as ContextType);
export function OrderContextProvider({ children }: Readonly<PropsWithChildren>) {
const { entityId: masterId, setEntityId: setMasterId } = useEntityState();
const { entityId: serviceId, setEntityId: setServiceId } = useEntityState();
const { entityId: clientId, setEntityId: setClientId } = useEntityState();
const [date, setDate] = useState<Date>(new Date());
const [time, setTime] = useState<null | string>(null);
const { nextStep, prevStep, setStep, step } = useStep();
useAutoSetClient({ entityId: clientId, setEntityId: setClientId });
const value = useMemo(
() => ({
clientId,
date,
masterId,
nextStep,
prevStep,
serviceId,
setClientId,
setDate,
setMasterId,
setServiceId,
setStep,
setTime,
step,
time,
}),
[
clientId,
date,
masterId,
nextStep,
prevStep,
serviceId,
setClientId,
setMasterId,
setServiceId,
setStep,
step,
time,
],
);
return <OrderContext value={value}>{children}</OrderContext>;
}

View File

@ -1,46 +0,0 @@
/* eslint-disable canonical/id-match */
import { type Action, type State, type Steps } from './types';
import { useProfileQuery } from '@/hooks/profile';
import { Enum_Customer_Role } from '@repo/graphql/types';
const masterSteps: Steps[] = [
'master-select',
'client-select',
'service-select',
'datetime-select',
'success',
];
const clientSteps = masterSteps.filter((step) => step !== 'client-select');
export function useStepsReducer() {
const { data: profile } = useProfileQuery();
const stepsSequence = profile?.role === Enum_Customer_Role.Master ? masterSteps : clientSteps;
return function (state: State, action: Action): State {
switch (action.type) {
case 'NEXT_STEP': {
const currentIndex = stepsSequence.indexOf(state.step);
const nextIndex = currentIndex + 1;
const nextStep = stepsSequence[nextIndex];
return nextStep ? { ...state, step: nextStep } : state;
}
case 'PREV_STEP': {
const currentIndex = stepsSequence.indexOf(state.step);
const previousIndex = currentIndex - 1;
const previousStep = stepsSequence[previousIndex];
return previousStep ? { ...state, step: previousStep } : state;
}
case 'SET_STEP': {
return { ...state, step: action.payload };
}
default:
return state;
}
};
}

View File

@ -28,7 +28,8 @@
"react": "catalog:",
"react-dom": "catalog:",
"use-debounce": "^10.0.4",
"zod": "catalog:"
"zod": "catalog:",
"zustand": "^5.0.3"
},
"devDependencies": {
"@playwright/test": "^1.49.1",

View File

@ -0,0 +1 @@
export * from './store';

View File

@ -0,0 +1,52 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable canonical/id-match */
import { type OrderStore, type Steps } from './types';
import { Enum_Customer_Role } from '@repo/graphql/types';
import { create } from 'zustand';
const MASTER_STEPS: Steps[] = [
'master-select',
'client-select',
'service-select',
'datetime-select',
'success',
];
const CLIENT_STEPS: Steps[] = MASTER_STEPS.filter((step) => step !== 'client-select');
export const useOrderStore = create<OrderStore>((set, get) => ({
_stepSequence: [],
clientId: null,
date: new Date(),
initStepSequence: (role) => {
const steps = role === Enum_Customer_Role.Master ? MASTER_STEPS : CLIENT_STEPS;
const initialStep = steps[0];
set({ _stepSequence: steps, step: initialStep });
},
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 }),
setStep: (step) => set({ step }),
setTime: (time) => set({ time }),
step: 'master-select',
time: null,
}));

View File

@ -1,27 +1,24 @@
export type Action =
| { payload: Steps; type: 'SET_STEP' }
| { type: 'NEXT_STEP' }
| { type: 'PREV_STEP' };
import { type Enum_Customer_Role } from '@repo/graphql/types';
export type ContextType = {
export type OrderStore = {
_stepSequence: Steps[];
clientId: null | string;
date: Date;
initStepSequence: (role: Enum_Customer_Role | undefined) => void;
masterId: null | string;
nextStep: () => void;
prevStep: () => void;
serviceId: null | string;
setClientId: (clientId: null | string) => void;
setClientId: (id: null | string) => void;
setDate: (date: Date) => void;
setMasterId: (customerId: null | string) => void;
setServiceId: (serviceId: null | string) => void;
setMasterId: (id: null | string) => void;
setServiceId: (id: null | string) => void;
setStep: (step: Steps) => void;
setTime: (time: string) => void;
setTime: (time: null | string) => void;
step: Steps;
time: null | string;
};
export type State = { step: Steps };
export type Steps =
| 'client-select'
| 'datetime-select'

26
pnpm-lock.yaml generated
View File

@ -183,6 +183,9 @@ importers:
zod:
specifier: 'catalog:'
version: 3.24.1
zustand:
specifier: ^5.0.3
version: 5.0.3(@types/react@19.1.2)(react@19.1.0)
devDependencies:
'@playwright/test':
specifier: ^1.49.1
@ -6170,6 +6173,24 @@ packages:
zod@3.24.1:
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
zustand@5.0.3:
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=18.0.0'
immer: '>=9.0.6'
react: '>=18.0.0'
use-sync-external-store: '>=1.2.0'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
snapshots:
'@alloc/quick-lru@5.2.0': {}
@ -12821,3 +12842,8 @@ snapshots:
zen-observable@0.8.15: {}
zod@3.24.1: {}
zustand@5.0.3(@types/react@19.1.2)(react@19.1.0):
optionalDependencies:
'@types/react': 19.1.2
react: 19.1.0