Skip to content

Commit 8970463

Browse files
authored
feat(auth): Use modal for authentication (#2455)
* feat: Use modal for authentication fix: Change broken `NotificationManager` calls to `showNotify()` * fix: Ensure DOM is loaded before API connect
1 parent ff26775 commit 8970463

File tree

4 files changed

+88
-20
lines changed

4 files changed

+88
-20
lines changed

src/Main.tsx

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { lazy, Suspense } from 'react';
1+
import React, { lazy, Suspense, useEffect } from 'react';
22
import NiceModal from '@ebay/nice-modal-react';
33
import { HashRouter, Route, Switch } from 'react-router-dom';
44
import { ReactNotifications } from 'react-notifications-component';
@@ -52,13 +52,23 @@ const ConnectedDashboardPage = lazy(() =>
5252
import('./components/dashboard-page/Dashboard').then((module) => ({ default: module.ConnectedDashboardPage })),
5353
);
5454

55+
import { AuthForm } from './components/modal/components/AuthModal';
56+
57+
import api from './ws-client';
58+
5559
export function Main() {
5660
const { theme } = store.getState();
61+
62+
useEffect(() => {
63+
api.connect();
64+
}, []);
65+
5766
return (
5867
<React.StrictMode>
5968
<ReactNotifications />
6069
<I18nextProvider i18n={i18n}>
6170
<NiceModal.Provider>
71+
<AuthForm id="auth-form" onAuth={() => null} />
6272
<Provider store={store}>
6373
<ThemeSwitcherProvider
6474
themeMap={{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React, { ChangeEvent } from 'react';
2+
import { useInputChange } from '../../../hooks/useInputChange';
3+
4+
import Modal, { ModalBody, ModalFooter, ModalHeader } from '../Modal';
5+
import NiceModal, { useModal } from '@ebay/nice-modal-react';
6+
7+
type AuthFormProps = {
8+
onAuth(token: string): void;
9+
};
10+
11+
export const AuthForm = NiceModal.create((props: AuthFormProps): JSX.Element => {
12+
const { onAuth } = props;
13+
14+
const modal = useModal();
15+
16+
const [authForm, handleInputChange] = useInputChange({ token: '' });
17+
18+
const onLoginClick = async (): Promise<void> => {
19+
await onAuth(authForm['token']);
20+
modal.remove();
21+
};
22+
23+
const handleKeyDown = async (e): Promise<void> => {
24+
if (e.key == 'Enter') {
25+
onLoginClick();
26+
}
27+
};
28+
29+
return (
30+
<Modal isOpen={modal.visible}>
31+
<ModalHeader>
32+
<h3>Enter Admin Token</h3>
33+
</ModalHeader>
34+
<ModalBody>
35+
<div className="mb-3">
36+
<label className="form-label">Token</label>
37+
<input
38+
name="token"
39+
onChange={handleInputChange as (event: ChangeEvent<HTMLInputElement>) => void}
40+
onKeyDown={handleKeyDown}
41+
type="password"
42+
className="form-control"
43+
autoCapitalize="none"
44+
value={authForm['token']}
45+
/>
46+
</div>
47+
</ModalBody>
48+
<ModalFooter>
49+
<button type="button" className="btn btn-primary" onClick={onLoginClick}>
50+
Login
51+
</button>
52+
</ModalFooter>
53+
</Modal>
54+
);
55+
});

src/index.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,8 @@ import './styles/styles.global.scss';
66
import React from 'react';
77
import { createRoot } from 'react-dom/client';
88

9-
import api from './ws-client';
10-
119
import { Main } from './Main';
1210

13-
api.connect();
14-
1511
const domNode = document.getElementById('root');
1612
if (domNode) {
1713
createRoot(domNode).render(<Main />);

src/ws-client.ts

+22-15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
stringifyWithPreservingUndefinedAsNull,
1010
} from './utils';
1111

12+
import NiceModal from '@ebay/nice-modal-react';
1213
import { Store } from 'react-notifications-component';
1314
import keyBy from 'lodash/keyBy';
1415

@@ -113,21 +114,27 @@ class Api {
113114
}
114115
}
115116

116-
urlProvider = async () => {
117-
const url = new URL(this.url)
118-
let token = new URLSearchParams(window.location.search).get("token")
119-
?? local.get<string>(TOKEN_LOCAL_STORAGE_ITEM_NAME);
120-
const authRequired = !!local.get(AUTH_FLAG_LOCAL_STORAGE_ITEM_NAME);
121-
if (authRequired) {
122-
if (!token) {
123-
token = prompt("Enter your z2m admin token") as string;
124-
if (token) {
125-
local.set(TOKEN_LOCAL_STORAGE_ITEM_NAME, token);
117+
urlProvider = async (): Promise<string> => {
118+
const promise = new Promise<string>((resolve) => {
119+
const url = new URL(this.url)
120+
let token = new URLSearchParams(window.location.search).get("token")
121+
?? local.get<string>(TOKEN_LOCAL_STORAGE_ITEM_NAME);
122+
const authRequired = !!local.get(AUTH_FLAG_LOCAL_STORAGE_ITEM_NAME);
123+
if (authRequired) {
124+
if (!token) {
125+
NiceModal.show('auth-form', { onAuth: (token: string) => {
126+
local.set(TOKEN_LOCAL_STORAGE_ITEM_NAME, token);
127+
url.searchParams.append("token", token);
128+
resolve(url.toString());
129+
}});
130+
return;
126131
}
132+
url.searchParams.append("token", token);
127133
}
128-
url.searchParams.append("token", token);
129-
}
130-
return url.toString();
134+
resolve(url.toString());
135+
});
136+
137+
return promise;
131138
}
132139

133140
connect(): void {
@@ -277,7 +284,7 @@ class Api {
277284
if (e.code === UNAUTHORIZED_ERROR_CODE) {
278285
local.set(AUTH_FLAG_LOCAL_STORAGE_ITEM_NAME, true);
279286
local.remove(TOKEN_LOCAL_STORAGE_ITEM_NAME);
280-
NotificationManager.error("Unauthorized");
287+
showNotify('error', "Unauthorized", false);
281288
setTimeout(() => {
282289
window.location.reload();
283290
}, 1000);
@@ -296,7 +303,7 @@ class Api {
296303
this.processDeviceStateMessage(data);
297304
}
298305
} catch (e) {
299-
NotificationManager.error(e.message);
306+
showNotify('error', e.message, false);
300307
console.error(event.data);
301308
}
302309

0 commit comments

Comments
 (0)