import { PatchUserRequest, UserDocumentTypes, UserDocuments, UserSettingsFieldStatus, UserSettingsField, User } from "@root/model/user"; import { produce } from "immer"; import { create } from "zustand"; import { createJSONStorage, devtools, persist } from "zustand/middleware"; import { StringSchema, string } from "yup"; import { patchUser } from "@root/api/user"; import { UserAccount, UserAccountSettingsFieldStatus, UserName, VerificationStatus } from "@root/model/account"; import { patchUserAccount } from "@root/api/account"; import { deleteCart, patchCart } from "@root/api/cart"; interface UserStore { userId: string | null; user: User | null; userAccount: UserAccount | null; settingsFields: ( UserSettingsFieldStatus & UserAccountSettingsFieldStatus & { hasError: boolean; } ); verificationStatus: VerificationStatus; verificationType: "juridical" | "nko"; isDocumentsDialogOpen: boolean; dialogType: "juridical" | "nko"; documents: UserDocuments; } const defaultFieldValues = { value: "", error: null, touched: false, }; const defaultFields: UserStore["settingsFields"] = { firstname: { ...defaultFieldValues }, secondname: { ...defaultFieldValues }, middlename: { ...defaultFieldValues }, orgname: { ...defaultFieldValues }, email: { ...defaultFieldValues }, phoneNumber: { ...defaultFieldValues }, password: { ...defaultFieldValues }, hasError: false, }; const defaultDocument = { file: null, uploadedFileName: null, imageSrc: null, }; const initialState: UserStore = { userId: null, user: null, userAccount: null, settingsFields: { ...defaultFields }, verificationStatus: "notVerificated", verificationType: "juridical", isDocumentsDialogOpen: false, dialogType: "juridical", documents: { "ИНН": { ...defaultDocument }, "Устав": { ...defaultDocument }, "Свидетельство о регистрации НКО": { ...defaultDocument }, }, }; export const useUserStore = create()( persist( devtools( (set, get) => initialState, { name: "User", enabled: process.env.NODE_ENV === "development", trace: true, } ), { version: 2, name: "user", storage: createJSONStorage(() => localStorage), partialize: state => ({ userId: state.userId, user: state.user, }), migrate: (persistedState, version) => ({ ...persistedState as UserStore, user: null, }), } ) ); export const setUserId = (userId: string | null) => useUserStore.setState({ userId }); export const setUser = (user: User) => useUserStore.setState( produce(state => { state.user = user; state.settingsFields.email.value = user?.email ?? ""; state.settingsFields.phoneNumber.value = user?.phoneNumber ?? ""; state.settingsFields.password.value = ""; }) ); export const setUserAccount = (user: UserAccount) => useUserStore.setState( produce(state => { state.userAccount = user; state.settingsFields.firstname.value = user?.name.firstname ?? ""; state.settingsFields.secondname.value = user?.name.secondname ?? ""; state.settingsFields.middlename.value = user?.name.middlename ?? ""; state.settingsFields.orgname.value = user?.name.orgname ?? ""; }), false, { type: "setUserAccount", payload: user, } ); export const setCart = (cart: string[]) => useUserStore.setState( produce(state => { if (state.userAccount) state.userAccount.cart = cart; }) ); export const clearUserData = () => useUserStore.setState({ ...initialState }); export const openDocumentsDialog = (type: UserStore["dialogType"]) => useUserStore.setState( produce(state => { state.isDocumentsDialogOpen = true; state.dialogType = type; }) ); export const closeDocumentsDialog = () => useUserStore.setState( produce(state => { state.isDocumentsDialogOpen = false; }) ); export const setDocument = (type: UserDocumentTypes, file: File | undefined) => useUserStore.setState( produce(state => { if (!file) { state.documents[type] = { ...defaultDocument }; return; } let imageSrc: string | null = null; try { const src = state.documents[type].imageSrc; if (src) URL.revokeObjectURL(src); imageSrc = URL.createObjectURL(file); } catch (error) { console.log("Error creating object url", error); } state.documents[type] = { file, uploadedFileName: null, imageSrc, }; }) ); export const setUploadedDocument = (type: UserDocumentTypes, fileName: string, url: string) => useUserStore.setState( produce(state => { state.documents[type] = { file: null, uploadedFileName: fileName, imageSrc: url, }; }) ); export const sendDocuments = () => { const state = useUserStore.getState(); const type = state.dialogType; const documents = state.documents; // const formData = new FormData(); // formData.append("file1", file1); // formData.append("file2", file2); // revoke on success // Object.values(documents).map(document => document?.imageSrc).forEach(src => { // if (src) URL.revokeObjectURL(src); // }); // useUserStore.setState(produce(state => { // state.isDocumentsDialogOpen = false; // })); }; export const setSettingsField = ( fieldName: UserSettingsField | keyof UserName, value: string, ) => useUserStore.setState( produce(state => { if (!state.settingsFields) return; let errorMessage: string | null = null; try { if (value) validators[fieldName].validateSync(value); } catch (error: any) { errorMessage = error.message; } state.settingsFields[fieldName].value = value || ""; state.settingsFields[fieldName].touched = true; state.settingsFields[fieldName].error = errorMessage; state.settingsFields.hasError = Object.values(state.settingsFields).reduce((acc: boolean, field) => { if (typeof field === "boolean") return acc; if (field.error !== null) return true; return acc; }, false); }) ); export const sendUserData = async () => { const state = useUserStore.getState(); if (!state.settingsFields) return; const isPatchingUser = state.settingsFields.email.touched || state.settingsFields.password.touched || state.settingsFields.phoneNumber.touched; const isPatchingUserAccount = state.settingsFields.firstname.touched || state.settingsFields.secondname.touched || state.settingsFields.middlename.touched || state.settingsFields.orgname.touched; const userPayload: PatchUserRequest = {}; if (state.settingsFields.email.value.length !== 0) userPayload.email = state.settingsFields.email.value; if (state.settingsFields.password.value.length !== 0) userPayload.password = state.settingsFields.password.value; if (state.settingsFields.phoneNumber.value.length !== 0) userPayload.phoneNumber = state.settingsFields.phoneNumber.value; const userAccountPayload: UserName = { firstname: state.settingsFields.firstname.value, secondname: state.settingsFields.secondname.value, middlename: state.settingsFields.middlename.value, orgname: state.settingsFields.orgname.value, }; await Promise.all([ isPatchingUser && patchUser(userPayload), isPatchingUserAccount && patchUserAccount(userAccountPayload), ]); }; export const addTariffToCart = async (tariffId: string) => { const result = await patchCart(tariffId); setCart(result); }; export const removeTariffFromCart = async (tariffId: string) => { const result = await deleteCart(tariffId); setCart(result); }; const validators: Record = { email: string().email("Неверный email"), phoneNumber: string().matches(/^[+\d|\d]*$/, "Неверный номер телефона").min(6, "Номер телефона должен содержать минимум 6 символов"), password: string().min(8, "Минимум 8 символов").matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы в пароле").optional(), firstname: string(), secondname: string(), middlename: string(), orgname: string(), };