import { PatchUserRequest, UserDocumentTypes, UserDocuments, UserDocumentsUrl, UserSettingsFieldStatus, UserSettingsField, } 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 { UserAccountSettingsFieldStatus, VerificationStatus, } from "@root/model/account"; import { patchCurrency, deleteCart, patchCart } from "@root/api/cart"; import { User, UserAccount, UserName, getInitials, patchUserAccount } from "@frontend/kitui"; 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; documentsUrl: UserDocumentsUrl; comment: string; initials: string; } 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, }; export const defaultDocument = { file: null, uploadedFileName: null, imageSrc: null, }; const initialState: UserStore = { userId: null, user: null, userAccount: null, settingsFields: { ...defaultFields }, verificationStatus: VerificationStatus.NOT_VERIFICATED, verificationType: "juridical", isDocumentsDialogOpen: false, dialogType: "juridical", comment: "", documents: { ИНН: { ...defaultDocument }, Устав: { ...defaultDocument }, "Свидетельство о регистрации НКО": { ...defaultDocument }, }, documentsUrl: { ИНН: "", Устав: "", "Свидетельство о регистрации НКО": "", }, initials: "AA", }; 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 setVerificationStatus = (verificationStatus: VerificationStatus) => useUserStore.setState({ verificationStatus }); export const setVerificationType = (verificationType: "nko" | "org") => useUserStore.setState({ verificationType: verificationType === "org" ? "juridical" : "nko", }); 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 ?? ""; state.initials = getInitials(state.settingsFields.firstname.value, state.settingsFields.secondname.value); }), false, { type: "setUserAccount", payload: user, } ); export const setCart = (cart: string[]) => useUserStore.setState( produce((state) => { if (state.userAccount) state.userAccount.cart = cart; }) ); export const setComment = (comment: string) => useUserStore.setState({ comment }); 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 | null) => 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 setDocumentUrl = (type: UserDocumentTypes, url: string) => useUserStore.setState( produce((state) => { if (!url) { state.documentsUrl[type] = ""; return; } state.documentsUrl[type] = url; }) ); export const setUploadedDocument = ( type: UserDocumentTypes, fileName: string, url: string ) => useUserStore.setState( produce((state) => { state.documents[type] = { file: null, uploadedFileName: fileName, imageSrc: url, }; }) ); 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).then(([user]) => user && setUser(user)), isPatchingUserAccount && patchUserAccount(userAccountPayload).then(setUserAccount), ]); }; export const addTariffToCart = async (tariffId: string) => { const [patchCartResponse, patchCartError] = await patchCart(tariffId); if (!patchCartError) { setCart(patchCartResponse); } }; export const removeTariffFromCart = async (tariffId: string) => { const [deleteCartResponse, deleteCartError] = await deleteCart(tariffId); if (!deleteCartError) { setCart(deleteCartResponse); } }; export const changeUserCurrency = async (currency: string) => { const [patchCurrencyResponse, patchCurrencyError] = await patchCurrency( currency ); if (!patchCurrencyError && patchCurrencyResponse) { setUserAccount(patchCurrencyResponse); } }; 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(), };