Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Walter/login view #67

Merged
merged 7 commits into from
Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -396,10 +396,10 @@ const AppComponent: React.VoidFunctionComponent = () => {
{loggedIn ? <Home detail={override} /> : <Redirect to='/login' />}
</Route>
<Route exact path='/login'>
{loggedIn ? <Redirect to='/' /> : <Login />}
{loggedIn ? <Redirect to='/' /> : <Login key='login' />}
</Route>
<Route exact path='/register'>
{loggedIn ? <Redirect to='/' /> : <Login registering />}
{loggedIn ? <Redirect to='/' /> : <Login registering key='register' />}
</Route>
<Route>
<Redirect to='/' />
Expand Down
140 changes: 140 additions & 0 deletions web/src/__tests__/views/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { create } from 'react-test-renderer';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { Login } from '../../views/Login';
import { AppProvider, IAppContext } from '../../AppContext';

const mockApp: IAppContext = {
searchQuery: '',
newCard() { throw new Error('Not Implemented'); },
update() { throw new Error('Not Implemented'); },
showCardDetail() { throw new Error('Not Implemented'); },
user: null,
login: jest.fn(),
logout() { throw new Error('Not Implemented'); },
};

describe('Login view render tests', () => {
beforeAll(() => {

});

test('Login Render Test', () => {
const tree = create(
<MemoryRouter>
<AppProvider value={mockApp}>
<Login registering={false} key='login' />
</AppProvider>
</MemoryRouter>,
).toJSON();
expect(tree).toMatchInlineSnapshot(`
<div
className="bodyStyle-53"
id="container"
>
<div
className="ms-Stack css-54"
>
<div
className="ms-TextField root-56"
>
<div
className="ms-TextField-wrapper"
>
<div
className="ms-TextField-fieldGroup fieldGroup-57"
>
<input
aria-invalid={false}
className="ms-TextField-field field-58"
id="TextField0"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onInput={[Function]}
placeholder="Username"
type="text"
value=""
/>
</div>
</div>
</div>
<div
className="ms-TextField root-56"
>
<div
className="ms-TextField-wrapper"
>
<div
className="ms-TextField-fieldGroup fieldGroup-57"
>
<input
aria-invalid={false}
className="ms-TextField-field field-58"
id="TextField3"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onInput={[Function]}
placeholder="Password"
type="password"
value=""
/>
</div>
</div>
</div>
<button
className="ms-Button ms-Button--primary root-67"
data-is-focusable={true}
onClick={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
type="button"
>
<span
className="ms-Button-flexContainer flexContainer-68"
data-automationid="splitbuttonprimary"
>
<span
className="ms-Button-textContainer textContainer-69"
>
<span
className="ms-Button-label label-71"
id="id__6"
>
Log in
</span>
</span>
</span>
</button>
<div
className="ms-StackItem css-75"
>
<label
className="ms-Label root-76"
>
<a
href="/register"
onClick={[Function]}
>
Register to get started
</a>
</label>
</div>
<div
className="ms-StackItem css-75"
>
<label
className="ms-Label root-77"
>
Can't log in?
</label>
</div>
</div>
</div>
`);
});
});
105 changes: 102 additions & 3 deletions web/src/views/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,111 @@
import React from 'react';
import {
Label,
PrimaryButton, Stack, TextField,
mergeStyleSets,
} from '@fluentui/react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { useApp } from '../AppContext';

export interface ILoginProps {
registering?: boolean;
}

/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
export const Login: React.VoidFunctionComponent<ILoginProps> = ({ registering }) => <></>;
const getClassNames = () => mergeStyleSets({
bodyStyle: {
display: 'flex',
height: '100%',
justifyContent: 'space-around',
overflow: 'auto',
},
});

export const Login: React.VoidFunctionComponent<ILoginProps> = ({ registering }) => {
// get context
const [username, setUsername] = useState<string>('');
const [password, setPassword] = useState<string>('');

const app = useApp();

const canSubmit = username && password;

const onLoginClick = () => {
if (canSubmit) {
(async () => {
const res = await app.login(username, password, registering);
if (!res.ok) {
// Show error dialog
}
})();
}
};

// rudimentary controlled input event catchers
const emptyField = (fieldValue: string, fieldName: string) => ((fieldValue) ? '' : `${fieldName} is required`);

const { bodyStyle } = getClassNames();

const textFieldStyles = {
root: {
height: '53px',
},
};

const linkLabelStyles = {
root: {
cursor: 'pointer',
},
};

return (
<div className={bodyStyle} id='container'>
<Stack tokens={{ childrenGap: '8px' }}>
<TextField
placeholder='Username'
key={registering ? 'userRegister' : 'userLogin'}
value={username}
onGetErrorMessage={(value) => emptyField(value, 'Username')}
validateOnFocusOut
validateOnLoad={false}
onChange={(event) => setUsername(event.currentTarget.value)}
styles={textFieldStyles}
/>
<TextField
placeholder='Password'
type='password'
key={registering ? 'passRegister' : 'passLogin'}
value={password}
onGetErrorMessage={(value) => emptyField(value, 'Password')}
validateOnFocusOut
validateOnLoad={false}
onChange={(event) => setPassword(event.currentTarget.value)}
styles={textFieldStyles}
/>
<PrimaryButton
onClick={onLoginClick}
text={registering ? 'Register' : 'Log in'}
/>
<Stack.Item align='center'>
<Label styles={linkLabelStyles}>
<Link to={registering ? '/login' : '/register'}>
{
registering
? 'Already with an account? Sign in here.'
: 'Register to get started'
}
</Link>
</Label>
</Stack.Item>
<Stack.Item align='center'>
<Label>
Can&apos;t log in?
</Label>
</Stack.Item>
</Stack>
</div>
);
};

Login.propTypes = {
registering: PropTypes.bool,
Expand Down