Skip to content

Commit

Permalink
add AppLoaderLayout, theme
Browse files Browse the repository at this point in the history
  • Loading branch information
nothing9537 committed Oct 30, 2023
1 parent a3a9a18 commit a26e5ba
Show file tree
Hide file tree
Showing 15 changed files with 144 additions and 58 deletions.
2 changes: 1 addition & 1 deletion json-server/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,7 @@
"isAppRedesigned": true
},
"jsonSettings": {
"theme": "app_light_theme",
"theme": "app_sand_theme",
"isArticlesPageWasOpenedOnce": true
},
"avatar": "https://source.boringavatars.com/pixel/120/Stefan?colors=26a653,2a1d8f,79646a"
Expand Down
54 changes: 21 additions & 33 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import { FC, Suspense, useEffect } from 'react';
import { classNames } from '@/shared/lib/classNames/classNames';
import { useAppDispatch } from '@/shared/lib/hooks/useAppDispatch';
import { useAppSelector } from '@/shared/lib/hooks/useAppSelector';
import { Skeleton } from '@/shared/ui/redesigned/Skeleton';
// import { Skeleton as SkeletonDeprecatedLoading } from '@/shared/ui/deprecated/Skeleton';
import { MainLayout } from '@/shared/layouts/MainLayout';
import { useTheme } from '@/shared/lib/hooks/useTheme';
import { ToggleFeatures } from '@/shared/lib/features';
import { HStack } from '@/shared/ui/redesigned/Stack';
import { Loader } from '@/shared/ui/deprecated/Loader';
import { AppLoaderLayout } from '@/shared/layouts/AppLoaderLayout';
import { getUserMounted, initAuthData } from '@/entities/User';
import { Navbar } from '@/widgets/Navbar';
import { Aside } from '@/widgets/Aside';

import { AppRouter } from './providers/RouterProvider';

const App: FC = () => {
Expand All @@ -18,42 +20,28 @@ const App: FC = () => {
const { theme } = useTheme();

useEffect(() => {
document.body.className = theme;
dispatch(initAuthData());
}, [dispatch, theme]);

/**
* @deprecated
*/
// const SkeletonDeprecated = (
// <div className={classNames('App', {}, ['scroll'])} id="App">
// <SkeletonDeprecatedLoading width="100%" height={50} />
// <div className="content-page">
// <SkeletonDeprecatedLoading width="var(--aside-width)" height="calc(100vh - var(--navbar-height))" />
// </div>
// </div>
// );

const SkeletonRedesigned = (
<div className={classNames('App', {}, ['scroll'])} id="App">
<div className={classNames('App_redesigned', {}, ['scroll'])} id="App">
<Suspense fallback="">
<MainLayout
header={<Skeleton width={170} height={48} margin="16px 16px 0 0" borderRadius={32} />}
aside={<Skeleton width={220} height="100%" borderRadius={32} />}
content={<Skeleton width="100%" height="100%" borderRadius={32} />}
/>
</Suspense>
</div>
</div>
);
if (!_mounted) {
document.body.className = theme;
dispatch(initAuthData());
}
}, [dispatch, theme, _mounted]);

if (!_mounted) {
return (
<ToggleFeatures
name="isAppRedesigned"
on={SkeletonRedesigned}
off={SkeletonRedesigned}
on={(
<div className={classNames('App_redesigned', {}, ['scroll'])} id="App">
<AppLoaderLayout />
</div>
)}
off={(
<div className={classNames('App', {}, ['scroll'])} id="App">
<HStack justify="center" height="100%">
<Loader />
</HStack>
</div>
)}
/>
);
}
Expand Down
11 changes: 10 additions & 1 deletion src/app/providers/ThemeProvider/ui/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import { useMemo, useState, FC, ReactNode, useEffect } from 'react';
import { Theme } from '@/shared/consts/theme';
import { ThemeContext } from '@/shared/lib/context/ThemeContext';
import { useJsonSettings } from '@/entities/User';
import { THEME_KEY } from '@/shared/consts/localStorage';

interface ThemeProviderProps {
initialTheme?: Theme;
children: ReactNode;
}

const fallbackTheme = localStorage.getItem(THEME_KEY) as Theme;

