Basic authentication forms (#347)
This commit is contained in:
parent
a293e95979
commit
a3527e0b77
9
.env
9
.env
|
@ -1,2 +1,9 @@
|
||||||
BROWSER=none
|
|
||||||
NPM_GITHUB_TOKEN=
|
NPM_GITHUB_TOKEN=
|
||||||
|
REACT_APP_FIREBASE_APP_SENTRY_DSN=
|
||||||
|
REACT_APP_FIREBASE_API_KEY=
|
||||||
|
REACT_APP_FIREBASE_AUTH_DOMAIN=
|
||||||
|
REACT_APP_FIREBASE_PROJECT_ID=
|
||||||
|
REACT_APP_FIREBASE_STORAGE_BUCKET=
|
||||||
|
REACT_APP_FIREBASE_MESSAGING_SENDER_ID=
|
||||||
|
REACT_APP_FIREBASE_APP_ID=
|
||||||
|
REACT_APP_FIREBASE_MEASUREMENT_ID=
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -35,6 +35,7 @@
|
||||||
"@sentry/tracing": "^6.16.1",
|
"@sentry/tracing": "^6.16.1",
|
||||||
"@speedy-tuner/types": "^0.2.1",
|
"@speedy-tuner/types": "^0.2.1",
|
||||||
"antd": "^4.17.4",
|
"antd": "^4.17.4",
|
||||||
|
"firebase": "^9.6.1",
|
||||||
"mlg-converter": "^0.5.1",
|
"mlg-converter": "^0.5.1",
|
||||||
"pako": "^2.0.4",
|
"pako": "^2.0.4",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
|
|
|
@ -64,6 +64,10 @@ html, body {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-checkbox-wrapper {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
|
|
||||||
|
|
52
src/App.tsx
52
src/App.tsx
|
@ -10,6 +10,8 @@ import {
|
||||||
import { Layout } from 'antd';
|
import { Layout } from 'antd';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
@ -32,6 +34,8 @@ import Log from './components/Log';
|
||||||
import Diagnose from './components/Diagnose';
|
import Diagnose from './components/Diagnose';
|
||||||
import useStorage from './hooks/useStorage';
|
import useStorage from './hooks/useStorage';
|
||||||
import useConfig from './hooks/useConfig';
|
import useConfig from './hooks/useConfig';
|
||||||
|
import Login from './components/Auth/Login';
|
||||||
|
import SignUp from './components/Auth/SignUp';
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
|
|
||||||
|
@ -73,7 +77,21 @@ const App = ({ ui, config }: { ui: UIState, config: ConfigType }) => {
|
||||||
// window.removeEventListener('beforeunload', beforeUnload);
|
// window.removeEventListener('beforeunload', beforeUnload);
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const ContentFor = useCallback((props: { children: ReactNode, marginLeft?: number }) => {
|
||||||
|
const { children, marginLeft } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout style={{ marginLeft }}>
|
||||||
|
<Layout className="app-content">
|
||||||
|
<Content>
|
||||||
|
{children}
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -102,22 +120,24 @@ const App = ({ ui, config }: { ui: UIState, config: ConfigType }) => {
|
||||||
</Layout>
|
</Layout>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path={Routes.LOG}>
|
<Route path={Routes.LOG}>
|
||||||
<Layout style={{ marginLeft: margin }}>
|
<ContentFor marginLeft={margin}>
|
||||||
<Layout className="app-content">
|
<Log />
|
||||||
<Content>
|
</ContentFor>
|
||||||
<Log />
|
|
||||||
</Content>
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route>
|
<Route path={Routes.DIAGNOSE}>
|
||||||
<Layout style={{ marginLeft: margin }}>
|
<ContentFor marginLeft={margin}>
|
||||||
<Layout className="app-content">
|
<Diagnose />
|
||||||
<Content>
|
</ContentFor>
|
||||||
<Diagnose />
|
</Route>
|
||||||
</Content>
|
<Route path={Routes.LOGIN}>
|
||||||
</Layout>
|
<ContentFor>
|
||||||
</Layout>
|
<Login />
|
||||||
|
</ContentFor>
|
||||||
|
</Route>
|
||||||
|
<Route path={Routes.SIGN_UP}>
|
||||||
|
<ContentFor>
|
||||||
|
<SignUp />
|
||||||
|
</ContentFor>
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
notification,
|
||||||
|
} from 'antd';
|
||||||
|
import {
|
||||||
|
MailOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
import { Routes } from '../../routes';
|
||||||
|
import validateMessages from './validateMessages';
|
||||||
|
|
||||||
|
const { Item } = Form;
|
||||||
|
|
||||||
|
const Login = () => {
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { login } = useAuth();
|
||||||
|
|
||||||
|
const onFinish = async ({ email, password }: { email: string, password: string }) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await login(email, password);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
notification.error({
|
||||||
|
message: 'Login failed',
|
||||||
|
description: (err as Error).message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
padding: 20,
|
||||||
|
maxWidth: 350,
|
||||||
|
margin: '0 auto',
|
||||||
|
}}>
|
||||||
|
<Divider>Login</Divider>
|
||||||
|
<Form
|
||||||
|
initialValues={{ remember: true }}
|
||||||
|
onFinish={onFinish}
|
||||||
|
validateMessages={validateMessages}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Item
|
||||||
|
name="email"
|
||||||
|
rules={[{ required: true, type: 'email' }]}
|
||||||
|
hasFeedback
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
prefix={<MailOutlined />}
|
||||||
|
placeholder="Email"
|
||||||
|
/>
|
||||||
|
</Item>
|
||||||
|
<Item
|
||||||
|
name="password"
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
hasFeedback
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
placeholder="Password"
|
||||||
|
prefix={<LockOutlined />}
|
||||||
|
/>
|
||||||
|
</Item>
|
||||||
|
<Item>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
Log in
|
||||||
|
</Button>
|
||||||
|
</Item>
|
||||||
|
<Link to={Routes.SIGN_UP}>Sign Up now!</Link>
|
||||||
|
<Link to="/" style={{ float: 'right' }}>
|
||||||
|
Forgot password?
|
||||||
|
</Link>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Login;
|
|
@ -0,0 +1,115 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
notification,
|
||||||
|
} from 'antd';
|
||||||
|
import {
|
||||||
|
MailOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
import { Routes } from '../../routes';
|
||||||
|
import validateMessages from './validateMessages';
|
||||||
|
|
||||||
|
const { Item } = Form;
|
||||||
|
|
||||||
|
const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/;
|
||||||
|
|
||||||
|
const SignUp = () => {
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { signUp } = useAuth();
|
||||||
|
|
||||||
|
const onFinish = async ({ email, password }: { email: string, password: string }) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await signUp(email, password);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
notification.error({
|
||||||
|
message: 'Failed to create an account',
|
||||||
|
description: (err as Error).message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
padding: 20,
|
||||||
|
maxWidth: 350,
|
||||||
|
margin: '0 auto',
|
||||||
|
}}>
|
||||||
|
<Divider>Sign Up</Divider>
|
||||||
|
<Form
|
||||||
|
initialValues={{ remember: true }}
|
||||||
|
onFinish={onFinish}
|
||||||
|
validateMessages={validateMessages}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Item
|
||||||
|
name="email"
|
||||||
|
rules={[{ required: true, type: 'email' }]}
|
||||||
|
hasFeedback
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
prefix={<MailOutlined />}
|
||||||
|
placeholder="Email"
|
||||||
|
/>
|
||||||
|
</Item>
|
||||||
|
<Item
|
||||||
|
name="password"
|
||||||
|
rules={[
|
||||||
|
{ required: true },
|
||||||
|
{ pattern: strongPassword, message: 'Password is too weak!' },
|
||||||
|
]}
|
||||||
|
hasFeedback
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
placeholder="Password"
|
||||||
|
prefix={<LockOutlined />}
|
||||||
|
/>
|
||||||
|
</Item>
|
||||||
|
<Item
|
||||||
|
name="passwordConfirmation"
|
||||||
|
rules={[
|
||||||
|
{ required: true },
|
||||||
|
({ getFieldValue }) => ({
|
||||||
|
validator(_, value) {
|
||||||
|
if (!value || getFieldValue('password') === value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(new Error('Passwords don\'t match!'));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
hasFeedback
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
placeholder="Password confirmation"
|
||||||
|
prefix={<LockOutlined />}
|
||||||
|
/>
|
||||||
|
</Item>
|
||||||
|
<Item>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
Sign up
|
||||||
|
</Button>
|
||||||
|
</Item>
|
||||||
|
Or <Link to={Routes.LOGIN}>login</Link> if you already have an account!
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SignUp;
|
|
@ -0,0 +1,8 @@
|
||||||
|
const validateMessages = {
|
||||||
|
required: 'This field is required!',
|
||||||
|
types: {
|
||||||
|
email: 'This is not a valid email!',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default validateMessages;
|
|
@ -5,7 +5,10 @@ import {
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
import {
|
||||||
|
InfoCircleOutlined,
|
||||||
|
FieldTimeOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
|
@ -22,19 +25,22 @@ const mapStateToProps = (state: AppState) => ({
|
||||||
|
|
||||||
const firmware = (signature: string) => (
|
const firmware = (signature: string) => (
|
||||||
<Space>
|
<Space>
|
||||||
<InfoCircleOutlined />{signature}
|
<InfoCircleOutlined />
|
||||||
|
{signature}
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|
||||||
const StatusBar = ({ status, config }: { status: StatusState, config: ConfigState }) => (
|
const StatusBar = ({ status, config }: { status: StatusState, config: ConfigState }) => (
|
||||||
<Footer className="app-status-bar">
|
<Footer className="app-status-bar">
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={8} />
|
<Col span={12}>
|
||||||
<Col span={8} style={{ textAlign: 'center' }}>
|
|
||||||
{config.megaTune && firmware(config.megaTune.signature)}
|
{config.megaTune && firmware(config.megaTune.signature)}
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8} style={{ textAlign: 'right' }}>
|
<Col span={12} style={{ textAlign: 'right' }}>
|
||||||
{status.text}
|
<Space>
|
||||||
|
<FieldTimeOutlined />
|
||||||
|
{status.text}
|
||||||
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Footer>
|
</Footer>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
useLocation,
|
useLocation,
|
||||||
useHistory,
|
useHistory,
|
||||||
} from 'react-router';
|
} from 'react-router';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Layout,
|
Layout,
|
||||||
Space,
|
Space,
|
||||||
|
@ -38,6 +39,8 @@ import {
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
ToolOutlined,
|
ToolOutlined,
|
||||||
FundOutlined,
|
FundOutlined,
|
||||||
|
UserAddOutlined,
|
||||||
|
LogoutOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
useEffect,
|
useEffect,
|
||||||
|
@ -51,6 +54,7 @@ import {
|
||||||
isToggleSidebar,
|
isToggleSidebar,
|
||||||
} from '../utils/keyboard/shortcuts';
|
} from '../utils/keyboard/shortcuts';
|
||||||
import { Routes } from '../routes';
|
import { Routes } from '../routes';
|
||||||
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
|
|
||||||
const { Header } = Layout;
|
const { Header } = Layout;
|
||||||
const { useBreakpoint } = Grid;
|
const { useBreakpoint } = Grid;
|
||||||
|
@ -59,16 +63,32 @@ const { SubMenu } = Menu;
|
||||||
const TopBar = () => {
|
const TopBar = () => {
|
||||||
const { sm } = useBreakpoint();
|
const { sm } = useBreakpoint();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
const { currentUser, logout } = useAuth();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const matchedTabPath = useMemo(() => matchPath(pathname, { path: Routes.TAB }), [pathname]);
|
const matchedTabPath = useMemo(() => matchPath(pathname, { path: Routes.TAB }), [pathname]);
|
||||||
|
|
||||||
const userMenu = (
|
const userMenu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item key="login" disabled icon={<LoginOutlined />}>
|
{currentUser ? (
|
||||||
Login / Sign-up
|
<Menu.Item key="logout" icon={<LogoutOutlined />} onClick={logout}>
|
||||||
</Menu.Item>
|
Logout
|
||||||
|
</Menu.Item>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Menu.Item key="login" icon={<LoginOutlined />}>
|
||||||
|
<Link to={Routes.LOGIN}>Login</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="sign-up" icon={<UserAddOutlined />}>
|
||||||
|
<Link to={Routes.SIGN_UP}>Sign Up</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Menu.Item key="gh" icon={<GithubOutlined />}>
|
<Menu.Item key="gh" icon={<GithubOutlined />}>
|
||||||
<a href="https://github.com/speedy-tuner/speedy-tuner-cloud" target="__blank" rel="noopener noreferrer">
|
<a
|
||||||
|
href="https://github.com/speedy-tuner/speedy-tuner-cloud"
|
||||||
|
target="__blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
GitHub
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { UserCredential } from 'firebase/auth';
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
ReactNode,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import {
|
||||||
|
auth,
|
||||||
|
createUserWithEmailAndPassword,
|
||||||
|
signInWithEmailAndPassword,
|
||||||
|
signOut,
|
||||||
|
} from '../firebase';
|
||||||
|
|
||||||
|
const AuthContext = createContext<any>(null);
|
||||||
|
|
||||||
|
interface AuthValue {
|
||||||
|
currentUser?: UserCredential,
|
||||||
|
signUp: (email: string, password: string) => Promise<UserCredential>,
|
||||||
|
login: (email: string, password: string) => Promise<UserCredential>,
|
||||||
|
logout: () => Promise<void>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const useAuth = () => useContext<AuthValue>(AuthContext);
|
||||||
|
|
||||||
|
const AuthProvider = (props: { children: ReactNode }) => {
|
||||||
|
const { children } = props;
|
||||||
|
const [currentUser, setCurrentUser] = useState<any | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
const value = useMemo(() => ({
|
||||||
|
currentUser,
|
||||||
|
signUp: (email: string, password: string) => createUserWithEmailAndPassword(auth, email, password),
|
||||||
|
login: (email: string, password: string) => signInWithEmailAndPassword(auth, email, password),
|
||||||
|
logout: () => signOut(auth),
|
||||||
|
}), [currentUser]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = auth.onAuthStateChanged((user) => {
|
||||||
|
setCurrentUser(user);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return unsubscribe;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={value}>
|
||||||
|
{!isLoading && children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
useAuth,
|
||||||
|
AuthProvider,
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { initializeApp } from 'firebase/app';
|
||||||
|
import {
|
||||||
|
getAuth,
|
||||||
|
createUserWithEmailAndPassword,
|
||||||
|
signInWithEmailAndPassword,
|
||||||
|
signOut,
|
||||||
|
} from 'firebase/auth';
|
||||||
|
import { getAnalytics } from 'firebase/analytics';
|
||||||
|
|
||||||
|
const firebaseConfig = {
|
||||||
|
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
|
||||||
|
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
|
||||||
|
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
|
||||||
|
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
|
||||||
|
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
|
||||||
|
appId: process.env.REACT_APP_FIREBASE_APP_ID,
|
||||||
|
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID,
|
||||||
|
};
|
||||||
|
|
||||||
|
const firebase = initializeApp(firebaseConfig);
|
||||||
|
const analytics = getAnalytics(firebase);
|
||||||
|
const auth = getAuth(firebase);
|
||||||
|
|
||||||
|
export {
|
||||||
|
auth,
|
||||||
|
analytics,
|
||||||
|
createUserWithEmailAndPassword,
|
||||||
|
signInWithEmailAndPassword,
|
||||||
|
signOut,
|
||||||
|
};
|
|
@ -10,6 +10,7 @@ import {
|
||||||
isProduction,
|
isProduction,
|
||||||
sentryDsn,
|
sentryDsn,
|
||||||
} from './utils/env';
|
} from './utils/env';
|
||||||
|
import { AuthProvider } from './contexts/AuthContext';
|
||||||
|
|
||||||
if (isProduction) {
|
if (isProduction) {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
|
@ -22,9 +23,11 @@ if (isProduction) {
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Provider store={store}>
|
<AuthProvider>
|
||||||
<App />
|
<Provider store={store}>
|
||||||
</Provider>
|
<App />
|
||||||
|
</Provider>
|
||||||
|
</AuthProvider>
|
||||||
</HashRouter>,
|
</HashRouter>,
|
||||||
document.getElementById('root'),
|
document.getElementById('root'),
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,4 +6,8 @@ export enum Routes {
|
||||||
DIALOG = '/tune/:category/:dialog',
|
DIALOG = '/tune/:category/:dialog',
|
||||||
LOG = '/log',
|
LOG = '/log',
|
||||||
DIAGNOSE = '/diagnose',
|
DIAGNOSE = '/diagnose',
|
||||||
|
LOGIN = '/login',
|
||||||
|
LOGOUT = '/logout',
|
||||||
|
SIGN_UP = '/sign-up',
|
||||||
|
FORGOT_PASSWORD = '/forgot-password',
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ export const loadLogs = (onProgress?: onProgressType, signal?: AbortSignal) =>
|
||||||
export const loadCompositeLogs = (onProgress?: onProgressType, signal?: AbortSignal) =>
|
export const loadCompositeLogs = (onProgress?: onProgressType, signal?: AbortSignal) =>
|
||||||
fetchWithProgress(
|
fetchWithProgress(
|
||||||
'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/composite_1_2.csv.gz',
|
'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/composite_1_2.csv.gz',
|
||||||
|
// 'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/composite_miata.csv.gz',
|
||||||
// 'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/2.csv.gz',
|
// 'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/2.csv.gz',
|
||||||
onProgress,
|
onProgress,
|
||||||
signal,
|
signal,
|
||||||
|
|
Loading…
Reference in New Issue