feat: introduce authentication context
This commit is contained in:

committed by
Ömer Faruk Aydın

parent
030d886cf7
commit
5d1c4b81e7
@@ -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} />
|
||||
|
@@ -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]);
|
||||
|
@@ -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;
|
||||
|
39
packages/web/src/contexts/Authentication.tsx
Normal file
39
packages/web/src/contexts/Authentication.tsx
Normal 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>
|
||||
);
|
||||
};
|
@@ -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);
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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),
|
||||
};
|
||||
}
|
||||
|
@@ -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')
|
||||
)
|
||||
|
Reference in New Issue
Block a user