Webatrice: KnownHosts component (#4456)

* refactor dexie services for future schema updates

Co-authored-by: Jeremy Letto <jeremy.letto@datasite.com>
This commit is contained in:
Jeremy Letto 2021-11-25 21:12:23 -06:00 committed by GitHub
parent 37879c4255
commit 6ce346af4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 1381 additions and 1291 deletions

View file

@ -0,0 +1,21 @@
.KnownHostForm {
width: 100%;
}
.KnownHostForm-item {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}
.KnownHostForm-submit {
width: 100%;
}
.KnownHostForm-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin: 5px 0 10px;
color: red;
}

View file

@ -0,0 +1,83 @@
// eslint-disable-next-line
import React, { useState } from "react";
import { connect } from 'react-redux';
import { Form, Field } from 'react-final-form'
import Button from '@material-ui/core/Button';
import AnchorLink from '@material-ui/core/Link';
import { InputField } from 'components';
import './KnownHostForm.css';
function KnownHostForm({ host, onRemove, onSubmit }) {
const [confirmDelete, setConfirmDelete] = useState(false);
return (
<Form
initialValues={{
id: host?.id,
name: host?.name,
host: host?.host,
port: host?.port,
}}
onSubmit={onSubmit}
validate={values => {
const errors: any = {};
if (!values.name) {
errors.name = 'Required'
}
if (!values.host) {
errors.host = 'Required'
}
if (!values.port) {
errors.port = 'Required'
}
if (Object.keys(errors).length) {
return errors;
}
}}
>
{({ handleSubmit }) => (
<form className="KnownHostForm" onSubmit={handleSubmit}>
<div className="KnownHostForm-item">
<Field label="Host Name" name="name" component={InputField} />
</div>
<div className="KnownHostForm-item">
<Field label="Host Address" name="host" component={InputField} />
</div>
<div className="KnownHostForm-item">
<Field label="Port" name="port" type="number" component={InputField} />
</div>
<Button className="KnownHostForm-submit" color="primary" variant="contained" type="submit">
{host ? 'Save Changes' : 'Add Host' }
</Button>
<div className="KnownHostForm-actions">
<div className="KnownHostForm-actions__delete">
{ host && (
<Button color="inherit" onClick={() => !confirmDelete ? setConfirmDelete(true) : onRemove(host)}>
{ !confirmDelete ? 'Delete' : 'Are you sure?' }
</Button>
) }
</div>
<AnchorLink href='https://github.com/Cockatrice/Cockatrice/wiki/Public-Servers' target='_blank'>
Need help adding a new host?
</AnchorLink>
</div>
</form>
) }
</Form>
);
}
const mapStateToProps = () => ({
});
export default connect(mapStateToProps)(KnownHostForm);

View file