const ThemeProvider: FC<ThemeProviderProps> = ({ children, initialTheme }) => {
const { theme: defaultTheme } = useJsonSettings();
const [themeInited, setThemeInited] = useState<boolean>(false);
const [theme, setTheme] = useState<Theme>(initialTheme || defaultTheme || Theme.DARK);
const [theme, setTheme] = useState<Theme>(initialTheme || fallbackTheme || Theme.DARK);

useEffect(() => {
if (!themeInited && defaultTheme) {
Expand All @@ -20,6 +23,12 @@ const ThemeProvider: FC<ThemeProviderProps> = ({ children, initialTheme }) => {
}
}, [defaultTheme, themeInited]);

useEffect(() => {
document.body.className = theme;

localStorage.setItem(THEME_KEY, theme);
}, [theme]);

const defaultProps = useMemo(() => ({
theme, setTheme,
}), [theme]);
Expand Down
2 changes: 2 additions & 0 deletions src/app/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ body {
.App {
background: var(--bg-color);
transition: background var(--app-transition);
width: 100vw;
height: 100vh;
}

.App_redesigned {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { ThunkConfig } from '@/app/providers/StoreProvider';
import { AUTH_TOKEN_KEY } from '@/shared/consts/localStorage';
import { AUTH_TOKEN_KEY, LAST_DESIGN_KEY } from '@/shared/consts/localStorage';
import { getUserDataByIdQuery } from '../../../api';
import { User } from '../../types/user';

Expand All @@ -21,6 +21,9 @@ export const initAuthData = createAsyncThunk<User, void, ThunkConfig>(
getUserDataByIdQuery(userId),
).unwrap();

const localStorageDesign = JSON.stringify(response.features?.isAppRedesigned ? 'new' : 'old');
localStorage.setItem(LAST_DESIGN_KEY, localStorageDesign);

return response;
} catch (error) {
console.log(error);

Check warning on line 29 in src/entities/User/model/services/initAuthData/initAuthData.ts

View workflow job for this annotation

GitHub Actions / checks (18.x)

Unexpected console statement
Expand Down
13 changes: 8 additions & 5 deletions src/entities/User/model/slice/userSlice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AUTH_TOKEN_KEY } from '@/shared/consts/localStorage';
import { AUTH_TOKEN_KEY, LAST_DESIGN_KEY } from '@/shared/consts/localStorage';
import { setFeatureFlags } from '@/shared/lib/features';
import { User, UserSchema } from '../types/user';
import { saveJsonSettings } from '../services/saveJsonSettings/saveJsonSettings';
Expand All @@ -13,10 +13,13 @@ export const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setAuthData: (state, action: PayloadAction<User>) => {
state.authData = action.payload;
setFeatureFlags(action.payload?.features);
localStorage.setItem(AUTH_TOKEN_KEY, JSON.stringify(action.payload.id));
setAuthData: (state, { payload }: PayloadAction<User>) => {
state.authData = payload;
setFeatureFlags(payload?.features);
localStorage.setItem(AUTH_TOKEN_KEY, JSON.stringify(payload.id));

const localStorageDesign = JSON.stringify(payload.features?.isAppRedesigned ? 'new' : 'old');
localStorage.setItem(LAST_DESIGN_KEY, localStorageDesign);
},
logout: (state) => {
state.authData = undefined;
Expand Down
1 change: 1 addition & 0 deletions src/features/AuthByUserName/ui/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const LoginForm: FC<LoginFormProps> = memo(({ className, onSuccess }) => {

if (result.meta.requestStatus === 'fulfilled') {
onSuccess();
window.location.reload();
}
}, [dispatch, username, password, onSuccess]);

Expand Down
31 changes: 16 additions & 15 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
// import { DrawComponentsTree } from '@/shared/lib/components/DrawComponentsTree';
import { DrawComponentsTree } from '@/shared/lib/components/DrawComponentsTree';
import { ForceUpdateProvider } from '@/shared/lib/render/forceUpdate';
import { StoreProvider } from '@/app/providers/StoreProvider';
import { ErrorBoundary } from '@/app/providers/ErrorBoundary';
import { ThemeProvider } from '@/app/providers/ThemeProvider';
import App from '@/app/App';
import '@/app/styles/index.scss';
import '@/shared/config/i18n/i18n';

// const componentsTree = [BrowserRouter, StoreProvider, ErrorBoundary, ThemeProvider, App];
const componentsTree = [BrowserRouter, StoreProvider, ErrorBoundary, ThemeProvider, ForceUpdateProvider, App];

const componentsTree = (
<BrowserRouter>
<StoreProvider>
<ErrorBoundary>
<ThemeProvider>
<App />
</ThemeProvider>
</ErrorBoundary>
</StoreProvider>
</BrowserRouter>
);
// const componentsTree = (
// <BrowserRouter>
// <StoreProvider>
// <ErrorBoundary>
// <ThemeProvider>
// <App />
// </ThemeProvider>
// </ErrorBoundary>
// </StoreProvider>
// </BrowserRouter>
// );

const container = document.getElementById('root');

Expand All @@ -30,5 +31,5 @@ if (!container) {

const root = createRoot(container);

root.render(componentsTree);
// root.render(DrawComponentsTree(componentsTree));
// root.render(componentsTree);
root.render(DrawComponentsTree(componentsTree));
5 changes: 4 additions & 1 deletion src/pages/ArticlesPage/ui/ArticlesPage/ArticlesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { initArticlesList } from '../../model/services/initArticlesList/initArti
import { ArticlesListFilters } from '../ArticlesListFilters/ArticlesListFilters';
import { ViewSelectorContainer } from '../ViewSelectorContainer/ViewSelectorContainer';
import { FiltersContainer } from '../FiltersContainer/FiltersContainer';
import { useArticlesListIsLoading } from '../../model/selectors/articlesList';

import cls from './ArticlesPage.module.scss';

Expand All @@ -27,12 +28,14 @@ const reducers: ReducersList = {

const ArticlesPage: FC<ArticlesPageProps> = ({ className }) => {
const dispatch = useAppDispatch();
const isLoading = useArticlesListIsLoading();

const onNextArticlesPageLoad = useCallback(() => {
if (__PROJECT__ !== 'storybook') {
dispatch(fetchNewArticles());
}
}, [dispatch]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch, isLoading]);

useInitialEffect(() => {
dispatch(initArticlesList(window.location.search));
Expand Down
1 change: 1 addition & 0 deletions src/shared/consts/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export const AUTH_TOKEN_KEY = 'user';
export const THEME_KEY = 'theme';
export const ARTICLES_VIEW_KEY = 'articles_view';
export const ARTICLES_SCROLL_ITEM_INDEX = 'scroll_item_index';
export const LAST_DESIGN_KEY = 'last_design';
Empty file.
29 changes: 29 additions & 0 deletions src/shared/layouts/AppLoaderLayout/AppLoaderLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { FC } from 'react';
import { Skeleton } from '@/shared/ui/redesigned/Skeleton';
import { HStack, VStack } from '@/shared/ui/redesigned/Stack';

import { MainLayout } from '../MainLayout';
import cls from './AppLoaderLayout.module.scss';

export const AppLoaderLayout: FC = () => {
return (
<MainLayout
header={(
<HStack className={cls.header}>
<Skeleton width={40} height={40} borderRadius="50%" margin="16px 16px 0 0" />
</HStack>
)}
content={(
<VStack height="100%" gap={16}>
<Skeleton width="70%" height={32} borderRadius={16} />
<Skeleton width="40%" height={20} borderRadius={16} />
<Skeleton width="50%" height={20} borderRadius={16} />
<Skeleton width="30%" height={32} borderRadius={16} />
<Skeleton width="80%" height="80%" borderRadius={16} />
<Skeleton width="80%" height="80%" borderRadius={16} />
</VStack>
)}
aside={<Skeleton width={220} height="100%" borderRadius={32} />}
/>
);
};
1 change: 1 addition & 0 deletions src/shared/layouts/AppLoaderLayout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AppLoaderLayout } from './AppLoaderLayout';
7 changes: 6 additions & 1 deletion src/shared/lib/features/lib/featureFlagsHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { LAST_DESIGN_KEY } from '@/shared/consts/localStorage';
import { FeatureFlags, FeatureFlagsKeys } from '@/shared/types/featureFlags';

let featureFlags: FeatureFlags = {};
const defaultFeatures: FeatureFlags = { isAppRedesigned: JSON.parse(localStorage.getItem(LAST_DESIGN_KEY) as string) === 'new' };

let featureFlags: FeatureFlags = {
...defaultFeatures,
};

export const setFeatureFlags = (newFeatureFlags?: FeatureFlags) => {
if (newFeatureFlags) {
Expand Down
40 changes: 40 additions & 0 deletions src/shared/lib/render/forceUpdate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createContext, ReactNode, useContext, useMemo, useState } from 'react';

interface ForceUpdateConfig {
value: boolean;
forceUpdate: () => void;
}

const ForceUpdateContext = createContext<ForceUpdateConfig>({
value: true,
forceUpdate: () => { return null; },
});

export const useForceUpdate = () => {
const { forceUpdate } = useContext(ForceUpdateContext);

return forceUpdate;
};

export function ForceUpdateProvider({ children }: { children: ReactNode }) {
const [value, setValue] = useState<boolean>(true);

const forceUpdate = () => {
setValue((prev) => !prev);
setTimeout(() => {
setValue((prev) => !prev);
}, 0);
};

const valueContext = useMemo(() => ({ value, forceUpdate }), [value]);

if (!value) {
return null;
}

return (
<ForceUpdateContext.Provider value={valueContext}>
{children}
</ForceUpdateContext.Provider>
);
}

0 comments on commit a26e5ba

Please sign in to comment.