Skip to content

Commit

Permalink
feat: change localStorage to json settings and save it to db (theme, …
Browse files Browse the repository at this point in the history
…etc...)
  • Loading branch information
TomatoVan committed Apr 8, 2024
1 parent 115ed34 commit 6cb799b
Show file tree
Hide file tree
Showing 15 changed files with 133 additions and 43 deletions.
11 changes: 8 additions & 3 deletions json-server/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -751,9 +751,14 @@
],
"features": {
"isArticleRatingEnabled": true,
"isCounterEnabled": false
"isCounterEnabled": true
},
"avatar": "https://mobimg.b-cdn.net/v3/fetch/22/2207633df03a819cd72889249c8361a8.jpeg?w=1470&r=0.5625"
"avatar": "https://mobimg.b-cdn.net/v3/fetch/22/2207633df03a819cd72889249c8361a8.jpeg?w=1470&r=0.5625",
"jsonSettings": {
"theme": "app_orange_theme",
"isFirstVisit": true,
"settingsPageHasBeenOpen": false
}
},
{
"id": "2",
Expand Down Expand Up @@ -897,4 +902,4 @@
"profileId": "2"
}
]
}
}
17 changes: 12 additions & 5 deletions src/app/providers/ThemeProvider/ui/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React, { ReactNode, useMemo, useState } from 'react';
import React, { ReactNode, useEffect, useMemo, useState } from 'react';
import { ThemeContext } from '@/shared/lib/context/ThemeContext';
import { Theme } from '@/shared/const/theme';
import { LOCAL_STORAGE_THEME_KEY } from '@/shared/const/localstorage';

const defaultTheme =
(localStorage.getItem(LOCAL_STORAGE_THEME_KEY) as Theme) || Theme.LIGHT;
import { useJsonSettings } from '@/entities/User';

