Skip to content

Commit

Permalink
continue improving code setup
Browse files Browse the repository at this point in the history
  • Loading branch information
nramos0 committed Sep 12, 2021
1 parent a0aeb29 commit 10bba9b
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 95 deletions.
2 changes: 1 addition & 1 deletion frontend/src/components/general/InnerApp/InnerApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import About from '../../about/About/About';
import { useSyncPageToStorage } from '../../../hooks/innerApp/useSyncPageToStorage';
import { useReturnToLastPage } from '../../../hooks/innerApp/useReturnToLastPage';

enum AppLocation {
export enum AppLocation {
Library = '/app/library',
Read = '/app/read',
AddArticle = '/app/add-article',
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/hooks/app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './useAppRedirectOnGotData';
export * from './useStartInitialLoad';
export * from './useAuthCheck';
export * from './useDisplayAuthError';
export * from './useLogoutRedirect';
19 changes: 19 additions & 0 deletions frontend/src/hooks/app/useAppRedirectOnGotData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect } from 'react';
import { useHistory, useLocation } from 'react-router-dom';

export const useAppRedirectOnGotData = (
hasData: boolean,
setShouldFetchUserData: (shouldFetchUserData: boolean) => void
) => {
const history = useHistory();
const location = useLocation();

useEffect(() => {
if (hasData) {
if (!location.pathname.includes('app')) {
history.push('/app');
}
setShouldFetchUserData(false);
}
}, [hasData, history, location.pathname, setShouldFetchUserData]);
};
48 changes: 48 additions & 0 deletions frontend/src/hooks/app/useAuthCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import to from 'await-to-js';
import { useState, useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { useAuth } from '../../components/general/AuthWrapper/AuthWrapper';
import { i18nInitPromise } from '../../i18n';
import { NonAppLocation } from '../../main/App';
import { authenticate } from '../../net/requests';
import { useDisplayAuthError } from './useDisplayAuthError';

export interface AuthError {
initialLocation: string | null;
err: boolean;
}

export const useAuthCheck = (
setShouldFetchUserData: (shouldFetchUserData: boolean) => void
) => {
const auth = useAuth();
const history = useHistory();

const [authErr, setAuthErr] = useState<AuthError>({
initialLocation: null,
err: false,
});

const onAuth = useCallback(async () => {
if (history.location.pathname === NonAppLocation.Account) {
return;
}

const [err] = await to(authenticate({ token: auth.token }));
if (err !== null) {
await i18nInitPromise;

setAuthErr({
initialLocation: history.location.pathname,
err: true,
});
history.push(NonAppLocation.Account);
} else {
setShouldFetchUserData(true);
}
}, [auth.token, history, setShouldFetchUserData]);

useDisplayAuthError(authErr, setAuthErr);

return { onAuth };
};
33 changes: 33 additions & 0 deletions frontend/src/hooks/app/useDisplayAuthError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useToast } from '@chakra-ui/react';
import { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { i18nInitPromise } from '../../i18n';
import { AuthError } from './useAuthCheck';

export const useDisplayAuthError = (
authErr: AuthError,
setAuthErr: React.Dispatch<React.SetStateAction<AuthError>>
) => {
const showToast = useToast();
const history = useHistory();

useEffect(() => {
if (authErr.err && authErr.initialLocation !== '/') {
i18nInitPromise.then((t) => {
showToast({
title: t('prompt-login-title'),
description: t('prompt-login-info'),
status: 'error',
duration: 5000,
isClosable: true,
});
});
setAuthErr((prev) => {
return {
...prev,
err: false,
};
});
}
}, [authErr, history.location.pathname, setAuthErr, showToast]);
};
17 changes: 17 additions & 0 deletions frontend/src/hooks/app/useLogoutRedirect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { NonAppLocation } from '../../main/App';

export const useLogoutRedirect = (
isLoggingOut: boolean,
hasData: boolean,
shouldFetchUserData: boolean
) => {
const history = useHistory();

useEffect(() => {
if (isLoggingOut && !hasData && !shouldFetchUserData) {
history.push(NonAppLocation.Account);
}
}, [hasData, history, isLoggingOut, shouldFetchUserData]);
};
17 changes: 17 additions & 0 deletions frontend/src/hooks/app/useStartInitialLoad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useEffect } from 'react';
import { InitialLoadData } from '../../main/App';

export const useStartInitialLoad = (
initialLoad: () => Promise<void>,
setInitialLoadData: React.Dispatch<React.SetStateAction<InitialLoadData>>
) => {
useEffect(() => {
setInitialLoadData((prevData) => {
return {
...prevData,
promiseList: [...prevData.promiseList, initialLoad()],
startInitialLoad: true,
};
});
}, [initialLoad, setInitialLoadData]);
};
168 changes: 74 additions & 94 deletions frontend/src/main/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { Box, useToast } from '@chakra-ui/react';
import { Switch, Route, useHistory, useLocation } from 'react-router-dom';
import to from 'await-to-js';
import React, { useCallback, useState, useMemo } from 'react';
import { Box } from '@chakra-ui/react';
import { Switch, Route } from 'react-router-dom';

import InnerApp from '../components/general/InnerApp/InnerApp';
import LoadWrapper from '../components/general/LoadWrapper/LoadWrapper';
Expand All @@ -10,18 +9,27 @@ import I18nBind from '../components/general/I18nBind/I18nBind';
import DataFetch from '../components/DataFetch/DataFetch';
import AccountPage from '../components/account/AccountPage/AccountPage';

import { i18nInitPromise } from '../i18n';
import { useAuth } from '../components/general/AuthWrapper/AuthWrapper';
import { authenticate } from '../net/requests/auth/auth';
import { observer } from 'mobx-react';
import { useContext } from 'react';

import './App.css';
import {
useAppRedirectOnGotData,
useAuthCheck,
useLogoutRedirect,
useStartInitialLoad,
} from '../hooks/app';

interface AppState {
shouldFetchUserData: boolean;
hasData: boolean;
isLoggingOut: boolean;
}

interface AppInfo {
setShouldFetchUserData: React.Dispatch<React.SetStateAction<boolean>>;
setHasData: React.Dispatch<React.SetStateAction<boolean>>;
setIsLoggingOut: React.Dispatch<React.SetStateAction<boolean>>;
setShouldFetchUserData: (shouldFetchUserData: boolean) => void;
setHasData: (hasData: boolean) => void;
setIsLoggingOut: (isLoggingOut: boolean) => void;
}

const AppContext = React.createContext<AppInfo>(undefined!);
Expand All @@ -30,96 +38,69 @@ export const useAppContext = () => {
return useContext(AppContext);
};

function App() {
const auth = useAuth();
const history = useHistory();
const location = useLocation();
const showToast = useToast();
export enum NonAppLocation {
Account = '/account',
Logout = '/logout',
}

const [shouldFetchUserData, setShouldFetchUserData] = useState(false);
const [hasData, setHasData] = useState(false);
const [isLoggingOut, setIsLoggingOut] = useState(false);
export interface InitialLoadData {
startInitialLoad: boolean;
promiseList: Promise<unknown>[];
}

const [initialLoadData, setInitialLoadData] = useState({
startInitialLoad: false,
promiseList: [] as Promise<unknown>[],
const App = () => {
const [
{ shouldFetchUserData, hasData, isLoggingOut },
setAppState,
] = useState<AppState>({
shouldFetchUserData: false,
hasData: false,
isLoggingOut: false,
});

useEffect(() => {
if (hasData) {
if (!location.pathname.includes('app')) {
history.push('/app');
}
setShouldFetchUserData(false);
}
}, [hasData, history, location.pathname]);

useEffect(() => {
if (isLoggingOut && !hasData && !shouldFetchUserData) {
history.push('/account');
}
}, [hasData, history, isLoggingOut, shouldFetchUserData]);

const [authErr, setAuthErr] = useState<{
initialLocation: string | null;
err: boolean;
}>({
initialLocation: null,
err: false,
const [initialLoadData, setInitialLoadData] = useState<InitialLoadData>({
startInitialLoad: false,
promiseList: [],
});

const setShouldFetchUserData = useCallback(
(shouldFetchUserData: boolean) => {
setAppState((prev) => ({
...prev,
shouldFetchUserData,
}));
},
[]
);
const setHasData = useCallback((hasData: boolean) => {
setAppState((prev) => ({
...prev,
hasData,
}));
}, []);

const setIsLoggingOut = useCallback((isLoggingOut: boolean) => {
setAppState((prev) => ({
...prev,
isLoggingOut,
}));
}, []);

useAppRedirectOnGotData(hasData, setShouldFetchUserData);

useLogoutRedirect(isLoggingOut, hasData, shouldFetchUserData);

const { onAuth } = useAuthCheck(setShouldFetchUserData);
const initialLoad = useCallback(async () => {
if (history.location.pathname === '/account') {
return;
}

const [err] = await to(authenticate({ token: auth.token }));
if (err !== null) {
await i18nInitPromise;

setAuthErr({
initialLocation: history.location.pathname,
err: true,
});
history.push('/account');
} else {
setShouldFetchUserData(true);
}
}, [auth.token, history]);

useEffect(() => {
if (authErr.err && authErr.initialLocation !== '/') {
i18nInitPromise.then((t) => {
showToast({
title: t('prompt-login-title'),
description: t('prompt-login-info'),
status: 'error',
duration: 5000,
isClosable: true,
});
});
setAuthErr((prev) => {
return {
...prev,
err: false,
};
});
}
}, [authErr, history.location.pathname, showToast]);

useEffect(() => {
setInitialLoadData((prevData) => {
return {
...prevData,
promiseList: [...prevData.promiseList, initialLoad()],
startInitialLoad: true,
};
});
}, [initialLoad]);
// cannot reject, onAuth() wraps its awaited promise in a to() call
await onAuth();
}, [onAuth]);

useStartInitialLoad(initialLoad, setInitialLoadData);

const contextData = useMemo(() => {
return { setShouldFetchUserData, setHasData, setIsLoggingOut };
}, []);
}, [setHasData, setIsLoggingOut, setShouldFetchUserData]);

return (
<Box
Expand All @@ -138,13 +119,12 @@ function App() {
fetch={shouldFetchUserData}
setHasData={setHasData}
/>
{/* <ReactQueryDevtools initialIsOpen={true} /> */}
<Switch>
<Route path="/account">
<Route path={NonAppLocation.Account}>
<AccountPage />
</Route>
<Route path="/app">{hasData && <InnerApp />}</Route>
<Route path="/logout">
<Route path={NonAppLocation.Logout}>
<Logout />
</Route>
</Switch>
Expand All @@ -153,6 +133,6 @@ function App() {
</Box>
</Box>
);
}
};

export default observer(App);

0 comments on commit 10bba9b

Please sign in to comment.