@ -1,46 +1,130 @@
// eslint-disable-next-line
import React from "react";
import React, { Component, useCallback, useEffect, useState, useRef } from 'react';
import { connect } from 'react-redux';
import { Form, Field, reduxForm, change } from 'redux-form'
import { Form, Field, reduxForm, change, FormSubmitHandler } from 'redux-form'
import Button from '@material-ui/core/Button';
import { InputField, KnownHosts } from 'components';
// import { ServerDispatch } from "store";
import { FormKey } from 'types';
import { AuthenticationService } from 'api';
import { CheckboxField, InputField, KnownHosts } from 'components';
import { useAutoConnect } from 'hooks';
import { HostDTO, SettingDTO } from 'services';
import { FormKey, APP_USER } from 'types';
import './LoginForm.css';
const LoginForm = (props) => {
const { dispatch, handleSubmit } = props;
const PASSWORD_LABEL = 'Password';
const STORED_PASSWORD_LABEL = '* SAVED *';
const LoginForm: any = ({ dispatch, form, submit, handleSubmit }: LoginFormProps) => {
const password: any = useRef();
const [host, setHost] = useState(null);
const [remember, setRemember] = useState(false);
const [passwordLabel, setPasswordLabel] = useState(PASSWORD_LABEL);
const [hasStoredPassword, useStoredPassword] = useState(false);
const [autoConnect, setAutoConnect] = useAutoConnect(() => {
dispatch(change(form, 'autoConnect', autoConnect));
if (autoConnect && !remember) {
setRemember(true);
}
});
useEffect(() => {
SettingDTO.get(APP_USER).then((userSetting: SettingDTO) => {
if (userSetting?.autoConnect && !AuthenticationService.connectionAttemptMade()) {
HostDTO.getAll().then(hosts => {
let lastSelectedHost = hosts.find(({ lastSelected }) => lastSelected);
if (lastSelectedHost?.remember && lastSelectedHost?.hashedPassword) {
dispatch(change(form, 'selectedHost', lastSelectedHost));
dispatch(change(form, 'userName', lastSelectedHost.userName));
dispatch(change(form, 'remember', true));
setPasswordLabel(STORED_PASSWORD_LABEL);
dispatch(submit);
}
});
}
});
}, [submit, dispatch, form]);
useEffect(() => {
dispatch(change(form, 'remember', remember));
if (!remember) {
setAutoConnect(false);
}
if (!remember) {
useStoredPassword(false);
setPasswordLabel(PASSWORD_LABEL);
} else if (host?.hashedPassword) {
useStoredPassword(true);
setPasswordLabel(STORED_PASSWORD_LABEL);
}
}, [remember, dispatch, form]);
useEffect(() => {
if (!host) {
return
}
dispatch(change(form, 'userName', host.userName));
dispatch(change(form, 'password', ''));
setRemember(host.remember);
setAutoConnect(host.remember && autoConnect);
if (host.remember && host.hashedPassword) {
// TODO: check if this causes a double render (maybe try combined state)
// try deriving useStoredPassword
useStoredPassword(true);
setPasswordLabel(STORED_PASSWORD_LABEL);
} else {
useStoredPassword(false);
setPasswordLabel(PASSWORD_LABEL);
}
}, [host, dispatch, form]);
const onRememberChange = event => setRemember(event.target.checked);
const onAutoConnectChange = event => setAutoConnect(event.target.checked);
const onHostChange = h => setHost(h);
const forgotPassword = () => {
console.log('Show recover password dialog, then AuthService.forgotPasswordRequest');
};
const onHostChange = ({ host, port }) => {
dispatch(change(FormKey.LOGIN, 'host', host));
dispatch(change(FormKey.LOGIN, 'port', port));
}
return (
<Form className="loginForm" onSubmit={handleSubmit}>
<div className="loginForm-items">
<div className="loginForm-item">
<Field label="Username" name="user" component={InputField} autoComplete="username" />
<Form className='loginForm' onSubmit={handleSubmit}>
<div className='loginForm-items'>
<div className='loginForm-item'>
<Field label='Username' name='userName' component={InputField} autoComplete='off' />
</div>
<div className="loginForm-item">
<Field label="Password" name="pass" type="password" component={InputField} autoComplete="current-password" />
<div className='loginForm-item'>
<Field
label={passwordLabel}
ref={password}
onFocus={() => setPasswordLabel(PASSWORD_LABEL)}
onBlur={() => !password.current.value && hasStoredPassword && setPasswordLabel(STORED_PASSWORD_LABEL)}
name='password'
type='password'
component={InputField}
autoComplete='new-password'
/>
</div>
<div className="loginForm-actions">
<span>Remember Me</span>
<Button color="primary" onClick={forgotPassword}>Forgot Password</Button>
<div className='loginForm-actions'>
<Field label='Save Password' name='remember' component={CheckboxField} onChange={onRememberChange} />
<Button color='primary' onClick={forgotPassword}>Forgot Password</Button>
</div>
<div className="loginForm-item">
<KnownHosts onChange={onHostChange} />
<div className='loginForm-item'>
<Field name='selectedHost' component={KnownHosts} onChange={onHostChange} />
</div>
<div className='loginForm-actions'>
<Field label='Auto Connect' name='autoConnect' component={CheckboxField} onChange={onAutoConnectChange} />
</div>
</div>
<Button className="loginForm-submit rounded tall" color="primary" variant="contained" type="submit">
<Button className='loginForm-submit rounded tall' color='primary' variant='contained' type='submit'>
Login
</Button>
</Form>
@ -55,27 +139,28 @@ const propsMap = {
if (!values.user) {
errors.user = 'Required';
}
if (!values.pass) {
errors.pass = 'Required';
if (!values.password && !values.selectedHost?.hashedPassword) {
errors.password = 'Required';
}
if (!values.host) {
errors.host = 'Required';
}
if (!values.port) {
errors.port = 'Required';
if (!values.selectedHost) {
errors.selectedHost = 'Required';
}
return errors;
}
};
const mapStateToProps = () => ({
initialValues: {
// host: "mtg.tetrarch.co/servatrice",
// port: "443"
// host: "server.cockatrice.us",
// port: "4748"
}
interface LoginFormProps {
form: string;
dispatch: Function;
submit: Function;
handleSubmit: FormSubmitHandler;
}
const mapStateToProps = (state) => ({
});
export default connect(mapStateToProps)(reduxForm(propsMap)(LoginForm));

View file

@ -1,5 +1,5 @@
// eslint-disable-next-line
import React, { Component } from 'react';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Form, Field, reduxForm, change } from 'redux-form'
@ -13,15 +13,16 @@ import './RegisterForm.css';
const RegisterForm = (props) => {
const { dispatch, handleSubmit } = props;
const onHostChange = ({ host, port }) => {
const onHostChange: any = ({ host, port }) => {
dispatch(change(FormKey.REGISTER, 'host', host));
dispatch(change(FormKey.REGISTER, 'port', port));
}
return (
<Form className="registerForm row" onSubmit={handleSubmit} autoComplete="off">
<div className="leftRegisterForm column" >
<div className="registerForm-item">
<KnownHosts onChange={onHostChange} />
<Field name="selectedHost" component={KnownHosts} onChange={onHostChange} />
{ /* Padding is off */ }
</div>
<div className="registerForm-item">

View file

@ -17,7 +17,7 @@ const RequestPasswordResetForm = (props) => {
const [errorMessage, setErrorMessage] = useState(false);
const [isMFA, setIsMFA] = useState(false);
const onHostChange = ({ host, port }) => {
const onHostChange: any = ({ host, port }) => {
dispatch(change(FormKey.RESET_PASSWORD_REQUEST, 'host', host));
dispatch(change(FormKey.RESET_PASSWORD_REQUEST, 'port', port));
}
@ -51,7 +51,7 @@ const RequestPasswordResetForm = (props) => {
</div>
) : null}
<div className="RequestPasswordResetForm-item">
<KnownHosts onChange={onHostChange} />
<Field name='selectedHost' component={KnownHosts} onChange={onHostChange} />
</div>
</div>
<Button className="RequestPasswordResetForm-submit rounded tall" color="primary" variant="contained" type="submit">

View file

@ -18,7 +18,7 @@ const ResetPasswordForm = (props) => {
const [errorMessage, setErrorMessage] = useState(false);
const onHostChange = ({ host, port }) => {
const onHostChange: any = ({ host, port }) => {
dispatch(change(FormKey.RESET_PASSWORD, 'host', host));
dispatch(change(FormKey.RESET_PASSWORD, 'port', port));
}
@ -47,7 +47,7 @@ const ResetPasswordForm = (props) => {
<Field label="Password Again" name="passwordAgain" component={InputField} />
</div>
<div className="ResetPasswordForm-item">
<KnownHosts onChange={onHostChange} />
<Field name='selectedHost' component={KnownHosts} onChange={onHostChange} />
</div>
</div>
<Button className="ResetPasswordForm-submit rounded tall" color="primary" variant="contained" type="submit">

View file

@ -1,6 +1,7 @@
export { default as CardImportForm } from './CardImportForm/CardImportForm';
export { default as ConnectForm } from './ConnectForm/ConnectForm';
export { default as LoginForm } from './LoginForm/LoginForm';
export { default as KnownHostForm } from './KnownHostForm/KnownHostForm';
export { default as RegisterForm } from './RegisterForm/RegisterForm';
export { default as SearchForm } from './SearchForm/SearchForm';
export { default as RequestPasswordResetForm } from './RequestPasswordResetForm/RequestPasswordResetForm';