@@ -0,0 +1,19 @@ +import axios from 'axios' + +import { API_URL } from '@/src/shared/lib/constants' + +import { IUserSetting } from '../model/types' + +export const addUserSettings = async (token: string, setting: Omit): Promise => { + try { + const { data } = await axios.post(API_URL + '/api/users/settings/', setting, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + return data + } catch (err) { + return null + } +} @@ -0,0 +1,19 @@ +import axios from 'axios' + +import { API_URL } from '@/src/shared/lib/constants' + +import { IUserSetting } from '../model/types' + +export const getUserSettings = async (token: string): Promise => { + try { + const { data } = await axios.get(API_URL + '/api/users/settings/', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + return data + } catch (err) { + return [] + } +} @@ -0,0 +1,23 @@ +import axios from 'axios' + +import { API_URL } from '@/src/shared/lib/constants' + +import { getUpdatedSettingsLocal } from '../lib/helpers/update-setting-local' +import { IUserSetting, SettingValueType } from '../model/types' + +export const updateUserSettings = async (token: string, id: string, value: SettingValueType, settings: IUserSetting[]) => { + try { + const { data } = await axios.put( + API_URL + `/api/users/settings/${id}`, + { value: value }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) + return getUpdatedSettingsLocal({ settings, id, value }) + } catch (err) { + return null + } +} @@ -0,0 +1,23 @@ +import { Device } from '@/src/shared/lib/types/entities' + +import { IUserSetting, SettingType } from '../../model/types' + +interface IProps { + settings: Omit | null + targetType: SettingType + targetDevice: Device +} + +export function isSettingExist({ settings, targetDevice, targetType }: IProps): IUserSetting | null { + if (!settings || settings.length === 0) { + return null + } + + for (let i = 0; settings.length; i++) { + if (settings[i].type === targetType && settings[i].device === targetDevice) { + return settings[i] + } + } + + return null +} @@ -0,0 +1,22 @@ +import { IUserSetting, SettingValueType } from '../../model/types' + +interface IProps { + settings: IUserSetting[] + id: string + value: SettingValueType +} + +export function getUpdatedSettingsLocal({ settings, id, value }: IProps): IUserSetting[] { + if (settings.length === 0) { + return settings + } + + return settings.map((setting) => + setting.id === id + ? { + ...setting, + value, + } + : setting + ) +} @@ -0,0 +1,75 @@ +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' + +import { addUserSettings } from '../api/add-user-settings' +import { getUserSettings } from '../api/get-user-settings' +import { updateUserSettings } from '../api/update-user-settings' +import { getUpdatedSettingsLocal } from '../lib/helpers/update-setting-local' + +import { IUserSetting, SettingValueType } from './types' + +interface IAddSettings { + token: string | undefined | null + setting: Omit +} + +export const getUserAccountSettings = createAsyncThunk('users/getSettings', async (token: string | undefined | null) => { + if (!token) { + return initialState.state + } + return await getUserSettings(token) +}) + +export const addUserAccountSettings = createAsyncThunk('users/addSettings', async ({ token, setting }: IAddSettings) => { + if (!token) { + return + } + return await addUserSettings(token, setting) +}) + +export const updateUserAccountSettings = createAsyncThunk( + 'users/updateSettings', + async ({ + token, + id, + value, + settings, + }: { + token: string | undefined | null + id: string + value: SettingValueType + settings: IUserSetting[] | null + }) => { + if (!token) { + return initialState.state + } + if (!settings) { + return initialState.state + } + return await updateUserSettings(token, id, value, settings) + } +) + +const initialState: { state: IUserSetting[] | null } = { + state: null, +} + +export const settingsSlice = createSlice({ + name: 'balance', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder.addCase(getUserAccountSettings.fulfilled, (state, action) => { + state.state = action.payload + }) + builder.addCase(addUserAccountSettings.fulfilled, (state, action) => { + if (state.state === null) state.state = [] + if (action.payload) state.state = [...state.state, action.payload] + }) + + builder.addCase(updateUserAccountSettings.fulfilled, (state, action) => { + if (action.payload) state.state = action.payload + }) + }, +}) + +export default settingsSlice.reducer @@ -1 +1,16 @@ +import { Device } from '@/src/shared/lib/types/entities' + export type AccountType = 'regular' | 'business_host' | 'business_account' + +export type SettingType = 'sidemenu' + +export type SettingValueType = { + sidemenu_state?: 'opened' | 'closed' +} + +export interface IUserSetting { + id: string + device: Device + type: SettingType + value: SettingValueType +} @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState } from 'react' import { Box } from '@mui/material' import { ThemeProvider } from '@mui/material/styles' import Head from 'next/head' @@ -8,8 +8,10 @@ import { useSession } from 'next-auth/react' import { getUserBalance } from '@/src/entities/balance' import { useTheme } from '@/src/entities/theme' import { getAllInfo } from '@/src/entities/user-account' +import { isSettingExist } from '@/src/entities/user-account/lib/helpers/is-setting-exist' +import { getUserAccountSettings } from '@/src/entities/user-account/model/settings' import InfoBar from '@/src/main/layout/ui/info-bar' -import { useAppDispatch } from '@/src/main/store/store' +import { useAppDispatch, useAppSelector } from '@/src/main/store/store' import { Loader } from '@/src/shared' import { pingFangFont } from '@/src/shared/lib/constants/font/font' import { Device } from '@/src/shared/lib/types/entities' @@ -28,9 +30,13 @@ interface Props { } export const Layout: React.FC = ({ children, device, isAuthPage = false, titlePage, title = titlePage, isLoader }) => { + const { data: sessionData } = useSession() + const appState = useAppSelector((state) => state) + const dispatch = useAppDispatch() + const desktop = device === 'desktop' - const dispatch = useAppDispatch() + const [sidemenuDefaultOpen, setSidemenuDefaultOpen] = useState(true) useTheme() @@ -49,6 +55,19 @@ export const Layout: React.FC = ({ children, device, isAuthPage = false, } }, [status]) + React.useEffect(() => { + if (sessionData && appState.settings.state === null) { + dispatch(getUserAccountSettings(sessionData.access)) + } + }, [sessionData]) + + React.useEffect(() => { + if (appState.settings.state !== null && device) { + let setting = isSettingExist({ settings: appState.settings.state, targetDevice: device, targetType: 'sidemenu' }) + if (setting) setting?.value?.sidemenu_state === 'opened' ? setSidemenuDefaultOpen(true) : setSidemenuDefaultOpen(false) + } + }, [appState.settings.state]) + if (status === 'loading' || isLoader) { return ( @@ -82,7 +101,7 @@ export const Layout: React.FC = ({ children, device, isAuthPage = false, {!isAuthPage ? ( desktop ? ( - + @@ -4,6 +4,7 @@ import { configureStore } from '@reduxjs/toolkit' import { balanceSlice } from '@/src/entities/balance' import { themeSlice } from '@/src/entities/theme' import { userSlice } from '@/src/entities/user-account' +import { settingsSlice } from '@/src/entities/user-account/model/settings' import { stepperSlice } from '@/src/features/register-business' import { copySlice } from '@/src/features/use-copy/copy-slice' import { paramsStore } from '@/src/main/store/model-parametres-store' @@ -19,6 +20,7 @@ export const store = configureStore({ copy: copySlice.reducer, notification: notificationSlice.reducer, params: paramsStore.reducer, + settings: settingsSlice.reducer, }, }) @@ -15,9 +15,14 @@ import { usePathname } from 'next/navigation' import { useRouter } from 'next/router' import { signOut, useSession } from 'next-auth/react' +import { addUserSettings } from '@/src/entities/user-account/api/add-user-settings' +import { updateUserSettings } from '@/src/entities/user-account/api/update-user-settings' +import { isSettingExist } from '@/src/entities/user-account/lib/helpers/is-setting-exist' +import { addUserAccountSettings, updateUserAccountSettings } from '@/src/entities/user-account/model/settings' import { getAll, ResponseAllInfo } from '@/src/entities/user-account/model/user-type-slice' -import { useAppSelector } from '@/src/main/store/store' +import { useAppDispatch, useAppSelector } from '@/src/main/store/store' import { TooltipCustom } from '@/src/shared' +import { Device } from '@/src/shared/lib/types/entities' import '../styles/styles.module.css' import styles from '../styles/styles.module.css' @@ -83,14 +88,16 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' }), })) -export const SideMenu = () => { - const [open, setOpen] = React.useState(true) +export const SideMenu = ({ device, sidemenuDefaultOpen }: { device: Device; sidemenuDefaultOpen: boolean }) => { + const [open, setOpen] = React.useState(sidemenuDefaultOpen) + const [isInitialValueSet, setIsInitialValueSet] = React.useState(false) const { status, data } = useSession() + const dispatch = useAppDispatch() const { pathname, basePath, asPath, push } = useRouter() - const theme = useAppSelector((state) => state.theme.theme) + const { theme, settings } = useAppSelector((state) => state) const handleDrawerOpen = () => { setOpen(true) @@ -116,10 +123,44 @@ export const SideMenu = () => { getAll(data?.access).then((res) => setUserInfo(res)) }, [data?.access]) + useEffect(() => { + if (settings.state !== null && isInitialValueSet) { + let response = isSettingExist({ settings: settings.state, targetDevice: device, targetType: 'sidemenu' }) + + if (response && data?.access) { + dispatch( + updateUserAccountSettings({ + token: data.access, + id: response.id, + value: { sidemenu_state: open ? 'opened' : 'closed' }, + settings: settings.state, + }) + ) + } else if (data?.access) { + dispatch( + addUserAccountSettings({ + token: data.access, + setting: { device, type: 'sidemenu', value: { sidemenu_state: open ? 'opened' : 'closed' } }, + }) + ) + } + } + }, [open]) + + useEffect(() => { + if (settings.state !== null && !isInitialValueSet) { + let setting = isSettingExist({ settings: settings.state, targetDevice: device, targetType: 'sidemenu' }) + if (setting) { + setting?.value?.sidemenu_state === 'opened' ? setOpen(true) : setOpen(false) + } + setIsInitialValueSet(true) + } + }, [settings.state]) + return ( @@ -166,7 +207,7 @@ export const SideMenu = () => { key={el.title} open={open} pathname={asPath} - theme={theme} + theme={theme.theme} title={el.title} link={el.link} icon={el.icon} @@ -181,7 +222,7 @@ export const SideMenu = () => { key={el.title} open={open} pathname={asPath} - theme={theme} + theme={theme.theme} title={el.title} link={el.link} icon={el.icon} @@ -196,7 +237,7 @@ export const SideMenu = () => { {