feat: introduce authentication context

This commit is contained in:
Ali BARIN
2022-03-08 19:50:51 +01:00
committed by Ömer Faruk Aydın
parent 030d886cf7
commit 5d1c4b81e7
8 changed files with 103 additions and 26 deletions

View File

@@ -1,7 +1,9 @@
import * as React from 'react';
import { ApolloProvider as BaseApolloProvider } from '@apollo/client';
import { useSnackbar } from 'notistack';
import client, { setLink } from 'graphql/client';
import useAuthentication from 'hooks/useAuthentication';
type ApolloProviderProps = {
children: React.ReactNode;
@@ -9,14 +11,15 @@ type ApolloProviderProps = {
const ApolloProvider = (props: ApolloProviderProps): React.ReactElement => {
const { enqueueSnackbar } = useSnackbar();
const authentication = useAuthentication();
const onError = React.useCallback((message) => {
enqueueSnackbar(message, { variant: 'error' });
}, [enqueueSnackbar]);
React.useEffect(() => {
setLink({ onError })
}, [onError]);
setLink({ onError, token: authentication.token })
}, [onError, authentication]);
return (
<BaseApolloProvider client={client} {...props} />

View File

@@ -6,6 +6,7 @@ import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import LoadingButton from '@mui/lab/LoadingButton';
import useAuthentication from 'hooks/useAuthentication';
import * as URLS from 'config/urls';
import { setItem } from 'helpers/storage';
import { LOGIN } from 'graphql/mutations/login';
@@ -38,6 +39,7 @@ function renderFields(props: { loading: boolean }) {
required
fullWidth
margin="dense"
autoComplete="current-password"
/>
<LoadingButton
@@ -57,8 +59,15 @@ function renderFields(props: { loading: boolean }) {
function LoginForm() {
const navigate = useNavigate();
const authentication = useAuthentication();
const [login, { loading }] = useMutation(LOGIN);
React.useEffect(() => {
if (authentication.isAuthenticated) {
navigate(URLS.DASHBOARD);
}
}, [authentication.isAuthenticated]);
const handleSubmit = async (values: any) => {
const { data } = await login({
variables: {
@@ -68,9 +77,7 @@ function LoginForm() {
const { token } = data.login;
setItem('token', token);
navigate(URLS.FLOWS);
authentication.updateToken(token);
};
const render = React.useMemo(() => renderFields({ loading }), [loading]);

View File

@@ -1,4 +1,3 @@
export const DASHBOARD = '/dashboard';
export const CONNECTIONS = '/connections';
export const EXPLORE = '/explore';
@@ -26,3 +25,6 @@ export const FLOWS = '/flows';
// TODO: revert this back to /flows/:flowId once we have a proper single flow page
export const FLOW = (flowId: string): string => `/editor/${flowId}`;
export const FLOW_PATTERN = '/flows/:flowId';
export const DASHBOARD = FLOWS;

View File

@@ -0,0 +1,39 @@
import * as React from 'react';
import { getItem, setItem } from 'helpers/storage';
export type AuthenticationContextParams = {
token: string | null;
updateToken: (token: string) => void;
};
export const AuthenticationContext = React.createContext<AuthenticationContextParams>({
token: null,
updateToken: () => void 0,
});
type AuthenticationProviderProps = {
children: React.ReactNode;
};
export const AuthenticationProvider = (props: AuthenticationProviderProps): React.ReactElement => {
const { children } = props;
const [token, setToken] = React.useState(() => getItem('token'));
const value = React.useMemo(() => {
return {
token,
updateToken: (newToken: string) => {
setToken(newToken);
setItem('token', newToken);
},
};
}, [token]);
return (
<AuthenticationContext.Provider
value={value}
>
{children}
</AuthenticationContext.Provider>
);
};

View File

@@ -5,6 +5,7 @@ import appConfig from 'config/app';
type CreateClientOptions = {
onError?: (message: string) => void;
token?: string | null;
};
const client = new ApolloClient({
@@ -12,8 +13,9 @@ const client = new ApolloClient({
link: createLink({ uri: appConfig.graphqlUrl })
});
export function setLink({ onError }: CreateClientOptions): typeof client {
const link = createLink({ uri: appConfig.graphqlUrl, onError });
export function setLink(options: CreateClientOptions): typeof client {
const { onError, token } = options;
const link = createLink({ uri: appConfig.graphqlUrl, token, onError });
client.setLink(link);

View File

@@ -3,16 +3,17 @@ import type { ApolloLink } from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import * as URLS from 'config/urls';
import { getItem } from 'helpers/storage';
type CreateLinkOptions = {
uri: string;
token?: string | null;
onError?: (message: string) => void;
};
const createHttpLink = (uri: CreateLinkOptions['uri']): ApolloLink => {
const createHttpLink = (options: Pick<CreateLinkOptions, 'uri' | 'token'>): ApolloLink => {
const { uri, token } = options;
const headers = {
authorization: getItem('token') || '',
authorization: token,
};
return new HttpLink({ uri, headers });
}
@@ -41,8 +42,16 @@ const createErrorLink = (callback: CreateLinkOptions['onError']): ApolloLink =>
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};
const createLink = ({ uri, onError = noop }: CreateLinkOptions): ApolloLink => {
return from([createErrorLink(onError), createHttpLink(uri)]);
const createLink = (options: CreateLinkOptions): ApolloLink => {
const {
uri,
onError = noop,
token,
} = options;
const httpOptions = { uri, token };
return from([createErrorLink(onError), createHttpLink(httpOptions)]);
};
export default createLink;

View File

@@ -1,7 +1,19 @@
import { getItem } from 'helpers/storage';
import * as React from 'react';
import { AuthenticationContext } from 'contexts/Authentication';
import type { AuthenticationContextParams } from 'contexts/Authentication';
export default function useAuthentication(): boolean {
const token = getItem('token');
type UseAuthenticationReturn = {
isAuthenticated: boolean;
token: AuthenticationContextParams["token"];
updateToken: AuthenticationContextParams["updateToken"];
};
return Boolean(token);
export default function useAuthentication(): UseAuthenticationReturn {
const authenticationContext = React.useContext(AuthenticationContext);
return {
token: authenticationContext.token,
updateToken: authenticationContext.updateToken,
isAuthenticated: Boolean(authenticationContext.token),
};
}

View File

@@ -4,21 +4,24 @@ import ThemeProvider from 'components/ThemeProvider';
import IntlProvider from 'components/IntlProvider';
import ApolloProvider from 'components/ApolloProvider';
import SnackbarProvider from 'components/SnackbarProvider';
import { AuthenticationProvider } from 'contexts/Authentication';
import Router from 'components/Router';
import routes from 'routes';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<SnackbarProvider>
<ApolloProvider>
<IntlProvider>
<ThemeProvider>
<Router>
{routes}
</Router>
</ThemeProvider>
</IntlProvider>
</ApolloProvider>
<AuthenticationProvider>
<ApolloProvider>
<IntlProvider>
<ThemeProvider>
<Router>
{routes}
</Router>
</ThemeProvider>
</IntlProvider>
</ApolloProvider>
</AuthenticationProvider>
</SnackbarProvider>,
document.getElementById('root')
)