interface ThemeProviderProps {
initialTheme?: Theme;
Expand All @@ -14,8 +11,18 @@ interface ThemeProviderProps {
const ThemeProvider = (props: ThemeProviderProps) => {
const { initialTheme, children } = props;

const { theme: defaultTheme = Theme.LIGHT } = useJsonSettings();
const [isThemeInited, setIsThemeInited] = useState(false);

const [theme, setTheme] = useState<Theme>(initialTheme || defaultTheme);

useEffect(() => {
if (!isThemeInited) {
setTheme(defaultTheme);
setIsThemeInited(true);
}
}, [defaultTheme, isThemeInited]);

const defaultProps = useMemo(
() => ({
theme,
Expand Down
25 changes: 25 additions & 0 deletions src/entities/User/api/userApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { rtkApi } from '@/shared/api/rtkApi';
import { User } from '../model/types/user';
import { JsonSettings } from '../model/types/jsonSettings';

interface SetJsonSettingsArg {
userId: string;
jsonSettings: JsonSettings;
}

const userApi = rtkApi.injectEndpoints({
endpoints: (build) => ({
setJsonSettings: build.mutation<User, SetJsonSettingsArg>({
query: ({ userId, jsonSettings }) => ({
url: `/users/${userId}`,
method: 'PATCH',
body: {
jsonSettings,
},
}),
}),
}),
});

export const setJsonSettingsMutation =
userApi.endpoints.setJsonSettings.initiate;
2 changes: 2 additions & 0 deletions src/entities/User/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export {
getUserRoles,
} from './model/selectors/roleSelectors';
export { UserRole } from './model/consts/consts';
export { useJsonSettings } from './model/selectors/jsonSettings';
export { saveJsonSettings } from './model/services/saveJsonSettings';
8 changes: 8 additions & 0 deletions src/entities/User/model/selectors/jsonSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { buildSelector } from '@/shared/lib/store';
import { JsonSettings } from '../types/jsonSettings';

const defaultJsonSettings: JsonSettings = {};

export const [useJsonSettings, getJsonSettings] = buildSelector(
(state) => state.user?.authData?.jsonSettings ?? defaultJsonSettings,
);
41 changes: 41 additions & 0 deletions src/entities/User/model/services/saveJsonSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { JsonSettings } from '../types/jsonSettings';
import { ThunkConfig } from '@/app/providers/StoreProvider';
import { getUserAuthData } from '../selectors/getUserAuthData/getUserAuthData';
import { getJsonSettings } from '../selectors/jsonSettings';
import { setJsonSettingsMutation } from '../../api/userApi';

export const saveJsonSettings = createAsyncThunk<
JsonSettings,
JsonSettings,
ThunkConfig<string>
>('user/saveJsonSettings', async (newJsonSettings, thunkApi) => {
const { rejectWithValue, getState, dispatch } = thunkApi;
const userData = getUserAuthData(getState());
const currentSettings = getJsonSettings(getState());

if (!userData) {
return rejectWithValue('');
}

try {
const response = await dispatch(
setJsonSettingsMutation({
userId: userData.id,
jsonSettings: {
...currentSettings,
...newJsonSettings,
},
}),
).unwrap();

if (!response.jsonSettings) {
return rejectWithValue('');
}

return response.jsonSettings;
} catch (e) {
console.log(e);
return rejectWithValue('');
}
});
12 changes: 12 additions & 0 deletions src/entities/User/model/slice/userSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { USER_LOCALSTORAGE_KEY } from '@/shared/const/localstorage';
import { User, UserSchema } from '../types/user';
import { setFeatureFlags } from '@/shared/lib/features';
import { saveJsonSettings } from '../services/saveJsonSettings';
import { JsonSettings } from '../types/jsonSettings';

const initialState: UserSchema = {
_inited: false,
Expand Down Expand Up @@ -29,6 +31,16 @@ export const userSlice = createSlice({
localStorage.removeItem(USER_LOCALSTORAGE_KEY);
},
},
extraReducers: (builder) => {
builder.addCase(
saveJsonSettings.fulfilled,
(state, { payload }: PayloadAction<JsonSettings>) => {
if (state.authData) {
state.authData.jsonSettings = payload;
}
},
);
},
});

export const { actions: userActions } = userSlice;
Expand Down
7 changes: 7 additions & 0 deletions src/entities/User/model/types/jsonSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Theme } from '@/shared/const/theme';

export interface JsonSettings {
theme?: Theme;
isFirstVisit?: true;
settingsPageHasBeenOpen?: false;
}
2 changes: 2 additions & 0 deletions src/entities/User/model/types/user.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { UserRole } from '../../model/consts/consts';
import { FeatureFlags } from '@/shared/types/featureFlags';
import { JsonSettings } from './jsonSettings';

export interface User {
id: string;
username: string;
avatar?: string;
roles?: UserRole[];
features?: FeatureFlags;
jsonSettings?: JsonSettings;
}

export interface UserSchema {
Expand Down
14 changes: 12 additions & 2 deletions src/features/ThemeSwitcher/ui/ThemeSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react';
import React, { useCallback } from 'react';
import { classNames } from '@/shared/lib/classNames/classNames';
import { Button, ButtonTheme } from '@/shared/ui/Button';
import LightIcon from '../../../shared/assets/icons/theme-light.svg';
import DarkIcon from '../../../shared/assets/icons/theme-dark.svg';
import { Theme } from '@/shared/const/theme';
import { useTheme } from '@/shared/lib/hook/useTheme/useTheme';
import { saveJsonSettings } from '@/entities/User';
import { useAppDispatch } from '@/shared/lib/hook/useAppDispatch/useAppDispatch';

interface ThemeSwitcherProps {
className?: string;
Expand All @@ -13,11 +15,19 @@ interface ThemeSwitcherProps {
export const ThemeSwitcher = ({ className }: ThemeSwitcherProps) => {
const { theme, toggleTheme } = useTheme();

const dispatch = useAppDispatch();

const onToggleHandler = useCallback(() => {
toggleTheme((newTheme) => {
dispatch(saveJsonSettings({ theme: newTheme }));
});
}, [dispatch, toggleTheme]);

return (
<Button
theme={ButtonTheme.CLEAR}
className={classNames('', {}, [className])}
onClick={toggleTheme}
onClick={onToggleHandler}
>
{theme === Theme.DARK ? <DarkIcon /> : <LightIcon />}
</Button>
Expand Down
9 changes: 3 additions & 6 deletions src/shared/lib/hook/useTheme/useTheme.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { useContext } from 'react';
import { ThemeContext } from '../../context/ThemeContext';
import { Theme } from '../../../const/theme';
import { LOCAL_STORAGE_THEME_KEY } from '../../../const/localstorage';

interface UseThemeResult {
toggleTheme: () => void;
toggleTheme: (saveAction: (theme: Theme) => void) => void;
theme: Theme;
}

export function useTheme(): UseThemeResult {
const { theme, setTheme } = useContext(ThemeContext);

const toggleTheme = () => {
const toggleTheme = (saveAction: (theme: Theme) => void) => {
let newTheme: Theme;
switch (theme) {
case Theme.DARK:
Expand All @@ -28,9 +27,7 @@ export function useTheme(): UseThemeResult {
}

setTheme?.(newTheme);
// чтобы работали стили в модалках и других частях приложения (используется по умолчанию везде как единая точка входа)
document.body.className = newTheme;
localStorage.setItem(LOCAL_STORAGE_THEME_KEY, newTheme);
saveAction?.(newTheme);
};

return {
Expand Down
25 changes: 0 additions & 25 deletions src/shared/ui/ThemeSwitcher/ThemeSwitcher.tsx

This file was deleted.

1 change: 0 additions & 1 deletion src/shared/ui/ThemeSwitcher/index.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/widgets/Sidebar/ui/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { memo, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { classNames } from '@/shared/lib/classNames/classNames';
import { ThemeSwitcher } from '@/shared/ui/ThemeSwitcher';
import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button';
import { VStack } from '@/shared/ui/Stack';
import { getSidebarItems } from '../../model/selectors/getSidebarItems';
import { SidebarItem } from '../SidebarItem/SidebarItem';
import cls from './Sidebar.module.scss';
import { LangSwitcher } from '../../../../features/LangSwitcher';
import { ThemeSwitcher } from '@/features/ThemeSwitcher';

interface SidebarProps {
className?: string;
Expand Down

0 comments on commit 6cb799b

Please sign in to comment.