migrate from order context to zustand store
This commit is contained in:
parent
2bc7607800
commit
e6f2e6ccaf
@ -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;
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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) ||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
@ -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>;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -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",
|
||||
|
||||
1
apps/web/stores/order/index.tsx
Normal file
1
apps/web/stores/order/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from './store';
|
||||
52
apps/web/stores/order/store.ts
Normal file
52
apps/web/stores/order/store.ts
Normal 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,
|
||||
}));
|
||||
@ -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
26
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user